I work on quite a few line of business applications, and a common ask is for printable reports. I could use a tool like SQL Server Reporting Services, but to be honest the report building and styling of those tools is difficult to say the least. Instead, I’d much rather work with HTML and CSS, and in particular, MVC and Razor as templating. My ideal would be to build PDFs as I would regular web pages.
Luckily, this is quite straightforward with HTML to PDF conversion tools. The one I’ve been using recently is EVO HTML to PDF Converter, which lets me work directly with streams (also available via NuGet).
The general idea is to take advantage of media queries in CSS to provide specific CSS for “print” that removes colors, headers, navigation, buttons and so on. With Chrome, I can enable/disable CSS media emulation with a simple checkbox, making it very simple to use my normal web developer tools to tweak styling in a browser and push updates to my SASS/LESS files.
To make it super simple to output PDF from any page, I’d like to just use a querystring parameter to turn PDF generation on/off for any page in my site. This allows us to re-use pages for viewing/printing/saving, and provide a consistent look and feel from printing or saving as a PDF.
We want to have this:
Look like this:
The print media version, but output as PDF.
Integrating PDF Generation
At the very heart of ASP.NET, we have our HTTP module pipeline. What we’d like to do is handle a request, let ASP.NET MVC handle it, and then modify it as the request goes out the door using a response filter:
We’ll attach our filter at the beginning of the request, wait for the normal MVC HTTP Handler to execute, and modify the stream as it gets writing to the Response stream. Here’s our PDF module:
First, we attach ourselves to an “early enough” event. In this case, PreRequest worked for me. I detect to see if there is a “Pdf” request parameter (the value doesn’t matter), and if it doesn’t, I just return and leave the request alone. I could pick anything here, as long as it doesn’t interfere with any other potential request parameters my application might need to use.
If I do happen to find that request parameter, I clear the response and add a header to indicate this is now a PDF. Finally, I instantiate my custom PdfFilter class, passing in the current response filter (to preserve any existing behavior) and the base URL of the site. This is used to resolve CSS and images, as the HTML to PDF converter acts as a mini-browser, rendering the content using WebKit.
Response filters are interesting in that they’re only Streams. Some HTML to PDF generators include custom Stream classes, but this one doesn’t. Our custom stream will write the underlying response stream to a memory stream, and once it’s done writing, will use our PDF converter to convert that underlying ASP.NET MVC-generated stream of content to PDF. The entire class can be found here:
First, when I create the filter, I capture the existing filter and create a new MemoryStream to temporarily save the MVC output:
As ASP.NET writes to my filter (effectively, the output of our MVC handler), I just capture the output as it writes:
Finally, when ASP.NET closes my filter stream, I invoke the converter, outputting the result of my conversion into the original ASP.NET response filter:
I love that I can still use MVC, HTML, Razor and CSS to generate my PDFs, and I’m able to use all modern CSS3 whether or not the user’s browser actually supports it. With Chrome tools, I can tweak the print version of my site until it looks great, then just modify the URL to verify the PDF version.
If you’re using CSS frameworks, you might need to do a bit of modification to your print CSS, as the responsive CSS doesn’t necessarily translate to the viewport of the PDF document generated. Most tools, including this one, offer tons of customization to rendering. And the best part is, I never have to deal with direct PDF tools, ever, ever again.