ASP.NET MVC: Creating reports using Chart control

ASP.NET Chart control is powerful control you can use to add charting support to your web applications. Although chart controls are mainly used with ASP.NET forms it is not very hard to use them also in ASP.NET MVC applications. In this posting I will show you how to use ASP.NET Chart control in ASP.NET MVC application and I will illustrate how to do it easily so you don’t mess up your views.

Before we start coding I want to make some notes about my solution:

  • this solution is pretty new and it is sure that one can improve the code provided here,
  • using this solution I’m trying to generalize in-place reporting for MVC applications,
  • also I’m trying to keep my views as clean as possible – chart definitions are not small if you have more complex charts or if you want very nice looking charts.

If you are not familiar with ASP.NET Chart control then please read my blog posting <asp:Chart>. You find there simple introduction to this free control and also all necessary links.

Solution overview

What we are trying to build here is shown on the following diagram. Our main goal is to avoid using ASP.NET forms elements in our MVC line user interface. Also we want to generalize reporting support so we have one interface for all reports.

Solution overview

I introduce here pretty simple report. If you have more complex reports then you can extend reporting interface shown below later. You may also find useful to reorganize outputing system. I am using here simple works-for-me or works-for-prototyping solution. I want to focus on point and let’s try not to lose it.

Reporting interface

Before we create our first report let’s define interface for it. This interface must define all basic actions we need to do with reports:

  • set source with report data,
  • bind data to report,
  • write report image to some (output) stream.

This is my reporting interface. Note that DataSource has only setter – it is because I don’t have currently need to ask data from chart. I only provide data to it.


public interface IReportControl : IDisposable

{

    void DataBind();

    object DataSource { set; }

    void SaveChartImage(Stream stream);

}


As you can see this interface is pretty thin. I am sure that it will grow in the future when need for more complex reports appears.

Sample report

Let’s define one report for testing purposes. Add web user control called MyReport.ascx to Reports folder of your web application and drag ASP.NET Chart control on it. Here is definition of my control.


<%@ Control Language="C#" AutoEventWireup="true"
    CodeBehind="LastEnquiriesChart.ascx.cs"
    Inherits="ReportingApp.Web.Reports.LastEnquiriesChart"
%>

<%@ Register
    assembly="System.Web.DataVisualization, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"

    namespace="System.Web.UI.DataVisualization.Charting"

    tagprefix="asp"
%>

 

<asp:Chart ID="Chart1" runat="server" Palette="Excel"

    Height="200px" Width="200px">

    <series>

        <asp:Series Name="Series2"

            CustomProperties="DrawingStyle=Emboss"

            XValueMember="Date"
           
YValueMembers="Count"
           
IsValueShownAsLabel="True"

            Font="Microsoft Sans Serif, 8pt"
            LabelBackColor="255, 255, 192"

            LabelBorderColor="192, 192, 0"
            LabelForeColor="Red">          

        </asp:Series>

    </series>

    <chartareas>

        <asp:ChartArea Name="ChartArea1" BorderDashStyle="Solid">

            <AxisX
                IntervalAutoMode="VariableCount"
                IntervalOffsetType
="Days"

                IntervalType="Days"
               
IsLabelAutoFit="False"
               
IsStartedFromZero="False">

                <MajorGrid 
                   
Interval="Auto"
                   
IntervalOffsetType="Days"
                    IntervalType
="Days" />

            </AxisX>

        </asp:ChartArea>

    </chartareas>

    <Titles>

        <asp:Title 
            Font="Microsoft Sans Serif, 8pt, style=Bold"

            Name="Title1"

            Text="Last week enquiries">

        </asp:Title>

    </Titles>

</asp:Chart>


If you look at the definition and consider it as simple one you understand why I don’t want this mark-up to be there in my views. This is just one simple report. But consider for a moment three complex reports. 90% of my view will be one huge report definition then and I will miss all the good things that views have.

As I want this report to be interfaced with my reporting mechanism I make it implement IReportControl interface. Code-behind of my control is as follows.


public partial class LastEnquiriesChart : UserControl, IReportControl

{

    public object DataSource

    {

        set

        {

            Chart1.DataSource = value;

        }

    }

 

    public override void DataBind()

    {

        base.DataBind();

        Chart1.DataBind();

    }

 

    public void SaveChartImage(Stream stream)

    {

        Chart1.SaveImage(stream);

    }

}


All other user controls we are using for reporting must also implement IReportControl interface. This leads us to one interesting finding – we don’t have to host only chart control in our user controls, we have to host there whatever ASP.NET forms control we need for reporting. We can also create wrapper controls that get report image from some other external source (let’s say we have some COM component that is able to return reports as images).

Creating loader

Now we have sample report control and interface we can use to provide data and catch output of report. It is time to create meeting place for two worlds: ASP.NET forms and ASP.NET MVC framework. I created class called ReportLoader. The name of this class is good enough for me because it tells me that this is the integration point between two worlds. Let’s look at loader implementation now.


public static class ChartLoader

{

    public static void SaveChartImage(string controlLocation,
    IEnumerable data, Stream stream)

    {

        using (var page = new Page())

        using (var control = (IReportControl)
               page.LoadControl("~/Reports/" + controlLocation))

        {

            control.DataSource = data;

            control.DataBind();

            control.SaveChartImage(stream);

        }

    }

}


