Creating Dynamic PDFs in ASP.NET MVC using iTextSharp

Everyone loves to take their content with them and read it offline. Today, I show you an easy way to create dynamic PDFs for your audience using iTextSharp and the Razor Engine.

Written by Jonathan "JD" Danylko • Last Updated: • Develop •

Rolled up Paper on a Page with a lightbulb drawing

PDFs are a great way to give your users a deliverable after they finish reading your site.

Of course, there are other services that allow your readers to take content and store it somewhere for later reading like Evernote's Web Clipper and Pocket.

But today, we go over an easy way to create PDFs based on posts that you can immediately deliver to your audience.

I will be implementing this on the site in the future, so this code below will be 99% production ready.

First Ingredients

First, our project needs iTextSharp and the Razor Engine.

For those new to these libraries, iTextSharp is a .NET library that allows you to create PDFs using C# or VB.NET code.

The Razor Engine is the templating engine used to render your Views in your ASP.NET MVC application. The library was abstracted and can be used as a standalone package.

You can even use the AT (@) syntax for your Views (which we'll do later).

But we're getting ahead of ourselves.

First, open your MVC project, then your Package Manager Console (View->Other Windows->Package Manager Console) and type:

Install-Package RazorEngine
Install-Package iTextSharp
Install-Package iTextSharp.xmlworker
Install-Package iTextSharp.xtra

UPDATE: It should be noted that version of iTextSharp I am using is strictly for the open-source community where I'm sharing the application/code with everyone. If you are looking into a more closed-source solution, please check out the website for iTextSharp pricing based on your needs.

Once these are installed, you are ready to write code.

Use the Clicky-Clicky!

In our View, we need a way to tell the server we want the PDF. For our demonstration, we'll simply make a link to issue the retrieval of it.

On our blog post page, we will have a section for requesting the PDF.

<div class="alert alert-success">
    <a href="/Blog/GetPdf?code=@Model.Post.Hash">Get PDF version</a>
</div>

The Hash is the ID of the blog post we'll pass into the controller.

Need some Action!

The link above points to GetPdf in the BlogController so we better make one.

This Action Method is not optimal, but again, it's just for demonstration purposes.

We need to load the post based on the code so we can pass it on to the PdfBuilder.

GetPdf Action Method

public ActionResult GetPdf(string code)
{
    var unitOfWork = this.ControllerContext.GetUnitOfWork<BlogUnitOfWork>();
    var repository = unitOfWork.GetRepository<PostRepository>();
    var post = repository.GetByHashId(code);
    var builder = new PdfBuilder(post, Server.MapPath("/Views/Amp/Pdf.cshtml"));
    return builder.GetPdf();
}

Notice I passed in the post object and the filename of the Html I want rendered in the PDF.

The reason I have the Pdf.cshtml in the Amp folder is because since I already made a watered-down version of my blog posts using Google AMP's initiative (discussed here), I thought this would be a great place to put an even more watered down version of a post.

The Pdf.cshtml is definitely smaller. I wanted a vanilla version of a blog post and then we can spruce it up later if we want. I didn't want to tax the iTextSharp renderer. ;-)

Here's what the Pdf.cshtml looks like.

Views/Amp/Pdf.cshtml

@model Post
<html>
<head>
    <title>@Model.Title</title>
</head>
<body>
    <h2>@Model.Title</h2>
    @Model.PublishedOn.ToString()     <h3>@Model.Abstract</h3>     @(new RawString(Model.Description)) </body> </html>

As you can see, very plain. I didn't even add any CSS to it. Very basic.

NOTE: See the @Raw @(new RawString()) method? This is the equivalent to @Html.Raw(data). Even though the Razor syntax in Visual Studio complains about it, the iTextSharp requires it.

Let's Build a Builder

With all of that out of the way, let's create our PdfBuilder class.

PdfBuilder.cs

public class PdfBuilder
{
    private readonly Post _post;

    private readonly string _file;
    public PdfBuilder(Post post, string file)     {         _post = post;         _file = file;     }

    public FileContentResult GetPdf()     {         var html = GetHtml();         Byte[] bytes;         using (var ms = new MemoryStream())         {             using (var doc = new Document())             {                 using (var writer = PdfWriter.GetInstance(doc, ms))                 {                     doc.Open();                     try                     {                         using (var msHtml = new MemoryStream(Encoding.UTF8.GetBytes(html)))                         {                             iTextSharp.tool.xml.XMLWorkerHelper.GetInstance()                                 .ParseXHtml(writer, doc, msHtml, Encoding.UTF8);                         }                     }                     finally                     {                         doc.Close();                     }                 }             }             bytes = ms.ToArray();         }         return new FileContentResult(bytes, "application/pdf");     }
    private string GetHtml()     {         var html = File.ReadAllText(_file);         return Razor.Parse(html, _post);     } }

As mentioned above, all we are passing into this class is the post object and the filename we want to render into the PDF.

When we call the GetPdf() method, we need the HTML first so we do that by calling the GetHtml() method.

We read the file and then we let the RazorEngine perform it's templating just like you would pass an object into your View from a controller.

The Razor Engine replaces all of the @ syntax with the object we passed into it and our final version of the HTML is returned with the replaced data.

Once we get our populated HTML, we can now turn that HTML into a PDF using iTextSharp's XmlWorkerHelper to perform all of the work.

Once we have all the bytes, we can return that as a FileContentResult ActionResult and specify the content type (application/pdf).

Everything is done in memory. However, instead of recreating it every time, you could save it to a directory and check if it exists. If it does, send that back to the user instead of creating it every single time.

Now, as soon as the user clicks on the link, it will return a PDF to their browser where they can save it or print it.

Conclusion

Today, I introduced a way to create dynamic PDFs for any type of content using iTextSharp and the Razor Engine.

The Razor Engine has many uses and I'm glad they abstracted it out from ASP.NET MVC.

I do have one last note about rendering the HTML into a PDF: while it's not a full-blown rendering engine, it can perform the basics to make it functional. I didn't include JavaScript. It does use inline CSS, not external style sheets. Keep it simple when generating your PDFs.

Remember, it's a document, not a web page.

With that said, these two NuGet packages pack quite a punch together. You can imagine the amount of PDF content you can generate on-the-fly. The possibilities are endless.

What are your thoughts on building some PDF's on the fly? Are you making a catalog with your database of products? Post your comments below!

ASP.NET 8 Best Practices on Amazon

ASP.NET 8 Best Practices by Jonathan Danylko


Reviewed as a "comprehensive guide" and a "roadmap to excellence" with over 120 Best Practices for ASP.NET Core 8, Jonathan's first book by Packt Publishing explores proven techniques for every phase of the SDLC.

Learn industry-standard concepts to improve your coding, debugging, and deployment of ASP.NET Core websites.

Order now on Amazon.com button

Picture of Jonathan "JD" Danylko

Jonathan "JD" Danylko is an author, web architect, and entrepreneur who's been programming for over 30 years. He's developed websites for small, medium, and Fortune 500 companies since 1996.

He currently works at Insight Enterprises as an Architect.

When asked what he likes to do in his spare time, he replies, "I like to write and I like to code. I also like to write about code."

comments powered by Disqus