Export (0) Print
Expand All

A DetailsView Control for ASP.NET 1.x

 

Dino Esposito
Wintellect

October 2004

Summary: Dino Esposito builds an ASP.NET 1.x custom control that mimics the behavior and the programming interface of the ASP.NET 2.0 DetailsView control. (20 printed pages)

Download the source code for this article.

Contents

Introducing Detail Views
The Starting Point
The View Engine
Navigating Through Records
The Control's Command Bar
The Eventing Model
Putting It All Together
Conclusion
Related Books

One of the most pleasant surprises to greet developers out of the Microsoft ASP.NET 1.x box was data binding. Compared to classic Active Server Pages (ASP) support for data access, it was an extraordinary mix of simplicity and effectiveness. However, when measured against the real-world needs of developers, it yet left something to be desired. One can readily see the promise of rich and advanced functions glimmering from the API of data-bound controls like DataGrid and DataList; nevertheless, most of them require a good deal of boilerplate code to work out practical solutions.

Repeater and DataList are templated controls that build their user interface around an enumerable data source by repeating a set of templates against bound records. The DataGrid is a more off-the-shelf control and the one that most data-driven version 1.x applications use. It generates a tabular representation of data and supplies paging, sorting, and in-place editing. The extreme flexibility of the DataGrid control makes it possible for brave developers to implement a single-record view control that also offers editing capabilities like update, insert, and delete. A similar control, though, doesn't exist in the ASP.NET 1.x toolbox and, overall, building one may be a challenge.

As you probably know, ASP.NET 2.0 comes with a brand new DetailsView control that is orthogonal to the DataGrid control and its 2.0 successor—the GridView control. In this article, I'll build an ASP.NET 1.x custom control that mimics the behavior and the programming interface of the ASP.NET 2.0 DetailsView control.

Introducing Detail Views

In ASP.NET 2.0, DetailsView is a data-bound control that renders a single record at a time from its associated data source. It can optionally provide paging buttons to navigate between records, and a command bar to execute basic operations on the current record (Insert, Update, Delete). DetailsView generates a user interface similar to the Form View of a Microsoft Access database, and is typically used for updating/deleting any currently displayed record or for inserting new records. Figure 1 compares the Access and ASP.NET typical single-record views.

Aa479325.detailsview_fig01(en-us,MSDN.10).gif

Figure 1. Comparing the Form view of an Access database and the output of a DetailsView control in an ASP.NET 2.0 page

The DetailsView control is also often used in a master-details scenario where the selected record of the master control (a GridView, for example) determines the record to display in the detail view. To learn more about the pair GridView/DetailsView, you can check out the following MSDN Magazine article or, better yet, grab an ASP.NET 2.0 startup book that covers the topic. (See the Related Books section at end of the article.)

What are the key aspects of a DetailsView control? The new control needs to:

  • Be a composite control and act as a naming container.
  • Be data-bindable to enumerable data sources.
  • Support some style properties.
  • Provide a navigation bar (pager).
  • Support replaceable views of the record fields.
  • Provide a command bar for common operations.

Some of these aspects have been fully covered in other recent articles appearing on this site. In particular, I suggest you take a close look at Scott Mitchell's excellent Building DataBound Templated Custom ASP.NET Server Controls article. That article explains the nitty-gritty details of how to design and code the binding engine of a control (that is, the DataBind method), how to define a related item class for the control (that is, the DetailsViewItem object), and how to add style properties to the various elements in the layout. For brevity's sake, I'll skim over most of these topics to focus on others, more specific to the DetailsView control, like navigation bar, command bar, and related events, and the interaction with the hosting page.

Should you have the need to extend this control for your own purposes, I heartily recommend that you read the Scott's article and let his code guide you through the steps necessary to build a data-bound, templated, styled control that rocks.

The Starting Point

The DetailsView control is declared as follows:

Public Class DetailsView 
Inherits WebControl  
Implements InamingContainer

As in Figure 1, the user interface of the control consists of a HTML table with two columns—field name and field value. The table may optionally have header, footer, navigation bar, and command bar. The record can display in three ways—read-only, edit, or insert.

