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.
protected string RenderActionResultToString(ActionResult result)
{
var sb = new StringBuilder();
var memWriter = new StringWriter(sb);
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;
result.ExecuteResult(fakeControllerContext);
System.Web.HttpContext.Current = oldContext;
memWriter.Flush();
return sb.ToString();
}
Now that we can intercept our rendered views as string
s, 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.
protected ActionResult ViewPdf(object model)
{
Document doc = new Document();
MemoryStream memStream = new MemoryStream();
PdfWriter writer = PdfWriter.GetInstance(doc, memStream);
writer.CloseStream = false;
doc.Open();
string xmltext = this.RenderActionResultToString(this.View(model));
XmlDocument xmldoc = new XmlDocument();
xmldoc.InnerXml = xmltext.Trim();
ITextHandler textHandler = new ITextHandler(doc);
textHandler.Parse(xmldoc);
doc.Close();
byte[] buf = new byte[memStream.Position];
memStream.Position = 0;
memStream.Read(buf, 0, buf.Length);
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.
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.
<%@ Page Language="C#"
Inherits="System.Web.Mvc.ViewPage<Sample1.Models.Customer>" %>
<%@ Import Namespace="Sample1.Models" %>
="1.0″ encoding="
<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:
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();
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.