Managing Nested GridView Controls Portland OR

There's an easy way to manage the complex ASP.NET presentations that involve nested grids and controls. Learn a reliable approach that leverages the observer pattern.

Local Companies

Cascade Custom Software
503-922-0135
1000 SW Broadway
Portland, OR
Advantyx Software LLC
503-246-9299
6501 SW Macadam Ave
Portland, OR
Aravo Solutions
503-224-4049
2627 NW Nicolai St
Portland, OR
Axis Clinical Software Inc
503-292-3022
6443 SW Beaverton Hillsdale Hwy
Portland, OR
ApogeeInvent
403.407.9915
9999 SW Wilshire St. Ste. 100
Portland, OR
Cendix
503.789.2676
501 4th Street, Suite 741
Lake Oswego, OR
Artisan Software Tools Inc
503-245-6200
10220 SW Greenburg Rd
Portland, OR
Vision33
971-255-0162
The Lincoln Center, 10260 SW Greenburg Road, 4th Floor
Portland, OR
Paradigma Software
(503) 574-2776
6107 SW Murray Blvd #151
Beaverton, OR
ClearStar.net
360-892-0687
915 Broadway
Vancouver, WA

provided by: 
Originally published at Internet.com


Because propagating events up through two, three, or more layers of nested grids and controls can be challenging, I receive a lot of email about nested grids. In response, I have devised an easy way to manage complex ASP.NET presentations using a consistent, reliable approach and the observer pattern. This article demonstrates my approach.

You'll see the plumbing of a nested presentation layer, including getting data in downstream, publishing changes to the page upstream using the publish-subscribe form of the observer pattern, and using nested controls. (Read a step-by-step walkthrough of creating a nested GridView control.) Whereas delegates/events in .NET are an implementation of the observer pattern that you can daisy chain through multiple layers of nested controls, the approach in this article bypasses that approach and implements a single publisher. All events are sent to the publisher, which forwards the events to the subscriber. This approach works in theory and in practice.

Get Up to Speed

My previous article showed you how to add a page with a GridView. In the grid view, it showed how to create a template column, drop a user control in it, and place a nested GridView on that user control. As far as the mechanics of nesting controls go, you can repeat this process as often as you'd like. The only caveat is that after three or four levels of nesting, the amount of data coming back from the server may make the page respond very slowly.

Adding the Plumbing to Nested Controls

The first half of this article presents a step-by-step walkthrough of designing the nested presentation layer. To manage nested controls, you need to push data to the downstream, nested controls and get events and data back to the main page. The reason for this is the page itself usually manages session state and has the business objects.

Tip: If you are using the InProc session state, you can modify data in session from anywhere in the control hierarchy because the InProc server uses pointers. However, once you switch to an out-of-process server, your business objects are serialized and desterilized, and an object in session in a user control will not point to the same address as a cached object in another control. That is, out-of-process session does not maintain addresses—it can't.

It also is harder to reuse user controls if a specific object is grabbed from session at the user control level rather than passed down from a single object source.

This technique assumes that the business object is created and managed (in session) at the Page level, and that subordinate—or detail objects—are passed down to the nested controls using a public property. To summarize how the plumbing is assembled, you need to: 1. Add a public property on every user control. This property represents the detail data you will be passing to the nested user control. 2. You will need a binding statement to get the business object assigned to each nested user control instance created by each row of the GridView. 3. You will need to implement the publisher and subscriber interfaces. The publisher is a stand-alone object and the subscriber is the Page itself. 4. All nested controls will send all their events to the singleton instance of the publisher, which in turn will forward all events to one location, the Page.

Adding a Public Property on the User Control

Getting data to nested controls is easy. You need to declare a public property on the User Control. I use an object data type for the property. This approach permits me to reuse controls for different literal data types with similar kinds of data. It also eliminates the need foor importing my business assembly references into the ASP.NET page.

Continuing where my previous article left off (with the custom business objects Customer and Order, each Customer contained a generic list of order objects; the main page contained a GridView with the Customer; and the nested GridView showed each of the Order objects for the Customer), you need a property that will ultimately represent the Customer Orders. Listing 1 demonstrates the user control with the property representing the orders.