The control can be given as many properties as needed to fulfill the level of customization you wish. The properties listed in the table below represent the minimum set of properties to make this control usable in realistic projects.

TABLE 1. Layout properties for the DetailsView control

PropertyDescription
AllowEditIndicates whether the Edit button should be displayed in the command bar. False by default.
AllowInsertIndicates whether the Insert button should be displayed in the command bar. False by default.
AllowPagingIndicates whether the control should display the navigation bar. False by default.
CurrentModeIndicates the current working mode of the control. Feasible values come from the DetailsViewMode enum type: ReadOnly, Edit, Insert.
EmptyTextThe text to display should the data source be empty.
FooterTextThe text to display on the footer of the control.
HeaderTextThe text to display atop the control.
ViewTemplateUrl Indicates the URL for the ASCX user control to use to provide a customized view of the record. (More on this later.)

In addition to these layout properties, the DetailsView control is also assigned a few style properties, as detailed in Table 2.

TABLE 2. Style properties for the DetailsView control

PropertyDescription
AlternatingItemStyleStyle of every other item in the displayed list. For this control, a displayed item represents a field on the current record.
FooterStyleStyle of the control's footer.
HeaderStyleStyle of the control's header.
ItemStyleStyle of all items. If an alternating-item style is specified, this property affects only items with an odd index (first, third, and so on.)

All style properties represent an instance of a class derived from TableItemStyle. In this particular case, all style properties are designed to be TableItemStyle objects, because there's no need to add extra style attributes. Below is the typical implementation of a style property in a composite control. Note that _itemStyle is a control's private member that holds the actual value of the style property. The call to the style object's TrackViewState method instructs the base class (TableItemStyle) to track changes to its view state so that styles can be correctly restored from viewstate on postbacks.

<PersistenceMode(PersistenceMode.InnerProperty)> _
Public ReadOnly Property ItemStyle() As TableItemStyle
   Get
     If _itemStyle Is Nothing Then
        _itemStyle = New TableItemStyle
     End If

     If IsTrackingViewState Then
        CType(_itemStyle, IStateManager).TrackViewState()
     End If

     Return _itemStyle
   End Get
End Property

In addition to viewstate built-in support, the class TableItemStyle also provides for great design-time support nearly identical to what you get for style properties of ASP.NET native controls like the DataGrid control. The PersistenceMode attribute instructs the DetailsView control to persist the style settings (as modified by RAD designers) as an inner tag in the ASPX source.

The DetailsView control is obviously a data-bound control and features a few data-related properties. See Table 3 for details.

TABLE 3. Data-binding properties of the DetailsView control

PropertyDescription
DataSourceIndicates the data source object associated with the control. In this example, the object must either be a DataTable or a DataView object. More generally, in the setter of this property, you can check and accept types at leisure.
RecordCountRead-only property, indicates the number of records in the currently bound data source. If paging is enabled, the navigation bar in the control's user interface lets you move back and forth through records.
SelectedRecordGets and sets the 0-based index of the currently displayed record. The index relates to the size of the data source bound to the control. This property is automatically updated when users navigate through records, but can also be programmatically set. The new record displays after the control's binding is refreshed.

You can bind any number of records to the control. As you'll see later, if more than one record is bound, the control displays a navigation bar with buttons to move around.

In addition, the DetailsView control exposes a Items collection object to hold key information about the table rows that form the control's user interface. The collection is a custom collection that groups DetailsViewItem objects. The Items collection is rebuilt from the viewstate or populated from the data source depending on the nature of the request. Typically, in postback requests, the collection is restored from viewstate; otherwise, it is filled with data read out of the data source.

The DetailsViewItem class (see companion source code) is a relatively simple class with four public read-only properties: Text, Value, DataType, DefaultValue. The first three properties are strings; the last is an object. These properties are set with data coming from the viewstate or from the data source. Once set, their values are used to build the control's hierarchy.

The control's hierarchy is generated care of a protected over-ridable method of the Control base class. The method is CreateControlHierarchy and is triggered by any call made to the DataBind method of the control.

