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 a free PDF microservice 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;

Instantiate it in the constructor:

_hostingEnvironment = hostingEnvironment;

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);

 

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>

 

Pass to API and Get Bytes to PDF

UPDATE (Oct 16, 2018):  The API suggested below stopped working!  For this API part jump to my updated post  then and check back at the bottom of this post in case you get that win32 error on deployment.

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();
}

 

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;

 

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" })

 

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

<MvcRazorExcludeRefAssembliesFromPublish>false</MvcRazorExcludeRefAssembliesFromPublish>

 

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