Ask the Pro
LANGUAGES: C#
ASP.NET VERSIONS: 1.0 | 1.1
Nest DataLists to Display Relational Data
Also, make session state available to HTTP handlers.
By Jeff Prosise
Q: I want to use nested DataLists - one DataList inside the other - to display relational data from two database tables that share a common column. Furthermore, I'd like events fired by the inner DataList to bubble up to the outer DataList. Can you help?
A: Nested DataLists test your knowledge of how DataLists and other data binding controls work. I had to think about this one a while before coming up with a decent example. The example's source code is split between Figures 1, 2, and 4. You can download the complete ASPX file (see end of article for details).
Figure 1 shows how to nest DataLists in a Web form. The outer DataList declares the inner DataList in an ItemTemplate. In this example, the outer DataList binds to records selected from the Northwind database's Customers table. For each item that it encounters in the data source, the outer DataList outputs the contents of the ContactName field, followed by an inner DataList with a CustomerID attribute that equals the contact name's customer ID. The outer DataList's output looks like this for the first customer listed in the Customers table:
Maria Anders
...
In the Northwind database, the Customers table and the Orders table are related by their CustomerID columns. The goal here is to have the inner DataList list all the order numbers attributed to the contact name output by the outer DataList. To that end, the inner DataList has an ItemTemplate of its own that renders order numbers as LinkButtons.
<%@ Import Namespace="System.Data.SqlClient" %>
Figure 1. This ASP.NET Web form features nested DataLists.
Figure 2 contains the all-important data binding code. The outer DataList is bound to its data source in Page_Load. The inner DataList is bound to a data source in response to the outer DataList's ItemDataBound events, which fire for each and every item that the outer DataList binds to. The OnItemDataBound attribute in the outer DataList's tag connects ItemDataBound events to a handler named BindInnerDataList:
OnItemDataBound="BindInnerDataList"> BindInnerDataList extracts the
relevant records from the Orders table - all orders corresponding to a given
customer ID - and binds them to the inner DataList. Observe that
BindInnerDataList doesn't perform a database query of its own. Instead, it uses
a DataView to fetch the relevant records from a DataSet that contains a partial
copy of Northwind's Orders table. BindInnerDataList initializes the DataView's
RowFilter property with a filter expression that includes the customer ID
encoded in the inner DataList's CustomerID attribute. Page_Load initializes the
DataSet. Records retrieved from memory rather than performed as separate
database queries consolidates what would have been one query per customer into
a single query and is virtually guaranteed to improve performance. DataSet _ds; ... void Page_Load (Object sender, EventArgs e) { if (!IsPostBack) { SqlConnection connection = new SqlConnection ("server=localhost;database=northwind;uid=sa"); try { connection.Open (); // Fetch
Orders data and cache it SqlDataAdapter
adapter = new SqlDataAdapter ("select OrderID, CustomerID from Orders", connection); _ds = new
DataSet (); adapter.Fill
(_ds); // Fetch
Customers data SqlCommand
command = new SqlCommand ("select CustomerID, ContactName from Customers", connection); SqlDataReader
reader = command.ExecuteReader (); OuterDataList.DataSource = reader; OuterDataList.DataBind (); } finally { connection.Close (); } } } void BindInnerDataList (Object sender, DataListItemEventArgs
e) { DataList inner =
(DataList) e.Item.Controls[1]; DataView view = new
DataView (_ds.Tables[0]); view.RowFilter =
("CustomerID='" + inner.Attributes["CustomerID"] + "'"); inner.DataSource =
view; inner.DataBind (); } Figure 2.
Page_Load binds the outer DataList to data from Northwind's Customers table.
The outer DataList's ItemDataBound handler - BindInnerDataList - binds the
inner DataList to data from Northwind's Orders table. Figure 3 shows the resulting page in Internet Explorer.
For demonstration purposes, if you click an order number, that number appears
in the Label control at the bottom of the page. Figure 4 shows how this little
feat is accomplished: Trap the inner DataList's ItemCommand events (which fire
each time a LinkButton - rendered by that DataList - is clicked) and execute
the handler. An OnItemCommand attribute in the
inner DataList's void OnInnerItemCommand (Object sender, DataListCommandEventArgs e) { LinkButton button =
(LinkButton) e.Item.Controls[1]; Output.Text = button.Text; } Figure 4. Click an
order number and this ItemCommand event handler displays the order number at
the bottom of the page. This page demonstrates how adept
nested DataLists are at displaying information from related tables in a
relational database. The customer names come from the Customers table, which is
related to the Orders table by customer ID. The order numbers come from the
Orders table. Now, what about bubbling events
fired by the inner DataList up to the outer DataList? In this case, it's not
necessary; as the sample demonstrates, processing the inner DataList's
ItemCommand events is sufficient to determine which LinkButton was clicked and
acts accordingly. This is usually, if not always, the case. And it's a good
thing, too, because bubbling events from an inner DataList to an outer DataList
is problematic, at best, due to the way DataList.OnBubbleEvent is implemented
in the .NET Framework. If you could sneak a peek at DataList's source code,
you'd see something like this: protected override bool OnBubbleEvent (Object sender, EventArgs e) { bool handled = false;
// Bubble the event upward if (e is
DataListCommandEventArgs) { handled =
true; // Don't bubble the event ... } return handled; } The bottom line: DataLists don't
bubble ItemCommand events. You could modify this behavior if you derive a class
of your own from DataList, override OnBubbleEvent in the derived class and
return false, and replace the inner DataList with an instance of the derived
class. But even then you'd run into problems, not the least of which is the
fact that the Item property of the DataListCommandEventArgs passed to the outer
DataList's ItemCommand handler would identify the inner DataList that fired the
event, not the LinkButton that precipitated the firing. Therefore, I wouldn't
recommend trying to bubble ItemCommand events unless the design of your
application absolutely requires it. Q: I've written a
custom HTTP handler that renders and returns images. To complete the rendering,
I need to access the caller's session. Any attempt to read from session state
in my handler, however, throws an exception. Is session state not available to
HTTP handlers? A: Session state is available to
HTTP handlers, but only if you make it available. The secret is to derive the
handler class from System.Web.SessionState.IRequiresSessionState (if you need
read/write access to session state) or
System.Web.SessionState.IReadOnlySessionState (if you need read access only).
These interfaces have no methods, so they require no implementation. Their very
presence is a signal to ASP.NET to initialize the Session property of the
HttpContext object passed to your handler, with a reference to an
HttpSessionState object representing the caller's session. To demonstrate, the
following ASHX file generates a NullReferenceException when it executes: <%@ WebHandler Language="C#"
Class="SessionHandler" %> using System.Web; public class SessionHandler : IHttpHandler { public void
ProcessRequest (HttpContext context) { context.Response.Write (context.Session.ToString ()); } public bool IsReusable { get { return true;
} } } This one executes without error, because the
IReadOnlySessionState interface ensures that HttpContext.Session contains a
valid reference: <%@ WebHandler Language="C#"
Class="SessionHandler" %> using System.Web; using System.Web.SessionState; public class SessionHandler : IHttpHandler, IReadOnlySessionState { public void
ProcessRequest (HttpContext context) { context.Response.Write (context.Session.ToString ()); } public bool IsReusable { get { return true;
} } } By default, ASP.NET refrains from
making session state available to HTTP handlers to bolster performance. The
same applies to ASP.NET Web services, as well. The sample code in this article is available for
download. Jeff Prosise is the author of several books, including Programming Microsoft .NET
from Microsoft Press. He's also a cofounder of Wintellect (http://www.wintellect.com), a software
consulting and education firm that specializes in .NET. Have a question for
this column? Submit queries to [email protected]. Tell us what you think! Please send any comments about
this article to [email protected].
Please include the article title and author.
Figure 3. Nested DataLists display
relational data from the Northwind database.