Exporting The Razor WebGrid To PDF Using iTextSharp

This article looks at how you can provide your users with the ability to export the contents of a Razor Web Pages WebGrid to a PDF file using the popular free PDF library, iTextSharp.

First, you need to get hold of the iTextSharp library. You can do this within WebMatrix via the built-in Nuget feature. Click the Nuget button on the main menu and type "iTextSharp" into the searhc box. There are a couple of versions of iTextSharp available - the most recent one is version 5, which is released under the GNU Affero General Public Licence. This is not as permissive as the licence (LGPL/MPL) under which the previous version was released. Not only that, but the differences in functionality between 4 and 5 are so small that I chose to use version 4.1.6, which is also the one I based my popular series of iTextSharp articles on.

Once you have decided which version you want to use, click the Install button and agree to the licence condiitons. The dll file will be installed in your bin folder.

Now you need a grid:

@{
    Page.Title = "Export To PDF";
    var db = Database.Open("Northwind");
    var query = "SELECT CustomerID, CompanyName, ContactName, Address, City, Country, Phone FROM Customers";
    var data = db.Query(query);
    var grid = new WebGrid(data, ajaxUpdateContainerId: "grid");
}
<h1>Export to PDF</h1>
<div id="gridContainer">
    <div id="grid">
        @grid.GetHtml(    
            tableStyle : "table",
            alternatingRowStyle : "alternate",
            headerStyle : "header",
            columns: grid.Columns(
                grid.Column("CustomerID", "ID"),
                grid.Column("CompanyName", "Company Name"),
                grid.Column("ContactName", "Contact Name"),
                grid.Column("Address"),
                grid.Column("City"),
                grid.Column("Country"),
                grid.Column("Phone")
            )
        )
        <img src="/images/pdf-icon.png" id="pdf" alt="Export to PDF" title="Export to PDF" />
    </div>
</div>

When the user clicks on the image, it should result in the data being downloaded as a PDF file. At the moment, it is just an image and it appears below the grid. Here's a little bit of jQuery to move the image to the footer area of the grid and to change the cursor when the user hovers over it:

<script type="text/javascript">
    $(function () {
        $('#pdf').appendTo($('tfoot tr td')).on('hover', function () {
            $(this).css('cursor', 'pointer');
        });
        $('#pdf').on('click', function () {
            $('<iframe src="/GeneratePdf"></iframe>').appendTo('body').hide();
        });
    });
</script>

The jQuery code also adds a handler to the click event of the image. It creates an iframe which it then adds to the body element, and then it makes the display property equal 'none' using the jQuery hide command. The src for the iframe is a file called GeneratePdf.cshtml, which is responsible for creating the PDF file. The hidden iframe technique is a clean way to manage downloads via AJAX without leaving the current page. Adding an iframe dynamically like this effectively "sucks" the HTTP response from the src URL through to the current page.

Here's the code for GeneratePdf.cshtml:

@using iTextSharp.text;
@using iTextSharp.text.pdf;
@{
    Layout = null;
    Response.AddHeader("Content-disposition", "attachment; filename=report.pdf");
    Response.ContentType = "application/octet-stream";
    var db = Database.Open("Northwind");
    var sql = "SELECT CustomerID, CompanyName, ContactName, Address, City, Country, Phone FROM Customers";
    var data = db.Query(sql);
    var columns = data.First().Columns;

    var doc = new Document();
    PdfWriter.GetInstance(doc, Response.OutputStream);
    doc.SetPageSize(PageSize.A4.Rotate());
    var arial = FontFactory.GetFont("Arial", 8, Color.BLACK);
    var arialBold = FontFactory.GetFont("Arial", 10, Font.BOLD, Color.BLACK);
    doc.Open();
    var table = new PdfPTable(columns.Count) {
        TotalWidth = 700f, 
        LockedWidth = true
    };

    foreach(var column in columns){
        table.AddCell(new Phrase(column, arialBold));
    }
    foreach(var row in data){
        foreach(var column in columns){
            table.AddCell(new Phrase(row[column] != null ? row[column].ToString() : string.Empty, arial));
        }
    }
    doc.Add(table);
    doc.Close();
}

As with any file that is intended to deliver a non-html response, the Layout is set to null to prevent stray HTML being included in the output. The Content-Disposition value is set to attachment, and the Content-type is set to application/octet-stream. This combination results in the browser offering a choice to the user - save or open, rather than attempting to display the response. The same query is executed against the database and the column names are extracted from the first record in the query result. Then a PDF file is created, and the PdfWrite.GetInstance method is used to ensure that the resulting document is written to the Response. The document is rotated to Landscape, and a couple of fonts are created for styling - one 8pt Arial black, and the other is 10pt and bold. A table is created and the column names are added using the bold font, followed by the data.

