How to render an ASP.NET MVC View in PDF format
I'm working with ExpertPDF's Html-to-PDF conversion utility for this question (although I'm open to other libraries if there's sufficient documentation).
In short, I have a view that is formatted a specific way and I would like to render it as a PDF document the user can save to disk.
What I have so far is a PrintService (which implements an IPrintService interface) and this implementation has two overloads for PrintToPDF(), one that takes just a URL and another that takes an HTML string, and both of which return a byte[]. I've only worked out the details of the second overload which requires the HTML string.
What I would like to do from my controller is something like:
public FileStreamResult Print(int id)
{
var model = _CustomRepository.Get(id);
string renderedView = SomethingThatRendersMyViewAsAString(model);
Stream byteStream = _PrintService.PrintToPdf(renderedView);
HttpContext.Response.AddHeader("content-disposition",
"attachment; filename=report.pdf");
return new FileStreamResult(byteStream, "application/pdf");
}
which in theory would render a PDF to the page. It's the "SomethingThatRendersMyViewAsAString" that I'm looking for help with. Is there a quick way to get the string representation of a View? Or perhaps I should just stick with the URL overload and pass in a URL to the view... Any other thoughts?
Thanks!
Solution 1:
I packaged my solution in a Nuget package: Rotativa http://nuget.org/packages/Rotativa. It's based on wkhtmltopdf.
Usage is really simple.
Having an action you would like to serve as Pdf, instead of Html page. You can define an action that returns an ActionResult of the type ActionAsPdf (RouteAsPdf is also available). So the code is just:
public ActionResult PrintIndex()
{
return new ActionAsPdf("Index", new { name = "Giorgio" }) { FileName = "Test.pdf" };
}
With name = "Giorgio" being a route parameter.
It works even if the action to print is protected by web forms authentication ([Authorize] attribute)
Solution 2:
You might be able to tap into the Response during OnResultExecuting and replace the Filter property with something that stores the resultant HTML in a MemoryStream. Then you could clear the Response during OnResultExecuted and replace it with the results of your PDF conversion. I'm not sure that this would be better than just getting the HTML from the URL, though.
public FileStreamResult Print(int id)
{
var model = _CustomRepository.Get(id);
this.ConvertToPDF = true;
return View( "HtmlView" );
}
public override OnResultExecuting( ResultExecutingContext context )
{
if (this.ConvertToPDF)
{
this.PDFStream = new MemoryStream();
context.HttpContext.Response.Filter = new PDFStreamFilter( this.PDFStream );
}
}
public override OnResultExecuted( ResultExecutedContext context )
{
if (this.ConvertToPDF)
{
context.HttpContext.Response.Clear();
this.PDFStream.Seek( 0, SeekOrigin.Begin );
Stream byteStream = _PrintService.PrintToPDF( this.PDFStream );
StreamReader reader = new StreamReader( byteStream );
context.HttpContext.Response.AddHeader( "content-disposition",
"attachment; filename=report.pdf" );
context.HttpContext.Response.AddHeader( "content-type",
"application/pdf" );
context.HttpContext.Response.Write( reader.ReadToEnd() );
}
}
The PDFStreamFilter would need to override the "Write" method(s) and send the data to the memory stream instead.