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: 24611

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

Gjuro 3/5/2015 8:17 PM
In response to MVC 5 with EF 6 in Visual Basic - Implementing Basic CRUD Functionality
in: "Create a Details Page" ++++++++++++++++++ The scaffolded code for the Students Index page...

Steve 3/5/2015 6:09 PM
In response to Usage of the @ (at) sign in ASP.NET
I was surprised I needed to use the @ before the html <fieldset> declaration in the following am...

stephen 3/4/2015 10:36 PM
In response to Conversion of a datetime2 data type to a datetime data type resulted in an out-of-range value
Thank you, Love all your tutorials :)...

Satyabrata Mohapatra 3/4/2015 5:16 AM
In response to ASP.NET MVC DropDownLists - Multiple Selection and Enum Support
Great article....

faysal 3/3/2015 11:46 AM
In response to Inline Editing With The WebGrid
Nice one can you please tell us how we can do ad and delete functionality in this. for e.g if i on...

Fairoze Mohamed Musthafa 3/2/2015 8:33 AM
In response to Date formatting in VBScript
Appreciated !!!!...

mahdi 3/1/2015 10:16 AM
In response to Getting the identity of the most recently added record
Great Article....

Sohrab 2/28/2015 12:53 PM
In response to Displaying One-To-Many Relationships with Nested Repeaters
hi.this was very usefull for me.after spending 6 hours I could find best answer for my alot....

Abolfazl RoshanZamir 2/28/2015 10:36 AM
In response to Date Formatting in C#
very informative... thanks for sharing sir......

Oscar Duran 2/27/2015 2:00 PM
In response to How To Check If A Query Returns Data In ASP.NET Web Pages
Thank you very much Mike, this post has been very useful to me....


© 2006 - 2015 Mike Brind