Listing 1: The Elided OrdersControl with the Public Property Used to Bind the List (of Orders) Partial Class OrdersControl Inherits System.Web.UI.UserControl Protected Sub Page_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load End Sub Public WriteOnly Property Data() As Object Set(ByVal value As Object) If (TypeOf value Is Customer) Then GridView1.DataSource = CType(value, Customer).MyOrders GridView1.DataBind() End If End Set End Property

Binding the Detail Data to the User Control

The next step is to tell the main page—or the parent control, if you are nested more than two levels deep—that data needs to be pushed to children (see Listing 2). The very simple binding statement is shown in bold as an assignment to the OrdersControl's public Data property using block script.

Listing 2: Binding the Child/Detail Data to the User Control <%@ Page Language="VB" AutoEventWireup="false" CodeFile="Default.aspx.vb" Inherits="_Default" %> <%@ Register src="http://www.developer.com/Orders.ascx" TagName="Orders" TagPrefix="uc2" %> <%@ Register src="http://www.developer.com/OrdersControl.ascx" TagName="OrdersControl" TagPrefix="uc1" %> Untitled Page

 

When the page in

If you are new to .NET, Listings 7, 8, and 9 present the Customer, Order, and data-access classes, respectively.

Listing 7: The Customer Class Imports Microsoft.VisualBasic Imports System.Collections.Generic ' This code was coded using coderush Public Class Customer Private _myOrders As List(Of _Order) = New List(Of _Order) Public Property MyOrders() As List(Of _Order) Get Return _myOrders End Get Set(ByVal Value As List(Of _Order)) _myOrders = Value End Set End Property Private _customerID As String Public Property CustomerID() As String Get Return _customerID End Get Set(ByVal Value As String) _customerID = Value End Set End Property Private _companyName As String Public Property CompanyName() As String Get Return _companyName End Get Set(ByVal Value As String) _companyName = Value End Set End Property Private _contactName As String Public Property ContactName() As String Get Return _contactName End Get Set(ByVal Value As String) _contactName = Value End Set End Property Private _contactTitle As String Public Property ContactTitle() As String Get Return _contactTitle End Get Set(ByVal Value As String) _contactTitle = Value End Set End Property Private _address As String Public Property Address() As String Get Return _address End Get Set(ByVal Value As String) _address = Value End Set End Property Private _city As String Public Property City() As String Get Return _city End Get Set(ByVal Value As String) _city = Value End Set End Property Private _region As String Public Property Region() As String Get Return _region End Get Set(ByVal Value As String) _region = Value End Set End Property Private _postalCode As String Public Property PostalCode() As String Get Return _postalCode End Get Set(ByVal Value As String) _postalCode = Value End Set End Property Private _country As String Public Property Country() As String Get Return _country End Get Set(ByVal Value As String) _country = Value End Set End Property Private _phone As String Public Property Phone() As String Get Return _phone End Get Set(ByVal Value As String) _phone = Value End Set End Property Private _fax As String Public Property Fax() As String Get Return _fax End Get Set(ByVal Value As String) _fax = Value End Set End Property End Class

Listing 8: The Implementation of the Order Class Public Class _Order Private _orderID As Nullable(Of Integer) Public Property OrderID() As Nullable(Of Integer) Get Return _orderID End Get Set(ByVal Value As Nullable(Of Integer)) _orderID = Value End Set End Property Private _customerID As String Public Property CustomerID() As String Get Return _customerID End Get Set(ByVal Value As String) _customerID = Value End Set End Property Private _employeeID As Nullable(Of Integer) Public Property EmployeeID() As Nullable(Of Integer) Get Return _employeeID End Get Set(ByVal Value As Nullable(Of Integer)) _employeeID = Value End Set End Property Private _orderDate As Nullable(Of DateTime) Public Property OrderDate() As Nullable(Of DateTime) Get Return _orderDate End Get Set(ByVal Value As Nullable(Of DateTime)) _orderDate = Value End Set End Property Private _requiredDate As Nullable(Of DateTime) Public Property RequiredDate() As Nullable(Of DateTime) Get Return _requiredDate End Get Set(ByVal Value As Nullable(Of DateTime)) _requiredDate = Value End Set End Property Private _shippedDate As Nullable(Of DateTime) Public Property ShippedDate() As Nullable(Of DateTime) Get Return _shippedDate End Get Set(ByVal Value As Nullable(Of DateTime)) _shippedDate = Value End Set End Property Private _shipVia As Nullable(Of Integer) Public Property ShipVia() As Nullable(Of Integer) Get Return _shipVia End Get Set(ByVal Value As Nullable(Of Integer)) _shipVia = Value End Set End Property Private _freight As Nullable(Of Decimal) Public Property Freight() As Nullable(Of Decimal) Get Return _freight End Get Set(ByVal Value As Nullable(Of Decimal)) _freight = Value End Set End Property Private _shipName As String Public Property ShipName() As String Get Return _shipName End Get Set(ByVal Value As String) _shipName = Value End Set End Property Private _shipAddress As String Public Property ShipAddress() As String Get Return _shipAddress End Get Set(ByVal Value As String) _shipAddress = Value End Set End Property Private _shipCity As String Public Property ShipCity() As String Get Return _shipCity End Get Set(ByVal Value As String) _shipCity = Value End Set End Property Private _shipRegion As String Public Property ShipRegion() As String Get Return _shipRegion End Get Set(ByVal Value As String) _shipRegion = Value End Set End Property Private _shipPostalCode As String Public Property ShipPostalCode() As String Get Return _shipPostalCode End Get Set(ByVal Value As String) _shipPostalCode = Value End Set End Property Private _shipCountry As String Public Property ShipCountry() As String Get Return _shipCountry End Get Set(ByVal Value As String) _shipCountry = Value End Set End Property End Class

