Microsoft Chart Controls to PDF with iTextSharp and ASP.NET MVC

If you are developing a business application using ASP.NET MVC, and need to produce some kind of dashboard-style reporting, the Chart Controls from Microsoft are a good place to start. Having a summary in some form of document, say a PDF is also a common requirement. This article introduces you to satisfying both needs.

A Senior executive came rushing out of his office and shouted at his underlings: "Has anyone seen my pencil?". "It's behind your ear" replied one of the team. "Come on!", the executive demanded " I'm a busy man! Which ear?". We've all met them. These are the people for whom web page logins are a pain. They much prefer to have a document nicely formatted and printed, and put on their desk. By combining the Chart Controls with the free PDF utility, iTextSharp, you can deliver the best of both worlds.

In previous articles, I have already covered both the Chart Controls and iTextSharp. Download instructions for both utilities are detailed in the previous articles, so I won't repeat them here.

UPDATE: The chart controls are included as a native part of ASP.NET from version 4.0 onwards, which means that you do not need to download them separately if you are using VS2010.

I will be generating a chart using LINQ to SQL to connect to the Northwind database, which is available here. In ASP.NET Web Forms, the chart controls are just that - server controls that can be dragged and dropped onto the Form Designer, and configured there. Within MVC there is no place for server controls, so we have to programme against their API instead.

The Chart controls can be rendered in a number of ways within Web Forms but ultimately generate an image that can be displayed using an <img> tag from disk, or streamed to the browser using an HttpHandler. In respect of MVC, an img tag will suffice that points to a controller action which generates the image:


<div><img src="Chart/GetChart" /></div>

And the action itself:


public FileContentResult GetChart()
{
  return File(Chart(), "image/png");
}

The action returns a FileContentResult, which is the actual image as a byte array. So the byte array needs to be generated via the Chart() method as follows:


private Byte[] Chart()
{
  var db = new NorthwindDataContext();
  var query = from o in db.Orders
              group o by o.Employee
                into g
                select new 
                { 
                  Employee = g.Key, 
                  NoOfOrders = g.Count() 
                };

  var chart = new Chart
  {
    Width = 300,
    Height = 450,
    RenderType = RenderType.ImageTag,
    AntiAliasing = AntiAliasingStyles.All,
    TextAntiAliasingQuality = TextAntiAliasingQuality.High
  };

  chart.Titles.Add("Sales By Employee");
  chart.Titles[0].Font = new Font("Arial", 16f);

  chart.ChartAreas.Add("");
  chart.ChartAreas[0].AxisX.Title = "Employee";
  chart.ChartAreas[0].AxisY.Title = "Sales";
  chart.ChartAreas[0].AxisX.TitleFont = new Font("Arial", 12f);
  chart.ChartAreas[0].AxisY.TitleFont = new Font("Arial", 12f);
  chart.ChartAreas[0].AxisX.LabelStyle.Font = new Font("Arial", 10f);
  chart.ChartAreas[0].AxisX.LabelStyle.Angle = -90;
  chart.ChartAreas[0].BackColor = Color.White;

  chart.Series.Add("");
  chart.Series[0].ChartType = SeriesChartType.Column;

  foreach (var q in query)
  {
    var Name = q.Employee.FirstName + ' ' + q.Employee.LastName;
    chart.Series[0].Points.AddXY(Name, Convert.ToDouble(q.NoOfOrders));
  }
  using (var chartimage = new MemoryStream())
  {
    chart.SaveImage(chartimage, ChartImageFormat.Png);
    return chartimage.GetBuffer();
  }
}

I've put this in the Controller, hence the fact that the method is private. The LINQ query returns an anonymous type which contains Employee objects together with the total number of orders they have each generated. A Chart object is instantiated and some properties are set for rendering, including some fonts and labels. The resulting data from the LINQ query is bound to the chart using the AddXY() method. The chart is then saved to a MemoryStream object and then returned as an array of bytes. From there, it is displayed on the page:

 

The link displayed in the image above to "Get PDF" is generated by the following html:


<div><a href="Chart/GetPdf">Get PDF</a></div>

Using the same principal as with the Chart, the hyperlink points to a controller action: GetPdf():


public FilePathResult GetPdf()
{
  var doc = new Document();
  var pdf = Server.MapPath("PDF/Chart.pdf");
  
  PdfWriter.GetInstance(doc, new FileStream(pdf, FileMode.Create));
  doc.Open();

  doc.Add(new Paragraph("Dashboard"));
  var image = Image.GetInstance(Chart());
  image.ScalePercent(75f);
  doc.Add(image);
  doc.Close();

  return File(pdf, "application/pdf", "Chart.pdf");
}

This action is very simple if you already have some familiarity with iTextSharp. If not, refer to the first in my iTextSharp series of articles, together with the article that covers working with images. The action creates a new iTextSharp Document object. A paragraph is added that simply says "Dashboard", and then the same byte array generated by the Chart() method is passed to an iTextsharp.itext.Image object. This is then reduced to 75 percent of its original size and added to the document. The Document.Close() method saves the resulting file to the location specified in the initial PdfWriter.GetInstance() call, and then it is returned through a FilePathResult class. Clicking the link generates an Open or Save dialogue box, and the complete PDF file:

