LANGUAGES: C# | VB .NET
ASP.NET VERSIONS: 1.0 | 1.1
Explore the Reports Starter Kit
In Part II of the Starter Kits series, extract the best parts of code and design from the free Microsoft ASP.NET Reports Starter Kit.
By Brian Noyes
In Mine the Starter Kits I introduced you to the ASP.NET Starter Kits and showed you how to use and extend the Community Starter Kit (CSK). This month I'll focus on the Reports Starter Kit (RSK) and show you how to extract the best nuggets of code and design from it. The RSK demonstrates many concepts for producing Web-based reporting solutions and displaying data in a variety of formats, including tables, hierarchical grids, drill-down reports, and graphical representations of your critical business data.
The bad news is- unlike the CSK- the RSK isn't an extensible framework of capabilities you can install and simply start populating with your own data and content. Instead, the RSK represents a collection of sample page types or mini-applications for displaying various types of reports, along with a design for getting the data to the pages from your data tier. The pages and middle-tier objects have a lot of application code mixed in specific to the sample, so you'll have to use them more like big sample snippets rather than something you can plug your data into and go. The good news is there are some great lessons in the RSK that'll show you effective techniques to collect and display data in various reporting formats in your own ASP.NET applications.
In this article, I'll focus on four aspects of the RSK that I think are its best lessons. These include how to create and display Web graphics on the fly, the use of custom collections for passing data through your middle tier, the use of the Data Access Application Blocks to simplify your data-access code, and the use of nested data-bound list controls on ASP.NET pages. The download code for this article, available in both C# and VB .NET, contains an implementation of a new visual report format to add to what the RSK provides. This new format follows the basic design of the other visual reports in the RSK and all the lessons I just mentioned (except the nesting of list controls).
Data a Manager Can Understand
The first step in reporting data is to decide in what form you want it displayed. Naturally, the most engaging report is the kind that even pointy-haired managers can understand: graphical or visual. This means you must take your business data and present it using one of many recognized forms of graphically summarized data. The RSK contains implementations for two forms of visual reports: simple pie charts and bar charts.
When implementing visual reports in the RSK, you have limited options compared to a professional graphics component such as those available from Infragistics, ComponentOne, or ChartFX. In fact, if you plan to report from pages in your Web application visually in a variety of common formats, I highly recommend you consider one of those vendor's products rather than trying to write it all yourself. But if you have custom visual reports you want to create that those products don't support, or you simply want to code it yourself for small projects, the RSK and the code for this article will give you a jumpstart.
This article's sample code includes a set of classes and a page that allow you to create line charts with multiple series of data (see Figure 1). I won't go into great detail on how to write the graphic-rendering code for this sample; for good coverage of that material see Get Graphic by Ken Getz and Build Dynamic Web Charts by Dino Esposito - but I will mention the process briefly.
Figure 1. The sample code for this article implements a third type of visual report: a line graph of multiple series. The report uses a collection of annual sales collections to display line graphs of those sales.
In the case of the RSK, the visual reports display a single series of sales by category. For the line graph visual report implemented in this article, I displayed multiple series of annual sales by region. I drew on the same set of data being used for the Cross Tab Report in the RSK.
To render your data graphically, use the Bitmap class from the .NET Framework Class Library. You get a Graphics object for it using the Graphics.FromImage method, then you can use Graphics object methods to render your graphic (see Figure 2).
// Create the bitmap we will draw to
Bitmap chartSurface = new Bitmap(Width,Height);
// Get the graphics object for the bitmap
Graphics g = Graphics.FromImage(chartSurface);
// Fill the background with the BG color
// Do any drawing on the Graphics object
// to render the graphic
// g.DrawLine(), g.FillEllipse(), etc.
// Clean up and release the graphics object
// return the result
Figure 2. You can obtain a Graphics object representing the Bitmap surface, on which you then can render your data using drawing commands. Once you are done rendering, you have a Bitmap object ready to go that you can save or return as a stream.
Once the Bitmap is rendered, you need to return it to the browser as something that can be placed on a page. One approach would be to save off the Bitmap as a temporary file and return an tag in the page that refers to that temp file. Because that's not a scalable solution, however, you must design some maintenance facilities for cleaning up the temp files later. A better approach is the way the RSK does it: Return the Bitmap as a binary stream from another page. That way, the tag simply can refer to that other page as the source of the Bitmap and it'll be requested and returned by the server as a binary stream. Or, you could return the image as a stream from an HTTP handler class instead of an actual page, but the result is the same.
The charting approach used in the RSK passes the chart data as Response.QueryString parameters to a class named ChartGenerator. This isn't a very scalable approach due to the length and encoding limitations of a query string. A better approach for real-world applications is either to post the data to the target page or handler, or design more intelligence into the chart generator so it could retrieve the data from the business tier itself.
Three of the other report types the RSK demonstrates provide ways for displaying hierarchical data: the Master-Details, Hierarchical, and Drill-Down reports. The Master-Details Report includes parent and child grids; the child grid displays the details of items selected in the parent grid. The Hierarchical and Drill-Down reports show two ways of providing an interactive navigation of the data from the top-level elements down into their dependent or child data items.
Gather the Seeds That You Sow
Once you know how you want to display data, the next thing you need to tackle is the plan to get the data from where it rests in the database to where it goes into action in your display pages. Although the RSK doesn't include much in the way of business logic, it does include an excellent practice for your business tier if you want to decouple the tiers of your architecture: Use custom collection classes to transport your data to the presentation tier.
The data-binding features of the DataGrid, DataList, and Repeater controls make wiring up data for Web page display a snap, even providing rich layout and interactivity. Unfortunately, simply passing your data straight through from your queries or stored procedures and binding it to the controls in the form of a DataSet or DataReader becomes quite tempting. Although using this approach results in less code and improves performance, it has one problem: It couples your presentation tier directly to the data tier- one of the key pitfalls n-tiered architectures aim to prevent. When you use this approach, you start writing data-binding code in your pages that relies directly on the schema of the data as it is returned from the database. Then, as soon as you need to change your stored procedures or schema, your presentation tier breaks.
To prevent this, you must realize that databinding supports many forms of collections including custom, type-safe collections of business objects you can create in your business tier to isolate the presentation code from the underlying data tier. The RSK demonstrates one step along this road by creating collection classes derived from ArrayList for the data exposed to the reporting pages. The problem with this approach of inheriting directly from ArrayList is that the resulting collection is not type-safe. The code that populates the collection class easily could insert objects of any type into the collection, and the display code probably is going to choke in a big way if it receives a collection of heterogeneous data that contains objects it doesn't understand.
If you're going to use collection classes to separate your presentation and data tiers, I recommend taking it one step further; I've demonstrated how in the sample code for this article. Instead of deriving from ArrayList, derive from CollectionBase, which contains an ArrayList member that allows you the same flexibility and ease in populating the collection, and it implements the ICollection, IList, and IEnumerable interfaces expected by collection iterators and data-bound list controls. When using this base class, you need to provide your own implementations of the methods to access the items in the collection (see Figure 3). This ensures that only items of the proper type can be put into the collection and that you can return items retrieved from the collection as typed, instead of untyped, object references. In fact, this article's sample code defines two collection classes; one is a collection of collections to handle the multiple data series of annual sales displayed in the line graph (see Figure 1). The design of these collections is shown in Figure 4.
public class RegionAnnualSalesItemCollection
public int Add(RegionAnnualSalesItem item)
public void Remove(RegionAnnualSalesItem item)
public void Insert(int index, RegionAnnualSalesItem item)
public int IndexOf(RegionAnnualSalesItem item)
public RegionAnnualSalesItem this[int index]
return this.List[index] as RegionAnnualSalesItem;
this.List[index] = value as RegionAnnualSalesItem;
Figure 3. By deriving from CollectionBase, you get free implementations of the ICollection, IList, and IEnumerable interfaces. You then provide your own implementations of the methods to access the items in the collection in a type-safe manner, and you can ensure that your collection remains a homogeneous collection of a specific object type.
Figure 4. The sample code for this article implements two collections; one contains the annual sales item data for a single region and the other contains the series of regional annual sales. They both derive from CollectionBase and implement type-safe collection classes decoupled from the database that provides the data.
Once you've defined the business layer collections you'll expose to your presentation tier, it's a simple matter in your business classes to gather the data from the database and populate the collections. Then, if in the future your data-tier implementation changes, only these translation classes in your business tier must also change; the presentation-layer consumers of the business collections do not. Any design decision like this requires you to consider trade-offs. You pay a slight performance penalty for doing this translation in the business tier and you must write a little more code (and maintain it). But in terms of providing a decoupled, maintainable, and scalable object-oriented architecture, it's usually worth the trade-off depending on your priorities.
In terms of collecting the data, the other nice technique the RSK demonstrates is the use of the Data Access Application Blocks (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnbda/html/daab-rm.asp) to simplify calls made to the database. If you've done much ADO.NET programming using stored procedures, you've probably written many methods that feel repetitive. You create a connection and a command object, then create each of the parameters and add them to the Command object's Parameters collection. Finally, you execute the command and harvest the results. The Data Access Application Blocks provide a SqlHelper class that encapsulates all that repetitive code for you; it allows you to do in a few lines of code what might've taken tens in raw ADO.NET.
For example, if you had a GetOrders stored procedure that took two parameters and returned a result set, you could make the call and get the results in a single line of code like this:
DataSet ds = SqlHelper.ExecuteDataset(
connString, "GetOrders", 24, 36);
The ExecuteDataset method takes a variable number of arguments as object references following the name of the stored procedure to call, and before making the call it iterates through those arguments, wraps them up as SqlParameter objects, and attaches them to the underlying SqlCommand object. Similar methods exist for scalar and non-query types of stored procedure results.
Cater to Your Nesting Instincts
One other great technique the RSK demonstrates is nesting the data-bound list controls within one another to achieve a logical display of hierarchical data. The Tabular Report, Cross-Tab Report, Hierarchical Report, and Drill-Down Report pages in the RSK each demonstrate different ways of nesting DataList and DataGrid controls within other DataList controls to achieve various layouts and interactivity with the hierarchical data display (see Figure 5). Although not demonstrated in the kit, you can achieve the same kinds of nesting with Repeater controls as well, but you must handle more of the details of the resulting layout.
Figure 5. Using templates, nesting DataGrids within DataLists to achieve a hierarchical display of data is straightforward.
This might sound complex, but the approach actually is quite easy- it simply requires a little understanding of the use of templates with the data-bound list controls. The basic approach is to create child controls within an item template for a DataList, or a bound column template for a DataGrid, that bind to some data item in its parent control. So, if you have order details tied to a particular order item, the parent list control can display order items, and the child list control can bind using the parent's item identifier to perform the query needed to populate its data (see Figure 6).
GetOrderDetails((int) DataBinder.Eval(Container.DataItem, "OrderID")) %>'>
DataBinder.Eval(Container.DataItem, "OrderID")) %>'>
Figure 6. In this code, a DataGrid is nested in a parent DataList. The DataGrid binds to data populated using the parent list item's order ID with a helper method defined in the page's codebehind.
If, in the code for the page, you write helper methods that use business objects to retrieve the child data based on the parent item's data, binding the child list controls to its parent item's data becomes a simple matter.
The RSK has many samples of how to create and display tabular and graphical data reports. You'll need to dig through the code and extract the coding patterns and techniques because the sample-specific code is interspersed with the generic design of the code. Using the design and coding techniques in the RSK should get you on the road to rich Web-based reporting applications much quicker than you could simply by blazing the trail on your own. Next, month I'll close this series with the Time Tracker, Portal, and Commerce Starter Kits. These kits demonstrate how to create line-of-business applications with rich interfaces that target both mobile and desktop users, and they include good techniques and design patterns worth emulating.
The sample code in this article is available for download.
Brian Noyes is an associate of IDesign Inc. (http://www.idesign.net) and a trainer, consultant, and writer. He's an MCSD with more than 12 years of programming, design, and engineering experience. Brian specializes in .NET architecture, design, and coding of data-driven Web and Windows applications, data access and XML technologies, and Office automation. He also is a contributing editor for asp.netPRO and other publications. E-mail him at mailto:[email protected].
Tell us what you think! Please send any comments about this article to [email protected]. Please include the article title and author.