The new nullable types are used for value type objects to permit assigning a null value to them without throwing an exception. Microsoft uses this technique when you use generated typed DataSet objects too. Nullable types can be assigned Nothing and work fine between nullable and non-nullable value types. You don't need to make string-types nullable because they are reference types and thus already nullable.

Listing 9: The Custom Data Access Class Imports Microsoft.VisualBasic Imports System.Collections.Generic Imports System.Data Imports System.Data.SqlClient Public Class Data Private Const connectionString As String = _ "Password=test;Persist Security Info=True;User ID=dummy;" + _ "Initial Catalog=Northwind;Data Source=localhost;" Public Shared Function GetCustomerList() As List(Of Customer) Dim customers As List(Of Customer) = GetCustomer() Dim orders As List(Of _Order) = GetOrder() Dim c As Customer Dim o As _Order For Each c In customers For Each o In orders If (c.CustomerID = o.CustomerID) Then c.MyOrders.Add(o) End If Next Next Return customers End Function Private Shared Function GetCustomer() As List(Of Customer) Using connection As SqlConnection = _ New SqlConnection(connectionString) connection.Open() Dim command As SqlCommand = _ New SqlCommand("SELECT * FROM Customers", connection) Dim reader As SqlDataReader = command.ExecuteReader() Dim customers As List(Of Customer) = New List(Of Customer) While (reader.Read()) Dim customer As Customer = New Customer customer.Address = HandleDBNull(reader("Address")) customer.City = HandleDBNull(reader("City")) customer.CompanyName = HandleDBNull(reader("CompanyName")) customer.ContactName = HandleDBNull(reader("ContactName")) customer.ContactTitle = HandleDBNull(reader("ContactTitle")) customer.Country = HandleDBNull(reader("Country")) customer.CustomerID = HandleDBNull(reader("CustomerID")) customer.Fax = HandleDBNull(reader("Fax")) customer.Phone = HandleDBNull(reader("Phone")) customer.PostalCode = HandleDBNull(reader("PostalCode")) customer.Region = HandleDBNull(reader("Region")) customers.Add(customer) End While Return customers End Using End Function Private Shared Function HandleDBNull(ByVal o As Object) If (o Is System.DBNull.Value) Then Return Nothing Else Return o End If End Function Private Shared Function GetOrder() As List(Of _Order) Using connection As SqlConnection = _ New SqlConnection(connectionString) connection.Open() Dim command As SqlCommand = New SqlCommand( _ "SELECT * FROM Orders Order BY CustomerID", connection) Dim reader As SqlDataReader = command.ExecuteReader() Dim orders As List(Of _Order) = New List(Of _Order) While (reader.Read()) Dim o As _Order = New _Order() o.CustomerID = HandleDBNull(reader("CustomerID")) o.EmployeeID = HandleDBNull(reader("EmployeeID")) o.Freight = HandleDBNull(reader("Freight")) o.OrderDate = HandleDBNull(reader("OrderDate")) o.OrderID = HandleDBNull(reader("OrderID")) o.RequiredDate = HandleDBNull(reader("RequiredDate")) o.ShipAddress = HandleDBNull(reader("ShipAddress")) o.ShipCity = HandleDBNull(reader("ShipCity")) o.ShipCountry = HandleDBNull(reader("ShipCountry")) o.ShipName = HandleDBNull(reader("ShhipName")) o.ShippedDate = HandleDBNull(reader("ShippedDate")) o.ShipPostalCode = HandleDBNull(reader("ShipPostalCode")) o.ShipRegion = HandleDBNull(reader("ShipRegion")) o.ShipVia = HandleDBNull(reader("ShipVia")) orders.Add(o) End While Return orders End Using End Function End Class Tip: For the custom data-access class in a production system, you would move the connection string to the web.config file and encrypt it. That technique is fodder for another article.