Protected Overridable Sub CreateControlHierarchy( _
       ByVal useDataSource As Boolean)
   ' Fill the Items collection from the viewstate/data source
   EnsureItemsFilled(useDataSource) 

   ' Create the outermost table
   Dim outerTable As New Table
   Controls.Add(outerTable)

   ' Create the header row
   CreateHeader(outerTable)

   ' Create all item rows
   If Items.Count = 0 Then
       CreateEmptyTable(outerTable)
   Else
       CreateView(outerTable)
   End If

   ' Create the footer row
   CreateFooter(outerTable)

   ' Create the pager bar
   If AllowPaging Then
       CreatePager(outerTable)
   End If

   ' Create the command bar
   CreateCommandBar(outerTable)
End Sub

The method first ensures that the Items collection is filled and usable; next it proceeds with the building of the control's user interface. The useDataSource parameter indicates whether the collection should be filled from the data source (true) or the viewstate (false). The control's infrastructure sets that parameter for you. You are only requested to write a custom method that reads the data source and populates the Items collection with record information. Here's an example:

Private Sub LoadItemsFromDataSource()
    Dim data As DataView = ResolveDataSource()
    If data Is Nothing Then
       Return
    End If
    If data.Count = 0 Then
       Return
    End If

    ' Select the record to display
    Dim row As DataRowView = data(SelectedRecord)
    If (row Is Nothing) Then
        Return
    End If

    ' Extract the schema from the underlying table
    Items.Clear()
    Dim index As Integer = 0
    For Each col As DataColumn In data.Table.Columns
       Dim item As New DetailsViewItem( _
                   col.ColumnName, _
                   row(index).ToString(), _
                   col.DataType.ToString(), _
                   col.DefaultValue)
       Items.Add(item)
       index += 1
    Next
    RecordCount = data.Count
End Sub

The ResolveDataSource method normalizes the data source object to a DataView object. (Bear in mind that here the data source can only be a DataView or DataTable object by design.)

The code selects the current record and fills the Items collection with as many elements as there are columns in the record. Each added element is completed with a label (column name), a string for the type, and the default value. All this data is excerpted from the table behind the DataView object.

The CreateControlHierarchy method also builds the HTML table that encapsulates the control's markup. First, it builds a new Table control and adds it to the Controls collection of the DetailsView control. Next, it starts adding rows, each of which represents a different item on the control—items, alternating items, header, footer, and so forth. All constituent items are styled just before rendering. No style information is processed while building the hierarchy of the final control. Here's a possible and likely implementation of the Render method:

Protected Overrides Sub Render(ByVal writer As HtmlTextWriter)
    PrepareControlForRendering(writer)
    MyBase.RenderContents(writer)
End Sub

The PrepareControlForRendering method applies styles to the rows of the constituent table. The following code snippet illustrates the core code behind control styling. The table is the first control in the DetailsView's Controls collection; it is applied a mix of base style attributes and user-defined attributes. The header is identified as first row in the table and is styled using the custom HeaderStyle object, if defined, or the built-in ControlStyle object.

Dim outerTable As Table = Controls(0)
outerTable.CopyBaseAttributes(Me)
If (ControlStyleCreated) Then
    outerTable.ApplyStyle(ControlStyle)
End If
:
Dim headerRow As TableRow = outerTable.Rows(0)
If Not (_headerStyle Is Nothing) Then
    headerRow.ApplyStyle(_headerStyle)
Else
    headerRow.ApplyStyle(ControlStyle)
End If

The record view provided by the DetailsView control is set up in the CreateView method.

' Create all item rows
If Items.Count = 0 Then
    CreateEmptyTable(outerTable)
Else
    CreateView(outerTable)
End If

The CreateView method is invoked if there's at least one record to display, and generates a relatively simple and spartan user interface—the left column shows the field name and right column shows the field value. This default view can be customized at will, as you'll see in a moment.

The View Engine

The CreateView method works by integrating a custom view object in the control's body or, alternatively, generates the classic layout you can see in Figure 1.

Private Sub CreateView(ByVal t As Table)
    ' Any custom view specified?
    If ViewTemplateUrl <> "" Then
         CreateCustomView(t)
         Return
    End If
    For Each item As DetailsViewItem In Items
         CreateItem(t, item)
    Next
End Sub

