(转)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.

 

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

 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? :)

posted @ 2010-02-05 14:16  PROS  阅读(963)  评论(0编辑  收藏  举报