omg, wkhtmltopdf & mvc3?

Blog

October 5, 2011

By Michael Johnson in Programming

Building PDFs dynamically using wkhtmltopdf in a MVC3 application

While working on a recent project, I needed to build a series of PDF documents dynamically, then zip them up into a single file. I checked out a couple of options, including iTextSharp and some non-open-source options. I then decided that I liked the looks of wkhtmltopdf.

In my previous life as a Java developer, I had a similar requirement and used iReport to design and fill a Jasper report with data. I knew that I wanted to stay as far away from that solution as possible. I had to have the ability to quickly make data/style/layout changes and have them all testable, without having to recompile or redeploy anything.

The solution I came up with used Razor views just like any other MVC3 page. So, I was able to preview and debug those views without having to actually generate the PDF, download it, and open it. Once I had the views perfected, I built a few helper classes that would render the view in the background, then use the wkhtmltopdf engine to build my PDF.

wkhtmltopdf introduced a couple of interesting problems, not the least of which is that it uses an executable to generate the PDF. I got around that by putting the executable into my application’s bin directory. This gave me the ability to fire it up in a process, sending in the appropriate parameters, then wait for it to finish and grab the file it created.

I put the following method into a helper class and pass into it an instance of my HttpServerUtilityBase so that I can get the executable’s path, and the url to my view that I had previously developed. It saves the PDF to a temp file, then I read the bytes from it, and promptly delete it.


public byte[] ConvertHtmlToPDF(HttpServerUtilityBase server, string inputUrl)
{
    byte[] bytes = null;

    FileInfo tempFile = new FileInfo(Path.GetTempFileName());

    StringBuilder argument = new StringBuilder();
    argument.Append(" --disable-smart-shrinking");
    argument.Append(" --no-pdf-compression");
    argument.Append(" " + inputUrl);
    argument.Append(" " + tempFile.FullName);

    try
    {
        // to call the exe to convert
        using (Process p = new System.Diagnostics.Process())
        {
            p.StartInfo.UseShellExecute = false;
            p.StartInfo.CreateNoWindow = true;
            p.StartInfo.FileName = server.MapPath("/bin/wkhtmltopdf.exe");
            p.StartInfo.Arguments = argument.ToString();
            p.StartInfo.RedirectStandardOutput = true;
            p.StartInfo.RedirectStandardError = true;

            p.Start();
            p.WaitForExit();
        }

        using (FileStream stream = new FileStream(tempFile.FullName, FileMode.Open, FileAccess.Read))
        {
            bytes = new byte[stream.Length];
            stream.Read(bytes, 0, bytes.Length);
        }
    }
    catch (Exception)
    {
        //logging
    }

    tempFile.Delete();
    return bytes;
}

Depending on your application, once you have the byte array, you can just return that to the client in a FileResult using the appropriate mime-type or drop them into a ZIP file like I did. Hopefully this will help you guys out there that need to produce PDFs in your next MVC3 project.

In addition to our blog, we also send out an email newsletter. Subscribe to the newsletter and get notified whenever we have something wise to share, which is totally, like, all the time.

Comments

No comments available.