A custom view object is an external ASCX user control whose URL can be set through the ViewTemplateUrl property. If this property is empty or not set the method defaults to the standard rendering algorithm; otherwise, it loads and initializes the user control.

The classic user interface is generated invoking the CreateItem method for each element in the Items collection.

Private Sub CreateItem(ByVal t As Table, ByVal item As DetailsViewItem)
   Dim row As New TableRow
   t.Rows.Add(row)

   ' Add the label 
   Dim cellLabel As New TableCell
   cellLabel.Text = item.Text
   row.Cells.Add(cellLabel)

   ' Add the value 
   Dim cellValue As TableCell
   Select Case CurrentMode
      Case DetailsViewMode.ReadOnly
           cellValue = CreateItemReadOnly(item)
      Case DetailsViewMode.Edit
           cellValue = CreateItemEdit(item)
      Case DetailsViewMode.Insert
           cellValue = CreateItemInsert(item)
   End Select
   row.Cells.Add(cellValue)
End Sub

CreateItem first adds a new row to the table and then splits it in two cells—field name and value. The field name is rendered as plain text; the rendering of the field value depends on the current mode instead. If the mode is ReadOnly, it is rendered as plain text as well. If the mode is Edit, it is rendered with an editable textbox. Finally, if the control is in Insert mode, the field value changes to the item's default value and is rendered with a textbox. When in Edit or Insert mode, the control is going to update the current record or to insert a new one. The buttons to finalize these operations will be created in the control's command bar. Figure 2 offers a preview of the control in action.

Aa479325.detailsview_fig02(en-us,MSDN.10).gif

Figure 2. The DetailsView control in action

To let a custom control support a customizable view, you can choose between two possible techniques: templates or user controls. In the aforementioned article, you find the template-based technique fully demonstrated. There's not much to add—templates are common amongst server controls, even though not very practical because any change requires editing and compiling the whole page. On the other hand, templates can take advantage of the ASP.NET data-binding model and can consume the current data item object. Writing a custom control that support templates requires a little bit more attention and care, but is overall a relatively simple technique to implement with several examples available as a guide.

Integrating a user control in a custom control decouples control and view objects and allows you to modify at will the user control without affecting the page.

Dim customView As DetailsViewLayout = Page.LoadControl(ViewTemplateUrl)
If customView Is Nothing Then
     Return
End If
cell.Controls.Add(customView)

The preceding code snippet shows how to load a user control into a table's cell. But what's that DetailsViewLayout class that show up in the listing? It is a class that inherits from UserControl and represents the base class for user controls to be used within a DetailsView control. Why on earth should you use a class like this?

The user control is meant to provide an alternative view of the record fields; so you need to pass the record data down to the user control. You can temporarily park data to Cache or Session (if it is not out-of-process), and agree on the name of the entry to use. Personally, I find another approach more elegant and neat. I set the requirement that not any user control can be used as the view object of a DetailsView control; only those which inherit from DetailsViewLayout can.

Public Class DetailsViewLayout : Inherits UserControl
   ' The control receives the data through this member 
   Public Items As Hashtable

   ' Used to initialize the UI reading from Items
   Public Overridable Sub Initialize()
   End Sub
End Class

The class adds a Items property (a hashtable object) and a virtual (overridable) method named Initialize.

After inserting the user control in the hierarchy, the CreateView method prepares a hashtable and fills it with the contents of the DetailsView's Items collection. The hashtable is then associated with the Items property of the user control.

' Retrieve data using a Items("customerid") syntax
Dim displayData As New Hashtable
For Each item As DetailsViewItem In Items
     displayData.Add(item.Text, item)
Next
customView.Items = displayData
customView.Initialize()

The Initialize method on the user control will bind data to local controls. Since the hashtable is a weakly typed object, you need to cast any object in the hashtable to the DetailsViewItem class. I've chosen this approach because it is the simplest (not only) way to use an intuitive syntax like the one below.

Public Overrides Sub Initialize()
   ' Initialize local controls
   customerid.Text = CType(Items("CustomerID"), DetailsViewItem).Value
   companyname.Text = CType(Items("CompanyName"), DetailsViewItem).Value
   contactname.Text = CType(Items("ContactName"), DetailsViewItem).Value
   :
   Address.Text = sb.ToString()
