Sunday, May 17, 2009

Asp.net MVC and the Microsoft Charting Control

For the new project I am working on I require the display of some dynamically generated charts. I am developing the code using asp.net MVC. So I was pondering what charting component to use. I have used Google charting services in the past. They are so easy to use and I do not have to develop or host any components locally. There are two limitations with using the Google charts. One, is that they are served using the http protocol. Applications that require the use of SSL end up with mixed secure and insecure content on the site, causing browsers like IE to give warnings every time you visit the page. The other issue is that there is a usage limit. The limit is high, but with several charts being displayed on every page I can possibly hit the limit if the project has moderate success.

So instead I decided to generate the charts locally, this way I have full control over the charts and also can control caching which will be important in this particular case.

The next question then became which charting component to use? I have used http://www.dotnetcharting.com/ in the past with great success. I have also used http://dundas.com/ charts in the past and was also happy with them. Not wanting to spend any money on the charting component I investigated the new release of a Microsoft component called Microsoft Charting Control which Scott Guthrie announced recently. After spending some time researching this I found out that Microsoft had bought some software from Dundas and that this component is in reality the Dundas charts repackaged. So that gives me the best of both worlds, a world class charting component, for free.

Here are the links to download the charting component and the tool support for Visual Studio 2008:

http://www.microsoft.com/downloads/details.aspx?FamilyID=130f7986-bf49-4fe5-9ca8-910ae6ea442c&DisplayLang=en

http://www.microsoft.com/downloads/details.aspx?familyid=1D69CE13-E1E5-4315-825C-F14D33A303E9&displaylang=en

 

There is plethora of examples on how to build the charts and sample code:

http://code.msdn.microsoft.com/mschart/Release/ProjectReleases.aspx?ReleaseId=1591

http://www.microsoft.com/downloads/details.aspx?FamilyId=EE8F6F35-B087-4324-9DBA-6DD5E844FD9F&displaylang=en

 

But very little in the way of integrating them with asp.net MVC. In the comments, Scott posted a link to a blog that described this but it was not really complete nor 100% clear to me.

Here is what I needed to do (after installing the actual control from the links above) to make my app recognize the charting components.

In my web.config I had to add the following lines:

 

<add tagPrefix="asp" namespace="System.Web.UI.DataVisualization.Charting" assembly="System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>

in the <controls> subsection of the <pages> section.

<add path="ChartImg.axd" verb="GET,HEAD" type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" validate="false"/>

in the <httpHandlers> section.

For IIS 7, I had to add the following

<add name="ChartHttpHandler" preCondition ="integratedMode" verb="GET,HEAD,POST" path="ChartImg.axd"
            type="System.Web.UI.DataVisualization.Charting.ChartHttpHandler, System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

to the <handlers> section.

Visual Studio adds the following line the first time you drag a chart from the tool box to a design surface:

<add assembly="System.Web.DataVisualization, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31BF3856AD364E35"/>

to the <assemblies> section.

 

This allowed me to create a simple page with a chart using the designer. It showed sample data when I ran the project in Visual Studio, but then the minute I published the project, it stopped working.

After a whole lot of time wasted trying to figure out why this failed, I discovered that the controls create the images by storing them in a temp folder on disk. The default path for the temp folder is some weird path (c:\tempImageFiles) that does not exist on my machine and the controls do not create it.

Turns out that I can override the path by adding a setting to the <appSettings> section to change the path, the setting is:

<add key="ChartImageHandler" value="storage=file;timeout=20;dir=c:\TempImageFiles\;" />

I just changed the path to an existing path on disk and that worked when I published to my local IIS.

However when I tested by publishing to my shared host, it failed again. So back to the drawing board. After a lot of head scratching I found that I can instruct the controls to use Memory for temporary image storage and that solved the problem at my shared host.

Here is a link http://blogs.msdn.com/deliant/archive/2008/12/02/managing-chart-generated-images-with-chart-image-handler.aspx that describes the different settings for that key.