Building the Publisher

Now you know what a publisher should look like to subscribers. However, you need additional plumbing. You need methods to permit downstream nested controls to send events, and you need code to publish those events to subscribers. Listing 10 contains the implementation of the publisher (I named Broadcaster by personal convention).

Listing 10: An Implementation of IPublisher Public Class Broadcaster Implements IPublisher Private Const KEY As String = "BROADCASTER" Public Shared Function GetBroadcaster( _ ByVal session As HttpSessionState) As Broadcaster If (session Is Nothing) Then Return Nothing If (session(KEY) Is Nothing) Then session(KEY) = New Broadcaster End If Return CType(session(KEY), Broadcaster) End Function Public Shared Sub PostalCodeFieldChanged( _ ByVal session As HttpSessionState, ByVal sender As Object, _ ByVal e As ChangeEventArgs) If (session Is Nothing) Then Return Dim instance As Broadcaster = GetBroadcaster(session) If (instance Is Nothing = False) Then instance.PostalCodeFieldChanged(sender, e) End If End Sub Private Sub PostalCodeFieldChanged( _ ByVal sender As Object, ByVal e As ChangeEventArgs) RaiseEvent PostalCodeFieldChangedEvent(sender, e) End Sub Public Shared Sub Subscribe(ByVal session As HttpSessionState, _ ByVal subscriber As ISubscriber) subscriber.Subscribe(GetBroadcaster(session)) End Sub Public Shared Sub Unscubscribe(ByVal Session As HttpSessionState, _ ByVal subscriber As ISubscriber) subscriber.Unsubscribe(GetBroadcaster(Session)) End Sub Public Event PostalCodeFieldChangedEvent( _ ByVal sender As Object, ByVal e As ChangeEventArgs) _ Implements IPublisher.PostalCodeFieldChangedEvent End Class

The code in bold implements IPublisher. This code literally just re-raises the event to anything that might be listening. Generally, the listener is the main page, as previously mentioned.

Because you need only one publisher, shared methods are defined to get a single Broadcaster/IPublisher instance from session. To receive these published events, the main page will subscribe and unsubscribe as need be.

Implementing the Subscriber

The subscriber is the main Page itself. To create the subscriber, all you need to do is implement ISubscriber in the Page and write the calls to Broadcaster.Subscribe and Broadcaster.Unsubscribe. Listing 11 shows the implementation of the main page representing the subscriber.

Listing 11: The Page Containing All of the Nested Controls Implements ISubscriber Imports System.Collections.Generic Partial Class _Default Inherits System.Web.UI.Page Implements ISubscriber Private customers As List(Of Customer) Private KEY As String = "Customers" Protected Sub Page_Load(ByVal sender As Object, _ ByVal e As System.EventArgs) Handles Me.Load Broadcaster.Subscribe(Session, Me) If (IsPostBack = False) Then customers = Data.GetCustomerList GridView1.DataSource = customers GridView1.DataBind() Else customers = CType(Session(KEY),

Author: Paul Kimmel

Read article at Internet.com site

Featured Local Company

Cascade Custom Software

503-922-0135
1000 SW Broadway
Portland, OR

Related Local Events
WOOD TECHNOLOGY CLINIC & SHOW 2010
Dates: 3/9/2010 - 3/11/2010
Location: Oregon Convention Center
Portland, OR
View Details

Party in the Pinot
Dates: 7/25/2009 - 7/25/2009
Location: Oswego Hills Winery
West Linn, OR
View Details