End Sub

Figure 3 shows the DetailsView control with a custom view object.

Aa479325.detailsview_fig03(en-us,MSDN.10).gif

Figure 3. A user control is used to provide the view of the DetailsView control.

The following markup is needed to set the DetailsView to use a given user control.

<cc1:detailsview id="DetailsView1" runat="server" ... 
        ViewTemplateUrl="customersview.ascx">

Navigating Through Records

A DetailsView control would be almost meaningless if it lacked a navigation bar, that is an additional row with buttons to move back and forth between bound records. The navigation bar is created as part of the control's hierarchy and typically supplies two buttons: Previous and Next record.

Private Sub CreatePager(ByVal t As Table)
   ' Create the row
   Dim row As New TableRow
   t.Rows.Add(row)

   ' Add a cell
   Dim cell As New TableCell
   cell.ColumnSpan = 2 
   row.Cells.Add(cell)

   ' Add the link button to the PREVIOUS record 
   _prevRecLink = New LinkButton
   _prevRecLink.Font.Name = "webdings"
   _prevRecLink.Font.Size = FontUnit.Point(10)
   _prevRecLink.Text = "7"
   If RecordCount > 1 Then
       AddHandler _prevRecLink.Click, AddressOf GotoPrevRecord
   Else
       _prevRecLink.Enabled = False
   End If
   If SelectedRecord = 0 Then
       _prevRecLink.Enabled = False
   End If

   ' Add the link button to the NEXT record 
   _nextRecLink = New LinkButton
   _nextRecLink.Font.Name = "webdings"
   _nextRecLink.Font.Size = FontUnit.Point(10)
   _nextRecLink.Text = "8"
   If RecordCount > 1 Then
      AddHandler _nextRecLink.Click, AddressOf GotoNextRecord
   Else
      _nextRecLink.Enabled = False
   End If
   If SelectedRecord = RecordCount - 1 Then
      _nextRecLink.Enabled = False
   End If

   cell.Controls.Add(New LiteralControl("&nbsp;"))
   cell.Controls.Add(_prevRecLink)
   cell.Controls.Add(New LiteralControl("&nbsp;"))
   cell.Controls.Add(_nextRecLink)
End Sub

The navigation row spans over the two columns that form the typical output of the control and contains a couple of link buttons. Navigation buttons are disabled if the data source counts a single record. Previous and Next buttons are also individually disabled if the currently selected record is the first or the last in the bound data source.

The controls are added a click handler to actually select the next record the user can view. The listing below illustrates the code that moves the pointer back to the previous record. It is structurally identical to the code called to move the pointer ahead.

Protected Sub GotoPrevRecord(ByVal sender As Object, ByVal e As EventArgs)
    Dim pos As Integer = SelectedRecord
    If pos > 0 Then
        SelectedRecord = pos - 1
    End If
    DataBind()
End Sub

The handler works by updating the SelectedRecord property, which determines the data source 0-based index of the record to view. Next, it calls DataBind to trigger the process that ultimately updates the user interface of the control. The enabled/disabled state of the buttons is handled within the CreatePager method when the buttons are created and inserted in the hierarchy.

The Control's Command Bar

The command bar is yet another table row that includes buttons to modify the state of the currently displayed record. Typical buttons are Edit, Insert, maybe Delete, and the pair OK/Cancel used to confirm changes and insertions. Here's the code used to add the Edit button to the command bar, as in Figure 2.

' Add the EDIT button
If AllowEdit Then
   _editButton = New Button
   _editButton.Text = "Edit"
   AddHandler _editButton.Click, AddressOf OnApplyEditMode
End If

To add more flexibility, you can define a Boolean property per each command button you plan to support (like AllowEdit) to instruct the control to display the XXX command button or not.

Clicking on Edit and Insert buttons is resolved by changing the control's working mode expressed by the CurrentMode property. In Edit mode, the control displays with textboxes for you to modify values; in Insert mode, it displays with blank textboxes or shows default values if any. In both Edit and Insert mode, the command bar hides Edit and Insert buttons and replaces them with OK and Cancel buttons, as in Figure 4 below.