This article shows how easy it is to use the Database helper to generate data to be displayed in a report, and how to use iTextSharp to generate the PDF file. If you want to know more about using iTextSharp, you can read these articles.

The source code for the sample site that accompanies this article is available as a GitHub repo.

 

Date Posted:
Last Updated:
Posted by:
Total Views to date: 25586

9 Comments

- bob

A good report should have:
- page numbers
- header and footer
- header row (1st row) of the table should be repeated on all of the produced pages.
...

- Mike

@bob

Yep - you can do all that using iTextSharp. I might even show how to do that in a separate article some time....

- Mridul Raj

Hi mike,

Is there a way to add tool tip text to the textbox in a pdf file created using itextsharp?

- Mike

@Mridul

I don't know.

- Massimo

Hi, Thank You for your articles.
I got an error for:
var columns = data.First().Columns;

'System.Collections.ObjectModel.ReadOnlyCollection' does not contain a definition for 'First'
Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: Microsoft.CSharp.RuntimeBinder.RuntimeBinderException: 'System.Collections.ObjectModel.ReadOnlyCollection' does not contain a definition for 'First'

Source Error:


Line 10: "@CustomerID = " + CustomerID.ToString();
Line 11: var data = db.Query(sql);
Line 12: var columns = data.First().Columns;
Line 13:
Line 14: var doc = new Document();

- prins patrick

Great tutorial! Helped me a lot! Thanks!

- satinder singht

Hi mike,
Nice article,
Am also waiting for Header/Footer separate article....

- Satyabrata Mohapatra

As always,extremely helpful.Used this technique successfully for an web application,in which around 80,000 students downloaded their admit card in PDF for a Joint Entrance Examination.

It is a habit now, for me to visit your website regularly with a cup of coffee every morning.

Thanks a lot sir.

- Sithelo

Great tutorial. I was wondering how I can use a value from my query say CompanyName and add it to a cell something like AddCell(new Phrase(data.CompanyName, arial). How do you insert your query value in cell when the query result is a single record?
Add your comment

If you have any comments to make about this article, please use this form to do so. Make sure that your comment relates specifically to the article above. More general comments can be posted through the form on the Contact page.

Please note, all comments are moderated, and I end up deleting quite a lot. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Requests to fix your code (post a question to forums.asp.net instead, please)
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam

I do not pass email addresses on to spammers, so a valid one will assist me in responding to you personally if required.

Recent Comments

kaleem 4/16/2015 9:14 AM
In response to ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS
hi this is very helpful ASP.NET MVC, Entity Framework, One-to-Many and Many-to-Many INSERTS if to...

cranston mason 4/16/2015 8:49 AM
In response to Managing Checkboxes And Radios In ASP.NET Razor Web Pages
How do you include a checkbox at the View module and send its values to controller to be processed?...

vamsi 4/15/2015 5:40 AM
In response to Sessions in ASP.NET 5
good article...

James Chaney 4/14/2015 8:44 PM
In response to 7 C# 6.0 Features That Every ASP.NET Developer Should Know About
Question on #5 - I don't see where this gets you out of a NullReferenceException if returns null is...

Grey 4/14/2015 6:09 PM
In response to Getting the identity of the most recently added record
Nice work....

Anh Huynh 4/14/2015 3:36 PM
In response to Create PDFs in ASP.NET - getting started with iTextSharp
I was successfully create and display PDF file from my localhost but when I move the aspx to the I...

Hisham Abdullah Bin Ateya 4/14/2015 7:00 AM
In response to Sessions in ASP.NET 5
Thanks Mike for sharing this article. George it will be nice if we let the web developers implement...

CTR 4/14/2015 6:39 AM
In response to Integrating Web API with ASP.NET Razor Web Pages
Thanks Mike, new to web pages. learning a lot from your posts, Making Ajax calls to Web API extend...

Bayu Angkasa 4/13/2015 11:27 AM
In response to The Difference Between @Helpers and @Functions In WebMatrix
Four years after you wrote it, I still find out how lucky I am to read your posts ... Thank you Mike...

abedon 4/10/2015 9:17 PM
In response to ASP.NET 5 Middleware, Or Where Has My HttpModule Gone?
I feel the title of this article is not that felicitous. ASP.NET 5 is built of top of the concept...


© 2006 - 2015 Mike Brind