Practical ASP.NET

Creating Master-Detail Pages in ASP.NET: Details, Details, Details

Peter shows you how to make a Master-Detail page that will work right and save your users a lot of grief.

While creating a Master-Detail page in ASP.NET using DataSources and DataViews is easy, getting it to work right requires setting some more obscure properties.

Seduced by the power of ASP.NET, say you decide to create a page that lists all of your application's customers in a drop-down listbox and, when the user selects a customer, displays all the orders for that customer. The user will be able to add, change and delete orders.

You drag a DataSource onto the page, aim it at some Customer object factory (for the ObjectDataSource), directly at the Customer table (SqlDataSource) or go through some auto-generated objects to the Customer table (LinqDataSource). You do the same to create a DataSource for Orders. You then bring on a DropDownList that you attach to your Customer DataSource to list all the customers and a DetailsView that you attach to the Orders DataSource (you use a DetailsView rather than a GridView because a GridView doesn't support adding new records).

The next step is to join the two DataSources so that the Orders DataSource displays only the orders for the selected customer. This is also easy: In the Customer DropDownList you make sure that the DataValueField property is set to the name of the field or property that holds the CustomerId. Then, in the Orders DataSource you aim a parameter that accepts a customer number at the SelectedValue property of the Customer DropDownList. Finally, you enable updating for the Orders DetailsView and DataSource so that users can change Order data.

You bring up the page and discover that you have...issues. The page works but it's both inefficient and encourages errors.

Skip Retrieving Data
The first thing you notice is that when the page comes up, it displays all of the orders for the first customer in the list. It's unlikely that your user will always want to see the orders for the first customer, so retrieving those orders when the page is first requested is a complete waste of time.

This solution is, after configuring your page, to go back to the Orders DetailsView and erase its DataSourceID property. When a DataView isn't connected to a DataSource, ASP.NET won't automatically retrieve its data. Only when a user actually selects a customer (i.e., in the SelectedIndexChanged event for the Customers DropDownList) should you set the Orders DetailsView's DataSourceId properties to point to its DataSource:

Protected Sub CustomerDownList_SelectedIndexChanged(...
   Me.OrdersView.DataSourceID = Me.OrdersDataSource.ID
End Sub

Picking the First Customer
This, unfortunately, creates another problem: When users do want to view the Orders for the first Customer in the DropDownList, they must get the SelectedIndexChanged event to fire. To get that even to fire for the first customers, users will have to select some other customer, wait for the page to refresh, and then select the first customer. Your users won't be happy.

You can solve this problem by adding a dummy ListItem to the DropDownList at design time using the DropDownList's Items property (perhaps with the ListItem's Text property set to "<Select a Customer>"). You'll also need to set the DropDownList's AppendDataBoundItems property to True or this dummy item will be wiped out when the list is loaded from the database.

Hiding Key Fields
The next thing you notice is that when you put the Orders DetailsView into insert mode, you make the Order's CustomerId field available for updating. This is wrong for two reasons: First, users shouldn't be allowed to enter the CustomerId because they'll put in the wrong ID and assign the order to the wrong customer. Second, users shouldn't have to enter the CustomerId because they've already selected the customer in the DropDownList.

To handle this, in the DetailsView's Edit Fields dialog, select the CustomerId field from the list of fields being displayed and set its InsertVisible property to False. This will prevent the field from being shown in Insert mode. It's now your responsibility to update that field which you can do in the DataSource's ItemInserting event:

Protected Sub OrdersView_ItemInserting(...
   e.Values("CustomerId") = Me.CustomerList.SelectedValue
End Sub

With these changes, you'll have a page that does what you intended but also doesn't retrieve unnecessary data and prevents the user from making errors. So go ahead -- take the rest of the day off. Tell your boss that I said it was OK.

About the Author

Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.

comments powered by Disqus

Featured

Subscribe on YouTube