Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / web / HTML

Rendering PDF Views in ASP MVC using iTextSharp

4.75/5 (16 votes)
22 Mar 2010CPOL3 min read 1  
This post talks about how you can add a new type of views to your ASP.NET MVC application to support PDF files. The technique allows you to define your views inside the Views folder using a markup language in a way similar to the way the regular views are constructed and displayed.

Generating PDF files can be done by calling iTextSharp classes and methods directly, but consistency with the rest of MVC is preferable. In this post, I’m going to explain how you can use iTextSharp to create PDF views the same way in which HTML views are created. What we need is to write our PDF files under the Views directory using a markup language. iTextSharp supports a special XML format that can be used in our views instead of HTML. One thing to note about aspx is that your tags don’t have to be HTML tags. You can type anything you want; of course Visual Studio will only recognize HTML but the ASP engine will spit that data out to the browser anyway. So, we are going to use regular views but we are going to write iTextSharp XML tags in them instead of HTML. Then, we’ll need a way to intercept the result and convert it into PDF before it’s sent to the browser.

We are going to expand the Controller class with functionalities needed for handling PDF views. Our new class will be called MyController and it will inherit from MVC’s Controller. Any controller that needs to support PDF views will need to inherit from MyController instead of Controller. As you know, the Controller class has a method called ‘View’ which performs all that’s needed to be done for an HTML view to be rendered to the browser. Following a similar approach, we are going to create a new method called ‘ViewPdf’ that will send out a PDF file instead of an HTML file. But before we write that method, we’ll need to be able to render our views into a string instead of the browser. To do this, we need to create a fake HttpResponse and give it a memory stream to write to. Then, we’ll need to create a fake HttpContext to use that fake response. Then, we can call ExecuteResult on our ActionResult object giving it the fake context. This will cause the view to be rendered to our memory stream. These steps will be included in a new method called RenderActionResultToString. This method will later on be used by ViewPdf to generate the PDF data.

C#
protected string RenderActionResultToString(ActionResult result)
{
    // Create memory writer.
    var sb = new StringBuilder();
    var memWriter = new StringWriter(sb);

    // Create fake http context to render the view.
    var fakeResponse = new HttpResponse(memWriter);
    var fakeContext = new HttpContext(System.Web.HttpContext.Current.Request,
        fakeResponse);
    var fakeControllerContext = new ControllerContext(
        new HttpContextWrapper(fakeContext),
        this.ControllerContext.RouteData,
        this.ControllerContext.Controller);
    var oldContext = System.Web.HttpContext.Current;
    System.Web.HttpContext.Current = fakeContext;

    // Render the view.
    result.ExecuteResult(fakeControllerContext);

    // Restore old context.
    System.Web.HttpContext.Current = oldContext;

    // Flush memory and return output.
    memWriter.Flush();
    return sb.ToString();
}

Now that we can intercept our rendered views as strings, we can now convert them into PDF files. This will be done inside the ViewPdf method, which returns an ActionResult object, so it can be used in the controller the same way the View method is used. ViewPdf creates a memory stream to which the generated PDF will be written, then it gets the buffer from that memory stream and uses it to initialize the BinaryContentResult object that will be returned to the caller.

C#
protected ActionResult ViewPdf(object model)
{
    // Create the iTextSharp document.
    Document doc = new Document();
    // Set the document to write to memory.
    MemoryStream memStream = new MemoryStream();
    PdfWriter writer = PdfWriter.GetInstance(doc, memStream);
    writer.CloseStream = false;
    doc.Open();

    // Render the view xml to a string, then parse that string into an XML dom.
    string xmltext = this.RenderActionResultToString(this.View(model));
    XmlDocument xmldoc = new XmlDocument();
    xmldoc.InnerXml = xmltext.Trim();

    // Parse the XML into the iTextSharp document.
    ITextHandler textHandler = new ITextHandler(doc);
    textHandler.Parse(xmldoc);

    // Close and get the resulted binary data.
    doc.Close();
    byte[] buf = new byte[memStream.Position];
    memStream.Position = 0;
    memStream.Read(buf, 0, buf.Length);

    // Send the binary data to the browser.
    return new BinaryContentResult(buf, "application/pdf");
}