Aa479325.detailsview_fig04(en-us,MSDN.10).gif

Figure 4. The DetailsView control in Edit mode

How should the control respond to a user clicking on OK and Cancel buttons? When a user click on the Cancel button, you could simply restore the ReadOnly working mode, hide OK/Cancel buttons, and return.

Protected Sub CancelClicked(ByVal sender As Object, ByVal e As EventArgs)
   ' Refresh the UI
   SetCurrentMode(DetailsViewMode.ReadOnly)
   ShowSaveButton()
   DataBind()
End Sub

What about the OK button? How do you save changes?

In ASP.NET 1.x, rich controls like the DataGrid that implement update functions work by firing events to the host environment. The DataGrid, for instance, doesn't include any relevant code to update the record being edited. It limits to refresh the user interface (that is, exits edit mode) and fires an event. The host page will then catch the event, retrieve data, and prepare the update statements. This is also the way in which the DetailsView control works here.

It is worth noting that in ASP.NET 2.0, data-bound controls normally connect themselves to external components, known as data source objects, to perform updates and other data-related operations. Data source objects implement a well-known interface, meaning that controls can invoke them transparently with no need to fire events to the host page. That's the way the ASP.NET 2.0 DetailsView control works.

The Eventing Model

In line with other 1.x data-bound controls, this implementation of the DetailsView control fires custom events to the page as the user clicks the OK and the Cancel button. The events are UpdateCommand and CancelCommand defined as below.

Public Delegate Sub DetailsViewUpdateEventHandler( _
     ByVal sender As Object, _
     ByVal e As DetailsViewUpdateEventArgs)
Public Event UpdateCommand As DetailsViewUpdateEventHandler
Public Event CancelCommand As DetailsViewUpdateEventHandler

The DetailsViewUpdateEventArgs class extends the base event data structure with a few new properties. CurrentMode indicates the working mode of the control when the user clicked; NewMode returns the working mode to be set after the event processing completes. Finally, DataItems contains the input values of the form to be used for update purposes. (More on this later.)

Public Class DetailsViewUpdateEventArgs : Inherits EventArgs
   Public CurrentMode As DetailsViewMode   
   Public NewMode As DetailsViewMode  
   Public DataItems As HybridDictionary     
End Class

In this way, you give a runtime a chance to decide whether to continue updating or just quit after saving. In light of these enhancements, the click handler of the OK button looks like this:

Protected Sub OkClicked(ByVal sender As Object, ByVal e As EventArgs)
   ' Fire event UpdateCommand with info about INS/EDIT somewhere
   Dim args As New DetailsViewUpdateEventArgs
   args.CurrentMode = CurrentMode
   args.NewMode = DetailsViewMode.ReadOnly
   RaiseEvent UpdateCommand(Me, args)

   ' Refresh the UI
   SetCurrentMode(args.NewMode)
   ShowSaveButton()
   DataBind()
End Sub

By default, the NewMode property is set to ReadOnly; this setting can be changed dynamically to continue the update.

Yet one (significant) problem is still unsolved: How would you pass the current modified values back to the page event handler? ASP.NET 1.x controls resort to a workaround that proves a little bit annoying in most situations. Controls like the DataGrid require the page developer to navigate the grid's hierarchy of constituent controls to locate by name or position the input field of interest. Although boring to code, this approach is still acceptable for controls—like the DataGrid—that show one record per row. It is far less acceptable for a DetailsView control called to show a field per row. DataGrid-related event handlers receive from the parent control an object that represents the current row—the DataGridItem object. By using the FindControl method on this object, you can find your way to the input data.

As implemented here, the DetailsView control doesn't have an equivalent item object that brings along a piece of the user interface. (The control does use an "item" class but that is limited to pack item-specific data. Again, see Building DataBound Templated Custom ASP.NET Server Controls for a full demonstration of item objects and events.)

What's up with the DetailsView control of ASP.NET 2.0?

As mentioned, the control delegates data-related operations to a separate component—the data source object. In doing so, though, it passes input data packed in a dictionary. As of Beta 1 of ASP.NET 2.0, any input data is collected navigating the hierarchy of constituent control in a way that is largely similar to 1.x DataGrids.

The DetailsView control presented here is architecturally simpler but not less effective. Whenever a textbox is created to edit/insert record fields, it is given the name of the field as an ID. Add this to the fact that the DetailsView is a naming container, and you'll get the client ID of each textbox (or any other control you plan to use). The ID has the format XXX:YYY, where XXX is the ID of the DetailsView control and YYY is the field name. How do you retrieve the value of this control? Through the old acquaintance Request.Form collection.

Dim values As New HybridDictionary(True)
For Each item As DetailsViewItem In Items
     Dim inputID As String = String.Format("{0}:{1}", ID, item.Text)
     values.Add(item.Text, Page.Request.Form(inputID))
Next
Dim args As New DetailsViewUpdateEventArgs
args.CurrentMode = CurrentMode
args.NewMode = DetailsViewMode.ReadOnly
args.DataItems = values
RaiseEvent UpdateCommand(Me, args)

The values are packed in a HybridDictionary object to gain maximum performance. The Boolean parameter in the constructor indicates that keys are case-insensitive.

Putting It All Together

Let's write a page that provides a master/detail view of the Customers table in the Northwind database using a DataGrid and a DetailsView control. The page is shown in Figure 5 below.

Aa479325.detailsview_fig05(en-us,MSDN.10).gif

Figure 5. A sample page to demonstrate master/details view

The following code snippet illustrates a possible handler for the UpdateCommand event fired when the user clicks to save changes made to a record. For simplicity, the handler saves only the Country field, but the mechanism shown can easily be extended to any other field.

Private Sub DetailsView1_UpdateCommand( _
    ByVal sender As System.Object, _
    ByVal e As Expoware.DetailsViewUpdateEventArgs) _
Handles DetailsView1.UpdateCommand
    ' Prepare the update statement (only field Country)
    Dim cmdText As String = "UPDATE customers SET country=@country " + _
                            "WHERE customerid=@customerid"
    Dim conn As New SqlConnection("...")
    Dim cmd As New SqlCommand(cmdText, conn)
    cmd.Parameters.Add("@country", e.DataItems("country"))
    cmd.Parameters.Add("@customerid", e.DataItems("customerid"))

    ' Execute the statement
    cmd.Connection.Open()
    cmd.ExecuteNonQuery()
    cmd.Connection.Close()

    ' Continue update
    e.NewMode = e.CurrentMode

    ' Reload and refresh data
    BindData()
End Sub

Note the use of the DataItems collection to retrieve field values as the user edited them in the form.

Conclusion

The DetailsView control is a hot new entry in ASP.NET 2.0 and a useful addition to many ASP.NET 1.x applications. I've tried to design and code the control to adhere as much as possible to the 2.0 implementation and follow 1.x best practices for data-bound controls.

The control can be bound to an enumerable data source and provides a single-record view with optional navigation and command bars. The command bar supports classic commands, like Edit and Insert, for you to edit existing records and add new ones. The control's user interface is rendered in a default way, but can be fully replaced using a user control. The control doesn't support templates. Note, though, that not even the ASP.NET 2.0 DetailsView supports templates. In ASP.NET 2.0, you should resort to the FormView control to get templates.

The eventing model overall is simpler than that of many other data-bound controls. In particular, it doesn't take advantage of row-item objects and doesn't provide a centralized console for bubbling events up the hierarchy of constituent controls. The simpler design, though, doesn't make the control itself less effective. Retrieval of input data to pass to update handlers is direct and all the data is passed ready-to-use to the handlers.

Download the control and don't let your feedback lack!

Related Books

ASP.NET v. 2.0-The Beta Version

ASP.NET 2.0 Beta Preview

Introducing Microsoft ASP.NET 2.0

 

About the author

Dino Esposito is a Wintellect instructor and consultant based in Italy. Author of Programming ASP.NET and the new Introducing ASP.NET 2.0 (both from Microsoft Press), he spends most of his time teaching classes on ASP.NET and ADO.NET and speaking at conferences. Join the discussions at Dino's blog at http://weblogs.asp.net/despos.

Show:
© 2014 Microsoft