Loader does one trick: it doesn’t render the control. It only runs as long as report is written to stream and then it disposes user control and temporary page instance to avoid all other actions they may take. I made ChartLoader and SaveChartImage methods as static because I don’t need hell load of classes and super-cool architecture right now.

Creating controller action

We are almost there… Let’s create now controller action that returns chart image. As I am prototyping my application I use very robust controller action. You may be more polite coders and I strongly suggest you to read Bia Securities blog posting BinaryResult for Asp.Net MVC. You can find BinaryResult implementation also from MVC Contrib project.


[HttpGet]

public ActionResult GetChart()

{           

    var repository = Resolver.Resolve<IPriceEnquiryRepository>();

    var enquiries = repository.ListPriceEnquiries();

    var data = from p in enquiries

               group p by p.Date.Date into g

               select new { Date = g.Key, Count = g.Count() };

 

    Response.Clear();

    Response.ContentType = "image/png";

    ChartLoader.SaveChartImage(

                "LastEnquiriesChart.ascx",

                data,

                Response.OutputStream);           

    Response.End();           

 

    return null; // have to return something

}


Now we have controller action that asks data from somewhere, prepares it for report and asks report as image from ChartLoader. Before outputing the report Response is cleared and content type is set to PNG. After writing image to response output stream the response is ended immediately to avoid any mark-up that may be written there otherwise.

As you can see I created special automatic objects for reporting. If you look at my report definition you can see that x-axis is bound to Date and y-axis to Count. You can also prepare reporting data in some near-DAL class methods and then slide this data through controller to report. The choice is yours.

Adding report to view

ASP.NET Chart control output Now let’s link report to view. We just have to add one simple img tag to our view and make it src to call out GetChart() method defined above.


<img
    src="<%= Url.Action("GetChart")%>
    alt="Last week enquiries
    title="Last week enquiries"
/>


Output of report is shown on right. I don’t have much data here and my report is not very nice but it works. Now, if you are not too tired or bored, it is time to make your chart very nice and show it to your boss or customer.

Conclusion

Mixing forms and MVC worlds of ASP.NET doesn’t always have to end up with hard mess. In this posting I showed you how to add simple but pretty generic reports support to your ASP.NET MVC application. Due to good interfacing we achieved separation between forms and MVC templates and linking reports to views is very-very simple.

Of course, code and interfaces represented here are not production-ready examples. But they give you right direction and you can always improve design of my solution. My point was to illustrate how to mix MVC and forms world in reporting context and I feel like I succeeded this time. What do you think? :)

14 Comments

  • Nice article.

    Didnt know that we can use the UserControl can be used in this way to do a dirty work and save the chart image. Thanks for the article :)

  • This sounds great for a stateless implementation. But doesn't using an image handler give you caching capabilities? I guess I am confused as to why this way is better than using an image handler?

  • Is the image loaded asynchronously with the page? Also, does IIS cache this image as you can when using an HTTP ImageHandler?

  • Hi Chuck! Sorry for late answer. The solution provided here is just to give you some idea how to mix separate ASP.NET frameworks so this mix doesn't happen in same files and modules.

    Of course, you can use this code as base and add caching if necessary etc. You can also use image handler - no problem. Using image handler and caching is more like optimization topic and I wasn't sure if it is good idea to cover this topic too as this posting is long enough.

  • This post is wonderful. It is just what I want. Thank you.

    I also would like to export the chart to excel using OpenXML. Do you have an example to share?

  • Thanks for feedback, Mei :)

    Currently I have no good example for charting in Excel. As soon as I invent something I will write about it.

  • Could you put a complete project code on the page so I can download and try it?

  • You can take source here: http://github.com/gpeipman/Visual-Studio-Experiments/tree/master/Experiments.ChartControlInMvc/Experiments.ChartControlInMvc/ You can open files in browser and take source using copy and paste

  • Thanks for the tutorial. It's very helpful in being able to switch out chart images on the fly. But what I found with this solution is that you can't have any client-side interactivity with it (because it's an image). For example, if I want to show a tooltip of the (x,y) of the position when the mouse hovers over it... can't do that with an image.

    In this case I guess I'll have to use control in an iframe or something.... so I can both switch it out on the fly and have client-side interactivity... I'll try it. Thanks for the example.

  • This is the only line I'm having trouble with
    Resolver.Resolve

    1. Revsolver is unknown
    2. IPriceEnquiryRepository is unknown

    Thus am I missing an Interface ?

    I'm using 3.5 sp1 mvc2

    Thanks

    T

  • Resolver and IPriceEnquiryRepository are just example types. Resolver is shortcut method to some IoC/DI container that returns you object by interface. You can play it around if you need or you can use some IoC/DI container like Unity, StructureMap, NInject etc.

  • MVC 2 and above includes the File Action Result so the GetChart Controller action could be modified as follows:

    var ms = new MemoryStream();
    ChartLoader.SaveChartImage(
    "LastEnquiriesChart.ascx",
    data,
    ms);
    return File(ms.GetBuffer(), "image/png");

  • Great post thanks.

  • Hi,

    Thanks for a great article!!... I've gotten somewhere but I'd love to see the project if you have a sample.

    Thanks!

Comments have been disabled for this content.