DEV Community

catrina
catrina

Posted on • Originally published at catrina.me on

Print PDF's on Azure Using an API and RazorLight

The Problem

I recently had an issue with printing a report to PDF using Microsoft Reporting Service and a RDLC file, etc. Something similar to this. Unfortunately, it worked great in development, but refused to work once deployed into Azure. No matter what I did, I could not duck the GDI errors I kept getting, and apparently this continues through a line of various PDF exporting extensions, all of which rely on GDI for export. Turns out, I'm not alone in facing this problem and so, I decided to find a solution.

The Solution

My general idea was to use something to render my PDF view, send that view as one long html string to this free PDF microservice by @alvercarto and get the PDF in return.

Render View as HTML

I passed an ID to my PrintAsync function, retrieved it's contents into my view model, but needed to pass that view model into a view, render it, grab it's HTML and send it to the API.

RazorLight

For rendering the model in Razor, I used @toddams RazorLight. (be sure to use 2.0-beta1 version if you are on Core 2.0)

It's as easy as declaring a hosting environment variable (used to grab the path of your view). I created a folder "pdf" under wwwroot for ease of access:

private readonly IHostingEnvironment _hostingEnvironment;
Enter fullscreen mode Exit fullscreen mode

Instantiate it in the constructor:

_hostingEnvironment = hostingEnvironment;
Enter fullscreen mode Exit fullscreen mode

and use that to bring in your web root path.

var engine = new RazorLightEngineBuilder()
              .UseFilesystemProject(_hostingEnvironment.WebRootPath + "\\pdf\\")
              .UseMemoryCachingProvider()
              .Build();

var view = await engine.CompileRenderAsync("PDF.cshtml", invoiceVM);
Enter fullscreen mode Exit fullscreen mode

Notes on the View

Take special note on how you set up your view. No need to pass the model up top, and to make the path of my layout file easy, I just dropped it in same folder as my PDF view. Also notice how it just refers to @Model. Here's a snippet from my PDF view:

@{
    Layout = "CleanLayout.cshtml";
}
<div class="container">
    <div class="columns">
        <div class="column"><b>Invoice Date: </b>@Model.InvoiceDate</div>
        <div class="column"><b>Account #: </b>@Model.Account</div>
        <div class="column"><b>Invoice #: </b>@Model.InvoiceNumber</div>
    </div>
</div>
Enter fullscreen mode Exit fullscreen mode

Pass to API and Get Bytes to PDF

Now that I had the HTML string I needed, I was easily able to create a JSON request and post to the api:

var httpWebRequest = (HttpWebRequest)WebRequest.Create("https://url-to-pdf-api.herokuapp.com/api/render");

//2cm margins with emulateScreenMedia for css
httpWebRequest.ContentType = "application/json";
httpWebRequest.Method = "POST";

            var json = new {
                html = view,
                emulateScreenMedia = false,
                pdf = new {
                    margin = new {
                        top ="2cm", 
                        left = "2cm", 
                        right = "2cm", 
                        bottom = "2cm"
                    }
                }
            };

var asJson = JsonConvert.SerializeObject(json);

using (var streamWriter = new StreamWriter(httpWebRequest.GetRequestStream()))
{
    streamWriter.Write(asJson);
    streamWriter.Flush();
    streamWriter.Close();
}
Enter fullscreen mode Exit fullscreen mode

This was successful, the only problem being the httpresponse returns in bytes and needs to be saved as a PDF file. Here's that solution:

var httpResponse = (HttpWebResponse)httpWebRequest.GetResponse();

Stream objStream = httpResponse.GetResponseStream();
var response = File(objStream, "application/pdf"); 

return response;
Enter fullscreen mode Exit fullscreen mode

So, I simply have a print button to call this method:

@Html.ActionLink("Print Claim", "PrintAsync", new { id = Model.Id }, new { @class = "button is-info" })
Enter fullscreen mode Exit fullscreen mode

Deploying onto Azure, I got a Microsoft.Win32 error, so I had to add this to my .csproj file:

<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>
Enter fullscreen mode Exit fullscreen mode

And done! Of course, I still gotta work on the styling, margins of PDF, but it was enough to get started.

Top comments (0)