That’s all that’s needed in our MyController class, but what’s the BinaryContentResult class used inside the ViewPdf method? It’s a class inherited from ActionResult that is used to return arbitrary content, much like the ContentResult class, but returns binary data instead.

C#
public class BinaryContentResult : ActionResult
{
    private string ContentType;
    private byte[] ContentBytes;

    public BinaryContentResult(byte[] contentBytes, string contentType)
    {
        this.ContentBytes = contentBytes;
        this.ContentType = contentType;
    }

    public override void ExecuteResult(ControllerContext context)
    {
        var response = context.HttpContext.Response;
        response.Clear();
        response.Cache.SetCacheability(HttpCacheability.NoCache);
        response.ContentType = this.ContentType;

        var stream = new MemoryStream(this.ContentBytes);
        stream.WriteTo(response.OutputStream);
        stream.Dispose();
    }
}

We are now ready to start writing PDF views. The following example shows a simple PDF view that renders the list of orders for a specific customer.

ASP.NET
<%@ Page Language="C#" 
Inherits="System.Web.Mvc.ViewPage<Sample1.Models.Customer>" %>
<%@ Import Namespace="Sample1.Models" %>

<?xml version="1.0″ encoding="UTF-8″ ?>
<itext creationdate="2/4/2003 5:49:07 PM" producer="iTextSharpXML">
 <paragraph leading="18.0″ 
 font="unknown" size="16.0″ align="Default">
     <chunk>Orders in PDF</chunk>
 </paragraph>
 <paragraph leading="18.0″ 
 font="unknown" size="10.0″ align="Default">
  <chunk>Customer Name: <%= this.Model.Name %></chunk><newline />
  <chunk>Address: <%= this.Model.Address %></chunk><newline />
 </paragraph>
 <paragraph leading="18.0″ 
 font="unknown" size="10.0″ align="Default">
 <chunk font="unknown" 
 size="12.0″>Orders:</chunk><newline />
 <% foreach (Order o in this.Model.Order)
       { %>
        <chunk font="unknown" 
        size="10.0″><%= o.OrderId %>, <%= o.Description %>, 
        <%= o.Amount %>, <%= o.Date %></chunk><newline />
 <% } %>
 </paragraph>
</itext>

We’ll name the view OrdersInPdf and our controller will then look like this:

C#
public class HomeController : MyController
{
    private Sample1Entities entities = new Sample1Entities();

    public ActionResult OrdersInHtml()
    {
        Customer c = (
            from cust in entities.Customer
            where cust.CustomerId == 1
            select cust).First();
        c.Order.Load();
        return View(c);
    }

    public ActionResult OrdersInPdf()
    {
        Customer c = (
            from cust in entities.Customer
            where cust.CustomerId == 1
            select cust).First();
        c.Order.Load();
        // To render a PDF instead of an HTML, all we need to do is call ViewPdf
        // instead of View. This requires the controller to be inherited from
        // MyController instead of MVC’s Controller.
        return ViewPdf(c);
    }
}

As the code implies, OrdersInHtml will render an HTML view which is the default behaviour of MVC, while OrdersInPdf will render a PDF instead. Notice that the only difference between the two is the return statement, OrdersInPdf calls ViewPdf instead of View. The programmer needs to make sure when he calls ViewPdf that the view contains iTextSharp XML data rather than HTML data. The other thing to notice in the controller is the first line which shows that the controller inherits from MyController instead of Controller.

iTextSharp also has the ability to convert HTML into PDF so we can modify the ViewPdf (or create a new method) to make it parse HTML instead of XML. However, it’s usually hard to get the expected formatting when you convert from HTML.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)