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;
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
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.
Top comments (0)