Nest DataLists to Display Relational Data

Also, make session state available to HTTP handlers.

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" %>

 

  

    

      

        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. 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. Nested DataLists display relational 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 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 tag connects ItemCommand events to the handler.

 

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.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish