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/.