Rendering ASP.NET content as PDF

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:

image

Look like this:

image

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:

image

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:

public class PdfModule : IHttpModule
{
    public void Init(HttpApplication context)
    {
        context.PreRequestHandlerExecute += context_BeginRequest;
    }

    private void context_BeginRequest(object sender, EventArgs e)
    {
        var httpApplication = (HttpApplication) sender;

        if (httpApplication.Request.Params["Pdf"] == null)
            return;

        httpApplication.Response.Clear();
        httpApplication.Response.AddHeader("Content-Type", "application/pdf");

        var baseUrl = string.Format("{0}://{1}{2}/", httpApplication.Request.Url.Scheme,
            httpApplication.Request.Url.Authority, httpApplication.Request.ApplicationPath.TrimEnd('/'));
        httpApplication.Response.Filter = new PdfFilter(httpApplication.Response.Filter, baseUrl);
    }

    public void Dispose()
    {
    }
}

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:

https://gist.github.com/jbogard/8804255

First, when I create the filter, I capture the existing filter and create a new MemoryStream to temporarily save the MVC output:

public PdfFilter(Stream oldFilter, string baseUrl)
{
    _oldFilter = oldFilter;
    _baseUrl = baseUrl;
    _memStream = new MemoryStream();
}

As ASP.NET writes to my filter (effectively, the output of our MVC handler), I just capture the output as it writes:

public override void Write(byte[] buffer, int offset, int count)
{
    _memStream.Write(buffer, offset, count);
}

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:

public override void Close()
{
    var converter = new PdfConverter
    {
        MediaType = "print",
    };
    converter.PdfDocumentOptions.LiveUrlsEnabled = false;

    _memStream.Position = 0;

    converter.SavePdfFromHtmlStreamToStream(_memStream, Encoding.UTF8, _baseUrl, _oldFilter);

    _oldFilter.Close();
}

The PDF converter has a ton of options, and even is able to allow Javascript to execute in the page. I opt to disable URLs, as it’s a bit strange to click links inside a PDF document for internal business applications, where these documents are often just saved and shared around. With this in place, I can just modify the URL of any page in my site to have a PDF version, matching the styling of the CSS print media:

image

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.

Related Articles:

Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Jimmy Bogard

