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: Tuesday, September 15, 2009 10:16 PM
Last Updated: Friday, October 10, 2014 9:13 PM
Posted by: Mikesdotnetting
Total Views to date: 85062

9 Comments

Wednesday, November 4, 2009 3:57 PM - 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.

Tuesday, November 17, 2009 11:40 AM - 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.

Wednesday, November 18, 2009 7:52 PM - Mike

@deepak

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

Friday, November 27, 2009 12:34 PM - 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?

Monday, November 22, 2010 3:10 AM - Sam

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

Thursday, June 5, 2014 8:17 AM - Kraus Maus

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

Thursday, June 5, 2014 9:19 AM - 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?

Wednesday, November 12, 2014 3:44 AM - Ima

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

Wednesday, November 12, 2014 7:05 AM - 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.
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 some may not be published. The kind of things that will ensure your comment is deleted without ever seeing the light of day are as follows:

  • Not relevant to the article
  • Gratuitous links to your own site or product
  • Anything abusive or libellous
  • Spam
  • Anything in a language I don't understand including gibberish.

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

Gautam 11/20/2014 8:01 AM
In response to I'm Writing A Book On WebMatrix
Hello Mike, I read your book, loved it! However, I have a few request/suggestions: 1) an example...

Bret Dev 11/19/2014 8:39 PM
In response to The Difference Between @Helpers and @Functions In WebMatrix
Excellent post! One concern - where can you place global @Functions code within an MVC project to Is...

Rob Farquharson 11/19/2014 4:28 PM
In response to iTextSharp - Links and Bookmarks
How can I place text at an absolute position on the page? Also, how can I rotate text?...

Andy 11/17/2014 8:08 PM
In response to MVC 5 with EF 6 in Visual Basic - Sorting, Filtering and Paging
Hello I'm testing your sorting instructions above. This is great and I was able to get it to work...

Gautam 11/17/2014 5:51 PM
In response to WebMatrix - Database Helpers for IN Clauses
Hi Mike, I am very new to programming: In the above example if I want to use a delete button the...

donramon 11/17/2014 3:22 PM
In response to Entity Framework 6 Recipe - Alphabetical Paging In ASP.NET MVC
Congratulations on your new website look and the excellent articles. Thank you!...

Gautam 11/17/2014 11:26 AM
In response to Looking At The WebMatrix WebGrid
Hi Mike, I add the jquery script at the end of my html file.. when ajax attribute is added to the be...

Chet Ripley 11/15/2014 6:57 PM
In response to Adding A New Field
It appears the command is case sensitive. I had the same issue as Cameron. When I changed the to it...

Alvin 11/14/2014 12:49 PM
In response to Razor Web Pages E-Commerce - Adding A Shopping Cart To The Bakery Template Site
Great article Mike! When do you plan to extend the bakery shopping cart beyond this point?...

Gautam 11/14/2014 10:16 AM
In response to Web Pages - Efficient Paging Without The WebGrid
to get the count can we use only the below sql, why to join category and author table var sql =...