A quick word about the usings that appear at the top of the controller code:


using System;
using System.IO;
using System.Linq;
using System.Web.Mvc;
using System.Web.UI.DataVisualization.Charting;
using iTextSharp.text;
using iTextSharp.text.pdf;
using PDFCharting.Models;
using Color = System.Drawing.Color;
using Font = System.Drawing.Font;

System.Web.UI.DataVisualization.Charting is needed so that you can work with Chart objects. PDFCharting.Models references the Models area of the application which contains the LINQ to SQL classes, and the final two references are there to avoid namespace clashes. There are a number of objects within the iTextSharp component which are named the same as commonly found .Net classes, such as Image and Font. Typically, to avoid the compiler complaining of ambiguity, you might use the fully referenced class name in code. For example, System.Drawing.Font. However, as an alternative, I have provided a namespace alias so that I can reference .NET classes without having to add the fully qualified name.

Summary

We have seen that Charts are generated as images, and used two different derivatives of ActionResult to deliver them: FileContentResult to stream the binary content directly to the browser, and FilePathResult to return a file saved to disk. In addition, we learned the basics of binding a LINQ query result to the data points on a Chart. We have also seen how to add a byte array as an image to an iTextSharp PDF document, and finally learnt a bit about namespace aliases.

This is a very simple example that is intended just to illustrate a starting point. The Chart() method should not normally appear within the controller itself, even as a private method. Not unless your application is very simple. From the point of view of maintainability and extensibility, you might find K Scott Allen's ChartBuilder class a good place to start in terms of separating the grunt work into its own area. If you are feeling really adventurous, there is no reason why you couldn't use the concepts presented in the ChartBuilder class to create a similar utility for building PDF files.

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

9 Comments

- Chris

Is it possible to send the document back to the browser with physically saving on the server? I want to gentere a PDF in memory and return to the browser without leaving any files on the server.

- deepak

Hi,

I am not much familiar with the MVC architecture.
Could you please guide me on this fucntion

File(Chart(), "image/png");
File(pdf, "application/pdf", "Chart.pdf");

Where is the implemention of this method ,or is it the part of MVC..

Could you post the sample application.

Please help.

- Mike

@deepak

It's part of MVC. It returns a FilePathResult object.

- James

I am trying to achieve something similar here. I am generating the chart dynamically and looking to output it directly to PDF. I am currently using the Bullzip PDF printer to do this at the moment. The PDF quality is excellent, but it has certain restrictions that I can't seem to get past. Is there a way of converting the Chart to PDF via ITextSharp and maintain a very high level of quality? Or is converting it to an image the only way?

- Sam

This is an awesome post ! I love your blog ! Thank you for sharing your knowledge !

- Kraus Maus

This is a non scaleable solution. Real charts like xxxxxxxxxx can export scaleable (e.g. vector not raster) PDF.

- Mike

@Kraus,

I don't understand your point. Nowhere in the article is it claimed that the solution is scalable. Or were you simply trying to promote the component I edited out of your comment?

- Ima

Can't this be done in asp.net C#... I have absolutely no knowledge of mvc or linq

- Mike

@Ima.

The code is C#. You can put the body of the Chart method into a generic handler (ashx file) if you are working with Web Forms.

Recent Comments

Gayan 7/3/2015 6:20 AM
In response to 7 C# 6.0 Features That Every ASP.NET Developer Should Know About
Great Article thanks...

Semil 7/1/2015 7:03 AM
In response to iTextSharp - Drawing shapes and Graphics
I have created a rectangle using above methode. Now I want to add a text in the center of this How I...

Satyabrata Mohapatra 6/30/2015 6:12 PM
In response to Reading Excel Files Without Saving To Disk In ASP.NET
Ahh.....this is awesome. Happy to see after a long time you wrote a article on web form :D...

Marty 6/30/2015 7:16 AM
In response to Posting Data With jQuery AJAX In ASP.NET Razor Web Pages
Mike, you're the Man! Another great article. So incredibly helpful. I'm definitely going to buy your...

Rohan 6/30/2015 5:32 AM
In response to ASP.NET MVC 5 with EF 6 - Working With Files
Very good and helpful tutorial. Thanks. Just wanted to know what would be the max file size limit we...

Fernando 6/30/2015 1:59 AM
In response to Programmatically accessing data from DataSource controls
What if I want to pass parameters natively using the DataSourceSelectArguments object, instead of be...

pankaj 6/29/2015 3:13 PM
In response to How to retain carriage returns or line breaks in an ASP.NET web page
very nice i'm use this in my code thank you.... ...

Mike 6/29/2015 2:22 AM
In response to MVC 5 with EF 6 in Visual Basic - Sorting, Filtering and Paging
This is the first example that I have found that works....

Marty 6/28/2015 4:57 AM
In response to Posting Data With jQuery AJAX In ASP.NET Razor Web Pages
Mike, what if I don't want to render back the text to the browser, but I want to send it some other...

Mike 6/27/2015 4:00 PM
In response to Migrating Classic ASP To ASP.NET Razor Web Pages Part One- Razor Syntax And Visual Basic
have you used any of the code converters to convert classic asp to c#? If so, which one do you have...