Thursday, 27 June 2013

ASP.Net MVC View as PDF

So I needed to export a view as a pdf, and although the Chrome trick of printing to PDF is okay the page was too wide and needed tweaking.

After the usual google search I came up with a couple of things I thought might be useful and with a bit of patience came up with the following solution.

  1. Create a view of the item I want to print (along with the controller)
  2. Convert the html to string
  3. Use a tool based on WkHtmlToPdf to create the pdf. (Pechkin)

1.Create a view of the item I want to print

This is pretty straight forward, create a new view probably without a master layout. The only slight change is css has to be part of the view.
If you want to keep items together you can use 
 page-break-inside: avoid;

2.Convert the html to string

This uses the view engine to render the html with the data.
firstly we assign our model to the view
this.ViewData.Model = vm;

Then we need to do the magic

 using (StringWriter stringWriter = new StringWriter())
{
               ViewEngineResult viewResult = ViewEngines.Engines.FindView(this.ControllerContext, "Print", null);

               ViewContext viewContext = new ViewContext(this.ControllerContext, viewResult.View, this.ViewData, this.TempData, stringWriter);

                viewResult.View.Render(viewContext, stringWriter);

                var st = stringWriter.GetStringBuilder().ToString();
}
so we now have a string "st" with the html in it.

3. Export to PDF.
So to export to pdf I used a tool based on WkHtmlToPdf, the reason for this is it uses Webkit to render the page, before exporting to pdf.  It did a much better job of it than plain iTextSharp.

The tool I used is Pechkin which also has a nuget package Pechkin.Synchronized (which is also a winner :-) )

There are 2 config objects you can set. GlobalConfig and ObjectConfig


global config 

This sets the page size and also bookmarks etc.
 
           GlobalConfig gc = new GlobalConfig();
            // set it up using fluent notation because we can 
            gc.SetDocumentTitle("A Title ")
              .SetPaperSize(PaperKind.A4)
              .SetOutlineGeneration(true);


object config

This allows me to add colours to my background, a footer and set the font size
 
            ObjectConfig oc = new ObjectConfig();
            oc.SetPrintBackground(true);
            oc.Footer.SetFontSize(8);
            oc.Footer.SetLeftText(" Generated By:" + HttpContext.User.Identity.Name + " on " + DateTime.Now.ToString("dd-MMM-yyyy HH:mm"));

Generating the pdf

This generates the pdf, converts it to a byte array and post it back to the browser. We pass the global config in when we create the object, and object config when we create the document
 
var pechkin = new Pechkin.Synchronized.SynchronizedPechkin(gc);
return File(pechkin.Convert(oc,st), "application/pdf");
One of the nice things with this approach is you can use a "normal" controller to test the html and call the view like so (assuming your printable view is called "print"
 
 return View("print", vm);
The in javascript I called the controller in the normal way
 
  window.open("/controller/Print?id=" + id);
I hope this is of help.

Update

a couple of gotchas on deploy,
On IIS 7 I had to enable 32 bit application on the Application Pool

and needed a redirect on the common.logging dll in web.config