Bernie Cook's Blog

Azure, C#, .NET, Architecture & Related Tech News

Dynamically Creating Zipped PDFs in .NET

3 Comments

This post illustrates how you can dynamically create a number of PDFs on the fly, zip them all up, and return the ZIP file via a HTTP response.

I’ve created a sample project which you can download that has a working example of the source code referenced in the post – ZippingPDFs.zip (1.5 MB). For simplicity I went for an ASP.NET web application. The only requirements are that you have Visual Studio 2010 installed.

Required Assemblies

Two assemblies are required:

  1. iTextSharp – ported from an open source Java library for .NET and available for download via SourceForge. The current version is v5.2.0.
  2. DotNetZip – a simple to use library for performing virtually any operation you could imagine with ZIP files.  The current version is v1.9.1.8 and it’s available for download from CodePlex.

I’ve been using both for a while and can highly recommend them. iTextSharp has a few minor quirks – lack of detailed documentation, coding “irregularities”, etc. but it’s the best assembly by far for creating PDFs in .NET.

Generating PDFs on the fly

There are a few ways to solve this problem with iTextSharp, and hopefully you’ll find the one I’m suggesting the simplest. I haven’t had any complaints in production so can vouch for it’s stability. In addition, my code is hosted within an Azure Web Role so it also works in the cloud, not that there are any reasons as to why it wouldn’t.

string fileName = "simple.pdf";
Document pdfDocument = new Document(PageSize.A4, 30, 30, 30, 30);
Paragraph paragraph = null;
PdfWriter pdfWriter = null;
Text.Font helveticaEightNormalBlack = new Text.Font(Text.Font.FontFamily.HELVETICA, 8, iTextSharp.text.Font.NORMAL, new BaseColor(System.Drawing.Color.Black));

using (MemoryStream memoryStream = new MemoryStream())
{
    pdfWriter = PdfWriter.GetInstance(pdfDocument, memoryStream);
    pdfWriter.ViewerPreferences = PdfWriter.PageLayoutSinglePage;

    // Open the PDF, add a simple paragraph, then close the PDF.
    pdfDocument.Open();
    paragraph = new Paragraph(string.Format("This file is named: {0}", fileName));
    pdfDocument.Add(paragraph);
    pdfDocument.Close();

    // Generate a browser prompt to open/save the PDF.
    Response.Clear();
    Response.ContentType = "application/pdf";
    Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}", fileName));
    Response.OutputStream.Write(memoryStream.GetBuffer(), 0, memoryStream.GetBuffer().Length);
    Response.End();
}

The main elements are the Document (PDF), MemoryStream and HTTP Response. What I’ve done is open a PDF, add a simple paragraph to the PDF, then close it. After which I write the memory stream to the HTTP Response’s output stream. There are a few extra bits and pieces, such as setting the PDF documents dimensions, creating a font, setting the PDF viewing preferences – which you could certainly skip over but I wanted to add a little bit of extra sauce to show anyone new to the iTextSharp library that there is an array of options available.

So all very simple.  Next create a series of PDFs and zip them up.

Zipping the PDFs

The following code sample has a few extra lines on the previous one, mainly to do with; creating 20 PDFs, creating a ZipFile instance and a utilising a second MemoryStream. The ZipFile instance is how you go about adding your PDF document, as a memory stream, to the zip file that will ultimately be downloaded. The second memory stream is so that we can make a copy of the closed PDF document memory stream and: (1) open it, (2) make it readable, and (3) set it to the position to the start. It is crucial that these 3 items are addressed otherwise you’ll run into all sorts of different problems when zipping, or unzipping the file on the client end.

string fileName = null;
Document pdfDocument = null;
MemoryStream memoryStream = null;
Paragraph paragraph = null;
PdfWriter pdfWriter = null;
Stream memoryStreamForZipFile = null;
Text.Font helveticaEightNormalBlack = new Text.Font(Text.Font.FontFamily.HELVETICA, 8, iTextSharp.text.Font.NORMAL, new BaseColor(System.Drawing.Color.Black));
ZipFile zipFile = new ZipFile();

for (int i = 0; i < 20; i++)
{
    memoryStream = new MemoryStream();

    pdfDocument = new Document(PageSize.A4, 30, 30, 30, 30);
    pdfWriter = PdfWriter.GetInstance(pdfDocument, memoryStream);
    pdfWriter.ViewerPreferences = PdfWriter.PageLayoutSinglePage;

    fileName = string.Format("simple # {0}.pdf", i);

    // Open the PDF, add a simple paragraph, then close the PDF.
    pdfDocument.Open();
    paragraph = new Paragraph(string.Format("This file is named: {0}", fileName));
    pdfDocument.Add(paragraph);
    pdfDocument.Close();

    // NOTE: The stream has to be open, readable and set to the start.
    memoryStreamForZipFile = new MemoryStream(memoryStream.ToArray());
    memoryStreamForZipFile.Seek(0, SeekOrigin.Begin);

    zipFile.AddEntry(fileName, memoryStreamForZipFile);
}

// Generate a browser prompt to open/save the ZIP file.
Response.Clear();
Response.Buffer = false;
Response.ContentType = "application/zip";
Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}", "simple.zip"));

zipFile.Save(Response.OutputStream);

Response.End();

You’ll also notice the way I’ve handled the HTTP Response’s output stream – not the same as in the prior example but this is how it was recommended and how it works.