Great, so that solves the setup problems, but how do actually use this in MVC?

A quick Google search unveiled several unacceptable solutions. Some require writing directly to the Response object. I was not about to go down that path. The whole reason for going MVC is not to do stuff of the sort any more.

Another solution requires the use of the MVC Futures project, code that is slated to become part of the official release in the future, but may not make it. I wanted to use the released MVC.

Other solutions require writing your own custom ActionResult classes. While I do not mind doing this, I am not crazy about maintaining my own set of extensions to asp.net MVC that may become obsolete or broken with future releases.

What I really want to do is something like this:

<img src=’/MyChartController/GetChart/1’ alt=’Chart generated with MS Charting Control and served using MVC controller’>

to my html and have the code in my controller generate everything needed to display the chart correctly.

My final solution ended up using bits and pieces of the last two solutions I mentioned though relying on released MVC features without the need to write custom classes or using the Futures libraries.

Here is my sample code:

// Notice the return type, the regular, released, FileResult ActionResult. Not a generic result, nor a custom written class.

public FileResult  GetChart(int id) {

            // Create a Sample Chart with bogus data. Sample code can be found in the documentation, i just used some of the sample code in the examples I linked to above.

            System.Web.UI.DataVisualization.Charting.Chart Chart2 = new System.Web.UI.DataVisualization.Charting.Chart();
            Chart2.Width = 400;
            Chart2.Height = 300;
            Chart2.RenderType = System.Web.UI.DataVisualization.Charting.RenderType.ImageTag;
            Chart2.Palette = System.Web.UI.DataVisualization.Charting.ChartColorPalette.BrightPastel;
            System.Web.UI.DataVisualization.Charting.Title t = new Title("IMG source streamed from Controller", Docking.Top, new System.Drawing.Font("Trebuchet MS", 14,    System.Drawing.FontStyle.Bold), System.Drawing.Color.FromArgb(26, 59, 105));
            Chart2.Titles.Add(t);
            Chart2.ChartAreas.Add("Series 1");
            // create a couple of series  
            Chart2.Series.Add("Series 1");
            Chart2.Series.Add("Series 2");
            // add points to series 1  
            for(int value=0;value <10;value++) {
                Chart2.Series["Series 1"].Points.AddY(value);
            }

            for (int value = 20; value < 50; value++)
            {
                Chart2.Series["Series 2"].Points.AddY(value + 1);
            }

            Chart2.BorderSkin.SkinStyle = BorderSkinStyle.Emboss;
            Chart2.BorderlineWidth = 2;
            Chart2.BorderColor = System.Drawing.Color.Black;
            Chart2.BorderlineDashStyle = ChartDashStyle.Solid;
            Chart2.BorderWidth = 2;
            Chart2.Legends.Add("Legend1");

 

            // This is the most important part, and the departure from using any custom classes or Futures library.

            // Simply use a MemoryStream to save the chart.
            MemoryStream imageStream = new MemoryStream();
            Chart2.SaveImage(imageStream, ChartImageFormat.Png);

            // Reset the stream’s pointer back to the start of the stream.
            imageStream.Seek(0, SeekOrigin.Begin);

            // return the normal FileResult available in the current release of MVC

            return File(imageStream, "image/png");

        }

 

Of course, the content of my chart will not be this trivial. You could load the image from a database, but in my particular case, the images will constantly change as the underlying data changes so I cannot store a static copy in the database. Instead, i will generate the data series from the database and bind the Chart to my data source. I will also have other chart types, like pie charts, etc… but the general concept will be exactly as laid out above. Build the chart programmatically, load it with data, write it to a memory stream and return the stream as a FileResult.

The best news is that the solution uses a free (but very powerful) charting Control, using the released ASP.NET MVC and works in shared hosting environments.

Easy as pie :)

1 comments:

r4 ds said...

Thanx for the code. It will help ma alot in my proj. Will be visiting back soon. Keep posting. And thanx once again for the valuable information.