asp:feature
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
g.Clear(_backgroundColor);
// Do any drawing on the Graphics object
// to render the graphic
// g.DrawLine(), g.FillEllipse(), etc.
// Clean up and release the graphics object
g.Dispose();
// return the result
return chartSurface;
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 <img> 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 <img> 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.
using System;
using System.Collections;
public class RegionAnnualSalesItemCollection
: CollectionBase
{
public int
Add(RegionAnnualSalesItem item)
{
return
this.List.Add(item);
}
public void
Remove(RegionAnnualSalesItem item)
{
this.List.Remove(item);
}
public void Insert(int
index, RegionAnnualSalesItem item)
{
this.List.Insert(index, item);
}
public int
IndexOf(RegionAnnualSalesItem item)
{
return
this.List.IndexOf(item);
}
public RegionAnnualSalesItem
this[int index]
{
get {
return
this.List[index] as RegionAnnualSalesItem;
}
set {
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).
<asp:datalist id=Datalist1 runat="server" >
<itemtemplate>
<table>
<tr>
<td>
Order
<%#
DataBinder.Eval(Container.DataItem,
"OrderDate") %>
</td>
</tr>
<tr>
<td>
<asp:datagrid
id=Datagrid1 runat="server"
DataSource='<%#
GetOrderDetails((int)
DataBinder.Eval(Container.DataItem, "OrderID")) %>'>
<!-- Details
omitted -->
</asp:datagrid>
</td>
</tr>
</table>
</itemtemplate>
</asp:datalist>
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:brian.noyes@idesign.net.
Tell us what you think! Please send any comments about
this article to mailto:feedback@aspnetPRO.com.
Please include the article title and author.