I must stress how important it is to ensure the PDF document’s memory stream is open, readable and at the start position before adding it to the zip file. I lost some time on this and eventually found the documentation on the DotNetZip library website clearly pointed this out – “RTFM? Yes I know”

Complete Code

Default.aspx

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="Default.aspx.cs" Inherits="ZippingPDFs.Default" %>

<DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title>Zipping PDFs</title>
</head>
<body>
    <form id="form1" runat="server">

        <h1>Zipping PDFs</h1>

        <asp:Button runat="server" ID="btnDownloadPdfFile" Text=" Download PDF File " OnClick="btnDownloadPdfFile_OnClick" />
        <asp:Button runat="server" ID="btnDownloadZipFile" Text=" Download ZIP File " OnClick="btnDownloadZipFile_OnClick" />

    </form>

    <p style="color:Gray;">Copyright © Bernie Cook 2012</p>
</body>
</html>

Default.aspx.cs

using Ionic.Zip;

using iTextSharp.text;
using iTextSharp.text.pdf;

using System;
using System.IO;
using System.Web;
using System.Web.UI;

using Drawing = System.Drawing;
using Text = iTextSharp.text;

namespace ZippingPDFs
{
    public partial class Default : Page
    {
        protected void btnDownloadPdfFile_OnClick(object sender, EventArgs e)
        {
            string fileName = "simple.pdf";
            Document pdfDocument = new Document(PageSize.A4, 30, 30, 30, 30);
            Paragraph paragraph = null;
            PdfWriter pdfWriter = null;
            Text.Font helveticaEightNormalBlack = new Text.Font(Text.Font.FontFamily.HELVETICA, 8, iTextSharp.text.Font.NORMAL, new BaseColor(System.Drawing.Color.Black));

            using (MemoryStream memoryStream = new MemoryStream())
            {
                pdfWriter = PdfWriter.GetInstance(pdfDocument, memoryStream);
                pdfWriter.ViewerPreferences = PdfWriter.PageLayoutSinglePage;

                // Open the PDF, add a simple paragraph, then close the PDF.
                pdfDocument.Open();
                paragraph = new Paragraph(string.Format("This file is named: {0}", fileName));
                pdfDocument.Add(paragraph);
                pdfDocument.Close();

                // Generate a browser prompt to open/save the PDF.
                Response.Clear();
                Response.ContentType = "application/pdf";
                Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}", fileName));
                Response.OutputStream.Write(memoryStream.GetBuffer(), 0, memoryStream.GetBuffer().Length);
                Response.End();
            }
        }

        protected void btnDownloadZipFile_OnClick(object sender, EventArgs e)
        {
            string fileName = null;
            Document pdfDocument = null;
            MemoryStream memoryStream = null;
            Paragraph paragraph = null;
            PdfWriter pdfWriter = null;
            Stream memoryStreamForZipFile = null;
            Text.Font helveticaEightNormalBlack = new Text.Font(Text.Font.FontFamily.HELVETICA, 8, iTextSharp.text.Font.NORMAL, new BaseColor(System.Drawing.Color.Black));
            ZipFile zipFile = new ZipFile();

            for (int i = 0; i < 20; i++)
            {
                memoryStream = new MemoryStream();

                pdfDocument = new Document(PageSize.A4, 30, 30, 30, 30);
                pdfWriter = PdfWriter.GetInstance(pdfDocument, memoryStream);
                pdfWriter.ViewerPreferences = PdfWriter.PageLayoutSinglePage;

                fileName = string.Format("simple # {0}.pdf", i);

                // Open the PDF, add a simple paragraph, then close the PDF.
                pdfDocument.Open();
                paragraph = new Paragraph(string.Format("This file is named: {0}", fileName));
                pdfDocument.Add(paragraph);
                pdfDocument.Close();

                // NOTE: The stream has to be open, readable and set to the start.
                memoryStreamForZipFile = new MemoryStream(memoryStream.ToArray());
                memoryStreamForZipFile.Seek(0, SeekOrigin.Begin);

                zipFile.AddEntry(fileName, memoryStreamForZipFile);
            }

            // Generate a browser prompt to open/save the ZIP file.
            Response.Clear();
            Response.Buffer = false;
            Response.ContentType = "application/zip";
            Response.AddHeader("Content-Disposition", string.Format("attachment;filename={0}", "simple.zip"));

            zipFile.Save(Response.OutputStream);

            Response.End();
        }
    }
}

Final Note

The iTextSharp assembly exposes a lot of methods and properties for creating just about any type of PDF layout one could want – I’d recommend Googling anything you may want as there is no official documentation available at present. I also wanted to make a shout out to Massoud Mazar who’s post: “Code sample for using iTextSharp PDF library” provides a run through of how to implement to the “Page X of Y” function into a PDF – thank you.

Finally I wanted to express a huge thanks to the development community who given up their own time to work on these libraries.  Both iTextSharp and DotNetZip are so incredibly handy and have saved me a lot of work – something which I’m very grateful for.

Advertisements

Author: Bernie

I currently live and work in the UK, just outside of London. I've been working in IT for 15+ years and in that time have solved many technical problems via blogs, forums, tutorials etc. and feel like I should be giving something back. This blog post is my way of contributing and I hope it proves just as useful to others as their contributions have to me.

3 thoughts on “Dynamically Creating Zipped PDFs in .NET

  1. Reblogged this on Mahtab Rasheed and commented:
    A very useful article

  2. Reblogged this on Umut Esen.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s