I'm a technical architect with Headspring in Austin, TX. I focus on DDD, distributed systems, and any other acronym-centric design/architecture/methodology. I created AutoMapper and am a co-author of the ASP.NET MVC in Action books.
This entry was posted in ASP.NET MVC. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • khalidabuhakmeh

    That’s pretty awesome. As much as developers hate to admit it, our business users are still in love with PDF documents. You can spend months on an app, but if it doesn’t spit out a PDF then what was the point :P . Have you tried to use any other PDF tools that are FOSS and why did you choose EVO PDF?

    Anyways, I’m gonna keep a mental note of this post.

    • Joshua Holt
      • jbogard

        Great, thanks for the pointer!

        • imran_ku07

          Just to say. I have experienced with Pechkin and found a huge amount of issues. Then, I directly used wkhtmltopdf.exe with C# Process class which makes my life very easy to generate pdf with complex css. wkhtmltopdf.exe is very fast

          • jbogard

            I try to be very careful with tools that use Process.Start – it wreaks havoc with sites that use anything interesting authentication-wise.

          • imran_ku07

            Agree. But it depends upon the tool and how we use the tool(Mainly giving restricted permission)

    • jbogard

      I’ve tried a few OSS PDF generation tools (iTextSharp for one), but found the best results from tools that you pay for. I’ve also used PrincePDF with a lot of success. But honestly, preference to a specific tool is fleeting, usually there’s something very specific that a client needs and only one of the tools supports.

      • khalidabuhakmeh

        iTextSharp has a very restrictive commercial license last time I checked so that one is off the list. I guess I should bite the bullet and buy a PDF tool (when the time calls for it).

  • http://craigsmitham.com/ craigsmitham

    This is great … we’re trying to do something very similar.

  • Steve Fenton

    We switched to MVC for this recently because the old XSLT reporting tools made it so hard to make minor changes. It is working really well. We are using Prince for the PDF generation as it is the only tool we could find that supported the CSS Paged Media spec, which gives super awesome support for controlling headers, footers, page breaks and so on – all from CSS (http://www.stevefenton.co.uk/Content/Blog/Date/201312/Blog/Printing-Web-Pages-With-The-Paged-Media-Module/)

    I’m sure other PDF generation tools will eventually support this spec eventually – they should as it is probably the biggest thing to happen in the HTML to PDF industry for a while.

    We have also found that we got much more re-use doing things in MVC because everyone is used to partial views – so now we have only one “Disclaimer” in one place that is used on all the documents.

    • jbogard

      Oh yeah, child actions, view models, strongly-typed helpers and everything! We used Prince a lot as well.

  • Nathan Smith

    We are doing something similar with rendering reports as html and converting to pdf. We are using EO.PDF and it has been working well so far.

    One thing to watch out for is if the page you would like to render has assets that require authentication you can pass the ASPXAUTH cookie but if you pass the session id cookie it will block unless your using read only sessions.

    Our reports are not normal pages (reports only) so we base64 all the images and css into the html then render the razor view to text and pass that to the Html2Pdf render function. Normally the render function would request the css and images by making an HTTP request back to the web app using the base url path.

    This shaved off some time from the render process by preventing the external asset requests. Normally this would not matter but we are rendering hundreds a day similar to a boarding pass or concert ticket.

  • http://libertarianvanguard.wordpress.com/ Jan Daniel Andersen

    Hi, can you show a code snippet of how you attach the filter? I can’t seem to find any event called PreRequest. (I’m a little new to web programming).

  • Pingback: Decaying Code | Community Update 2014-02-07 – Creating custom filters with #ASPNET #MVC and viewing a GitHub repo with #ElasticSearch and #Kibana

  • Pingback: My links of the week – February 9, 2014 | R4

  • http://antix.co.uk/ Anthony Johnston

    Its a nice solution, but isn’t doing a print style sheet is enough, then let the client print to PDF if they need to.

  • http://nothingyoumissed.wordpress.com togakangaroo

    Any reason your using an IHttpModule rather than a custom owin middleware? Also rather than a PDF parameter isn’t this what content negotiation is for?

    • jbogard

      It’s difficult to perform content negotiation via someone clicking a “print me” link on a page ;) But certainly possible to do. Web API still isn’t that great for doing both Razor/HTML and other things, so I tend to use MVC for HTML and Web API for everything else. This is one of those strange occasions I need multiple representations, using Razor for both times.
      I do want to try Owin, so that’s my next exercise.

      • http://nothingyoumissed.wordpress.com togakangaroo

        I meant that you would do content negotiation manually in the OWN (or http module) component. Same way as you currently do with the parameter, just check for a header instead (and yeah, it has to be an ajax-intercepted link then). For similar functionality I’ve used the awesome jquery.fileDownload plugin on the client before which basically turns the download into a promise object. That way you can not only download easily but also stick download times in localStorage to create an evidence-based guess to show the user how long the download might take.

        • jbogard

          I think I’ll give it a shot!

  • Tomek

    I have some problems with browsing your blog history by category. When browsing categories like: “C#” or “Domain-driven design” nothing is displayed although counters shows that there should be 50 posts. Could you please fix it ?

  • Pingback: Roller Codester | 10 Things to read (#3)

  • http://www.movingtotheuk.co.uk Andrei

    can’t get this one working. Win 8 Pro, MVC4 app. Created PdfModule class in SiteManager.Helpers location. I have tried everything in web.config

    Can someone please give me a hand.
    Many thanks

    • http://www.movingtotheuk.co.uk Andrei

      figured it out, thanks. It is not free though, the license is quite expensive.

  • fdssf

    gfhfghhhfghfhfghhfgh

  • Renu Bisht

    Converting ASP.NET content as PDF is very difficult task and takes a lot amount of time but converters make this process smooth and easy.

    http://www.html-to-pdf.net/html-to-pdf-aspdotnet.aspx

  • Elvin Arzumanoğlu

    I have some problem with utf-8 characters. For example: ə, ç, ş etc. Don’t show in PDF document. How can I fix this?

    • jbogard

      Hmmm, not sure. Maybe try a different PDF renderer? There’s a few out there now that can render HTML as PDF.

  • Shalini

    You can also use free converter available online. Check detail here
    http://www.html-to-pdf.net/html-to-pdf-aspdotnet.aspx

  • Lasse

    Nice solution, but I can’t get it to work. It turns out the Write method on the filter is never called, but Flush and Close are called.