Nest DataLists to Display Relational Data
Also, make session state available to HTTP handlers.
October 30, 2009
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 usenested DataLists - one DataList inside the other - to display relational datafrom two database tables that share a common column. Furthermore, I'd likeevents fired by the inner DataList to bubble up to the outer DataList. Can youhelp?
A: Nested DataLists test your knowledge of how DataListsand other data binding controls work. I had to think about this one a whilebefore coming up with a decent example. The example's source code is splitbetween Figures 1, 2, and 4. You can download the complete ASPX file (see endof article for details).
Figure 1 shows how to nest DataListsin a Web form. The outer DataList declares the inner DataList in anItemTemplate. In this example, the outer DataList binds to records selectedfrom the Northwind database's Customers table. For each item that it encountersin the data source, the outer DataList outputs the contents of the ContactNamefield, followed by an inner DataList with a CustomerID attribute that equalsthe contact name's customer ID. The outer DataList's output looks like this forthe first customer listed in the Customers table:
Maria Anders
...
In the Northwind database, theCustomers table and the Orders table are related by their CustomerID columns.The goal here is to have the inner DataList list all the order numbersattributed to the contact name output by the outer DataList. To that end, theinner DataList has an ItemTemplate of its own that renders order numbers asLinkButtons.
<%@ Import Namespace="System.Data.SqlClient" %>
OnItemDataBound="BindInnerDataList"> <%#DataBinder.Eval (Container.DataItem, "ContactName") %> RunAt="server" CustomerID='<%# DataBinder.Eval (Container.DataItem, "CustomerID") %>' RepeatColumns="5" RepeatDirection="Horizontal" OnItemCommand="OnInnerItemCommand"> Text='<%# DataBinder.Eval (Container.DataItem, "OrderID") %>' /> Figure 1. ThisASP.NET Web form features nested DataLists. Figure 2 contains the all-importantdata 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 outerDataList's ItemDataBound events, which fire for each and every item that theouter DataList binds to. The OnItemDataBound attribute in the outer DataList'stag connects ItemDataBound events to a handler named BindInnerDataList: OnItemDataBound="BindInnerDataList"> BindInnerDataList extracts therelevant records from the Orders table - all orders corresponding to a givencustomer ID - and binds them to the inner DataList. Observe thatBindInnerDataList doesn't perform a database query of its own. Instead, it usesa DataView to fetch the relevant records from a DataSet that contains a partialcopy of Northwind's Orders table. BindInnerDataList initializes the DataView'sRowFilter property with a filter expression that includes the customer IDencoded in the inner DataList's CustomerID attribute. Page_Load initializes theDataSet. Records retrieved from memory rather than performed as separatedatabase queries consolidates what would have been one query per customer intoa 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 (); // FetchOrders data and cache it SqlDataAdapteradapter = new SqlDataAdapter ("select OrderID, CustomerID from Orders", connection); _ds = newDataSet (); adapter.Fill(_ds); // FetchCustomers data SqlCommandcommand = new SqlCommand ("select CustomerID, ContactName from Customers", connection); SqlDataReaderreader = command.ExecuteReader (); OuterDataList.DataSource = reader; OuterDataList.DataBind (); } finally { connection.Close (); } }} void BindInnerDataList (Object sender, DataListItemEventArgse) { DataList inner =(DataList) e.Item.Controls[1]; DataView view = newDataView (_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 theinner DataList to data from Northwind's Orders table.
Figure 3. Nested DataLists displayrelational data from the Northwind database. Figure 3 shows the resulting page in Internet Explorer.For demonstration purposes, if you click an order number, that number appearsin the Label control at the bottom of the page. Figure 4 shows how this littlefeat is accomplished: Trap the inner DataList's ItemCommand events (which fireeach time a LinkButton - rendered by that DataList - is clicked) and executethe handler. An OnItemCommand attribute in theinner DataList's tag connects ItemCommand events to thehandler. void OnInnerItemCommand (Object sender, DataListCommandEventArgs e) { LinkButton button =(LinkButton) e.Item.Controls[1]; Output.Text = button.Text; }Figure 4. Click anorder number and this ItemCommand event handler displays the order number atthe bottom of the page. This page demonstrates how adeptnested DataLists are at displaying information from related tables in arelational database. The customer names come from the Customers table, which isrelated to the Orders table by customer ID. The order numbers come from theOrders table. Now, what about bubbling eventsfired by the inner DataList up to the outer DataList? In this case, it's notnecessary; as the sample demonstrates, processing the inner DataList'sItemCommand events is sufficient to determine which LinkButton was clicked andacts accordingly. This is usually, if not always, the case. And it's a goodthing, too, because bubbling events from an inner DataList to an outer DataListis problematic, at best, due to the way DataList.OnBubbleEvent is implementedin 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 isDataListCommandEventArgs) { handled =true; // Don't bubble the event ... } return handled; } The bottom line: DataLists don'tbubble ItemCommand events. You could modify this behavior if you derive a classof your own from DataList, override OnBubbleEvent in the derived class andreturn false, and replace the inner DataList with an instance of the derivedclass. But even then you'd run into problems, not the least of which is thefact that the Item property of the DataListCommandEventArgs passed to the outerDataList's ItemCommand handler would identify the inner DataList that fired theevent, not the LinkButton that precipitated the firing. Therefore, I wouldn'trecommend trying to bubble ItemCommand events unless the design of yourapplication absolutely requires it. Q: I've written acustom 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 statein my handler, however, throws an exception. Is session state not available toHTTP handlers? A: Session state is available toHTTP handlers, but only if you make it available. The secret is to derive thehandler class from System.Web.SessionState.IRequiresSessionState (if you needread/write access to session state) orSystem.Web.SessionState.IReadOnlySessionState (if you need read access only).These interfaces have no methods, so they require no implementation. Their verypresence is a signal to ASP.NET to initialize the Session property of theHttpContext object passed to your handler, with a reference to anHttpSessionState object representing the caller's session. To demonstrate, thefollowing ASHX file generates a NullReferenceException when it executes: <%@ WebHandler Language="C#"Class="SessionHandler" %> using System.Web; public class SessionHandler : IHttpHandler{ public voidProcessRequest (HttpContext context) { context.Response.Write (context.Session.ToString ()); } public bool IsReusable { get { return true;} }} This one executes without error, because theIReadOnlySessionState interface ensures that HttpContext.Session contains avalid reference: <%@ WebHandler Language="C#"Class="SessionHandler" %> using System.Web; using System.Web.SessionState; public class SessionHandler : IHttpHandler, IReadOnlySessionState{ public voidProcessRequest (HttpContext context) { context.Response.Write (context.Session.ToString ()); } public bool IsReusable { get { return true;} }} By default, ASP.NET refrains frommaking session state available to HTTP handlers to bolster performance. Thesame applies to ASP.NET Web services, as well. The sample code in this article is available fordownload. Jeff Prosise is the author of several books, including Programming Microsoft .NETfrom Microsoft Press. He's also a cofounder of Wintellect (http://www.wintellect.com), a softwareconsulting and education firm that specializes in .NET. Have a question forthis column? Submit queries to [email protected]. Tell us what you think! Please send any comments aboutthis article to [email protected] include the article title and author.
About the Author
You May Also Like