GridView

Move Over DataGrid, There's a New Grid in Town!

Dino Esposito

This article is based on the May 2004 Technology Preview of ASP.NET 2.0. All information herein is subject to change.

This article discusses:

  • The ASP.NET 2.0 GridView, FormView, and DetailsView
  • Differences between the DataGrid and the GridView
  • The programming interfaces of these controls
  • How to achieve master/detail views
This article uses the following technologies:
ASP.NET, ASP.NET 2.0, C#

Code download available at:GridView.exe(124 KB)

Contents

GridView Versus DataGrid
GridView and Data Source Controls
The GridView Object Model
GridView Events
Displaying, Sorting, and Paging
Editing Data
The DetailsView Control
The FormView Control
Conclusion

Despite the richness and versatility of its programming interface, the ASP.NET 1.x DataGrid control requires you to write a lot of custom code to handle common operations such as paging, sorting, editing, and deleting data. For example, while the DataGrid control can raise events when the user clicks to save or cancel changes, it doesn't offer much more than that. If you want to store changes to a persistent medium, such as a database, you have to handle the UpdateCommand event yourself, retrieve changed values, prepare a SQL command, and then proceed from there to commit the update.

The reason the DataGrid control limits the raising of events for common data operations is that it's a data source-agnostic control that can be bound to any data object that is enumerable. Implementing data operations such as update or delete would require a direct link with one particular data source. In ASP.NET 1.x, you work around this limitation by writing ADO.NET code that is specific to your application.

ASP.NET 2.0 enhances the data-binding architecture, introducing a new family of components—the data source objects—which act as a bridge between data-bound controls and ADO.NET objects. These source objects promote a slightly different programming model and provide for new features and members. For data reporting purposes, your ASP.NET 2.0 applications should use the newest grid control—the GridView. The familiar DataGrid control is still supported, but it doesn't take full advantage of the specific capabilities of data source components.

The GridView control is the successor to the DataGrid and extends it in a number of ways. First, it fully supports data source components and can automatically handle data operations, such as paging, sorting, and editing, provided its bound data source object supports these capabilities. In addition, the GridView control offers some functional improvements over the DataGrid. In particular, it supports multiple primary key fields and exposes some user interface enhancements and a new model for handling and canceling events.

The GridView comes with a pair of complementary view controls: DetailsView and FormView. By combining these controls, you can easily set up master/detail views using very little code and sometimes no code at all.

GridView Versus DataGrid

In ASP.NET 2.0, the class hierarchy of data-bound controls is more consistent than in ASP.NET 1.x. In version 2.0, all controls derive from the same base class (the BaseDataBoundControl class), regardless of the actual implementation or user interface characteristics. Figure 1 shows the new class diagram. The DataGrid and other version 1.x controls like Repeater and DataList are not included in the diagram. The inheritance tree for these existing controls is the same as ASP.NET 1.x. In particular, the Repeater inherits WebControl, whereas DataList and DataGrid inherit BaseDataList. As you can see in Figure 1, GridView is a composite data-bound control and shares a common set of methods and properties with all other data-bound controls, including DropDownList, DetailsView, and ListBox.

Figure 1 ASP.NET Class Diagram

Figure 1** ASP.NET Class Diagram **

Although similar in their high-level functionality, the GridView and the DataGrid controls have a different foundation. To enable easy migration from existing pages, the GridView preserves the DataGrid object model as much as possible. However, you shouldn't expect 100 percent compatibility between DataGrid-based code and new GridView-based code.

Another key difference between the DataGrid and GridView controls lies in the adaptive user interface. Unlike the version 1.x DataGrid, the GridView can render on mobile devices, too. In other words, to build reports on mobile devices, you can use the same grid control you would use for desktop pages. The DataGrid in version 2.0 can also render adaptively, but its UI capabilities are not quite as rich as those of the GridView.

In ASP.NET 2.0, the DataGrid control has been enhanced to support common control features such as themes and personalization. In addition, the new DataGrid control can be populated by a data source control. Remember, though, that a DataGrid can only bind to a data source object for the purpose of reading data. To actually modify the underlying data source, some user-defined code is still required. In contrast, the GridView control takes advantage of the capabilities of the underlying data source and automatically deletes or updates records. Note that the GridView control also supports the classic binding mechanism based on the DataSource property and the DataBind method. Although fully supported, this programming practice is discouraged.

GridView and Data Source Controls

So, what's a data source control? I covered this hot new feature of ASP.NET 2.0 in great detail in the June 2004 issue of MSDN®Magazine. In summary, a data source control is a set of Microsoft® .NET Framework classes that facilitate two-way binding between data stores and data-bound controls. Existing controls like the DataGrid and new data-bound controls like the GridView can be bound to a data source, albeit with different capabilities.

A data source control represents the key functions of a data source: select, insert, update, and delete. Data source controls can represent any data source from relational databases to XML files, from streams to business objects. If this brief description makes you think of .NET managed providers, take a look at Figure 2.

Figure 2 Data Source Controls, GridView, and Data Sources

Figure 2** Data Source Controls, GridView, and Data Sources **

Data source controls may sit on top of some .NET data providers and form an intermediate layer between data-bound controls and data sources. Data source controls also expose a common interface for a few basic operations. Some of the data-bound controls—specifically, the GridView control—bind to these commands to automate in-place editing along with other data-related operations.

A data source control uses its properties and methods to expose the bound content as a set of named views. The IDataSource interface that all data source controls implement provides the basic set of capabilities to retrieve views of data from a data source. ASP.NET 2.0 supplies a handful of built-in data source controls, as listed in Figure 3. The data source controls in Figure 3 belong to two categories: tabular and hierarchical components. The SiteMapDataSource and XmlDataSource components are hierarchical data source controls and can be used by hierarchical components like the TreeView and Menu control. The various other components manage tabular data.

Figure 3 ASP.NET 2.0 Data Source Controls

Tabular Controls Description
AccessDataSource Represents a connection to an Access database. Inherits from the SqlDataSource control but ignores the ConnectionString and ProviderName properties in favor of a simpler DataFile property that will point to the MDB file. The control uses the Jet 4.0 OLE DB provider to connect to the database. If required for the MDB file, you can specify a user name and password. A ShareMode property allows you to specify whether the MDB file is read-only or read/write.
DataSetDataSource Works with the XML representation of a DataSet object. The XML data can be specified as a string or through a file name. You can't bind this control to a DataSet object, not even programmatically. The class features methods to retrieve the corresponding DataSet object and set schema information. Although XML-based, the control supports only the tabular interface and can only be bound to list controls. Mainly used to display XML data in read-only scenarios, the control also supports editing of the underlying XML data.
ObjectDataSource Allows binding to a custom .NET business object that returns data. The class is specified by name through the TypeName property. The control allows developers to structure applications using a three-tier architecture and still take advantage of the ASP.NET 2.0 declarative data-binding model. The class is expected to follow a specific design pattern and include, for example, a parameterless constructor and methods with a well-known behavior.
SqlDataSource Represents a connection to an ADO.NET data provider that returns SQL data, including data sources accessible through OLE DB and ODBC. The name of the provider and the connection string are specified through properties. Do not use this class to connect to an Access database.
Hierarchical Controls Description
SiteMapDataSource Gets data from an ASP.NET 2.0 sitemap source. The actual source depends on the site provider that the application uses. In the default case, the data source is an XML file with a .sitemap extension. The control pumps site map information into hierarchical data-bound controls such as the new TreeView control.
XmlDataSource Loads XML input data from a file, URL, or string containing the XML content. If the XML data does not also contain schema information, the XML schema may be specified as a separate file or string. The control can be used to populate hierarchical controls such as TreeView or Menu.

The code in Figure 4 shows a sample page that binds a GridView and a DataGrid to the same data source control. This is the recommended data-binding approach in ASP.NET 2.0. The SqlDataSource control is characterized by a ConnectionString property plus any combination of SelectCommand, UpdateCommand, InsertCommand, and DeleteCommand properties. All of them are string properties and reference a command text, optionally with parameters:

<asp:SqlDataSource runat="server" ID="MySource" ConnectionString="SERVER=(local); DATABASE=northwind;Integrated Security=SSPI;" SelectCommand="SELECT * FROM employees WHERE employeeid > @MinID"> <SelectParameters> <asp:ControlParameter Name="MinID" ControlId="EmpID" PropertyName="Text" /> </SelectParameters> </asp:SqlDataSource>

Figure 4 Binding GridView and DataGrid

<%@ Page theme="SmokeAndGlass" %> <html> <head runat="server" /> <body> <form runat="server"> <asp:TextBox runat="server" ID="EmpID" Text="3" /> <asp:button runat="server" Text="Refresh" /> <asp:SqlDataSource runat="server" ID="MySource" ConnectionString="SERVER=(local);DATABASE=northwind;Integrated Security=SSPI;" DataSourceMode="DataSet" SelectCommand="SELECT firstname, lastname FROM employees WHERE employeeid > @MinID"> <SelectParameters> <asp:ControlParameter Name="MinID" ControlId="EmpID" PropertyName="Text" /> </SelectParameters> </asp:SqlDataSource> <asp:GridView runat="server" ID="grid" DataSourceId="MySource" AutoGenerateColumns="true"> </asp:GridView> </form> </body> </html>

A data source control is characterized by a unique ID. This ID is the link that ties data-bound and data source controls together. You bind a GridView to a data source control using the DataSourceId property. For example, whenever the grid needs to fetch data, the SelectCommand SQL command of the associated SQLDataSource control executes. When the grid needs to update or delete a record, the corresponding UpdateCommand or DeleteCommand SQL commands execute. If no such commands exist, an exception is thrown. Internally, the GridView raises events like the DataGrid version 1.x does when the user deletes or updates a record. Unlike the DataGrid, though, the GridView defines internal handlers for these events. The default handlers retrieve the commands defined on the bound data source to handle these operations and execute them. As Figure 4 demonstrates, no code is needed behind the markup to keep the grid displaying or updating data. In more complex and sophisticated scenarios, you may need to write some code.

Figure 5 GridView and DataGrid

Figure 5** GridView and DataGrid **

In general, though, data source controls and the GridView control provide for codeless data binding. Figure 5 shows the output generated by the code shown in Figure 4.

In ASP.NET 2.0, the DataGrid exposes the DataSourceId property in addition to DataSource and DataMember. The DataSourceId property connects the DataGrid to a valid data source object defined on the same page. However, the DataGrid doesn't provide the same level of automation the GridView provides. When the user clicks to update a record, the DataGrid raises the UpdateCommand event, whereas the GridView retrieves the data source update command and runs it in addition to raising the Updating and Updated events to allow the user to customize the information sent to the data source control.

The GridView Object Model

The overall structure of a GridView looks similar to that of a DataGrid. Some of the common elements have been renamed and some of the common features now require a different syntax. All in all, though, if you're familiar with the DataGrid control, you'll immediately feel at home with the GridView. Figure 6 details the new elements that form a GridView (note that some of them, like DetailLinkStyle, are available only when the grid is rendered to mobile devices). Row elements are exposed through instances of the GridViewRow class gathered in the Rows collection. The GridViewRow class maps to the DataGridItem class whereas Rows clearly replaces the DataGrid's Items collection. Represented by the DataControlRowType enumeration, the type of the row indicates the position and the role (for example, footer, header, pager, and data row). The GridView also introduces a new concept—the state of a row element. This row state is represented by the values in the DataControlRowState flags enumeration—Normal, Edit, Alternate, Insert, and Selected. Interestingly enough, these two enum types happen to be shared by all of the data view controls—GridView, DetailsView, and FormView.

Figure 6 GridView Properties

Style Property Description
AlternatingRowStyle Every other row displayed in the table
DetailLinkStyle Links in the details view of the control when rendered on mobile devices
DetailTitleStyle Title in the details view of the control when rendered on mobile devices
EditRowStyle Row being edited
FooterStyle Grid's footer
HeaderStyle Grid's header
EmptyDataRowStyle Default row rendered when the GridView is bound to an empty data source; can be templatized using the EmptyDataTemplate property
PagerStyle Grid's pager; can be templatized using the PagerTemplate property
RowStyle Single row displayed in the table
SelectedRowStyle Row currently selected
SummaryTitleStyle Title in the summary view of the control when rendered on mobile devices

Aside from the elements introduced to comply with adaptive rendering, the GridView has only one other new element—the empty data row. When bound to an empty data source, the GridView can optionally display some default content to give feedback to the user. What is displayed on this occasion depends on the contents of the new empty data row element. You can set this row's content using either a property (EmptyDataText) or a template (EmptyDataTemplate).

The properties of the GridView control fall into three main categories: behavior, visual settings, and state. Figure 7 lists some of the GridView properties. Take a look at the new properties including EnableSortingAndPagingCallbacks, EmptyDataText, and UseAccessibleHeader, plus properties that implement functions already supported by the DataGrid that have been renamed or reworked.

Figure 7 GridView Properties

Property Description
AutoGenerateColumns Indicates whether bound fields are automatically created for each field in the data source.
AutoGenerateDeleteButton Indicates whether the control includes a button column to let users delete the record mapped to the clicked row.
AutoGenerateEditButton Indicates whether the control includes a button column to let users edit the record mapped to the clicked row.
AutoGenerateSelectButton Indicates whether the control includes a button column to let users select the record mapped to the clicked row.
BottomPagerRow Returns a GridViewRow object that represents the bottom pager of the grid.
Columns Gets a collection of objects that represent the columns in the grid. Note that this collection is always empty if columns are autogenerated.
DataKeyNames Gets and sets an array that contains the names of the primary key fields for the currently displayed items. Extends and replaces DataKeyField.
DataKeys Gets a collection of DataKey objects that represent the values of the primary key fields set in DataKeyNames for the currently displayed records.
DetailNextRowText Indicates the text for the link to the next row in the details view on mobile devices.
DetailPreviousRowText Indicates the text for the link to the previous row in the details view on mobile devices.
DetailSummaryText Indicates the text for the link to the summary view when the control is rendering on a mobile device.
EmptyDataText Indicates the text to render in the control when bound to an empty data source.
EnableSortingAndPagingCallbacks Indicates whether paging and sorting should be accomplished using script callback functions. Disabled by default.
PagerSettings Gets a reference to the PagerSettings object that lets you set the properties of the pager buttons. The PagerSettings object groups all the pager-specific properties.
Rows Gets a collection of GridViewRow objects that represent the data rows currently displayed in the control. Replaces the Items property of the DataGrid.
SelectedDataKey Returns the DataKey object for the currently selected record.
SelectedRow Returns a GridViewRow object that represents the currently selected row. Replaces the SelectedItem property of the DataGrid.
SelectedValue Similar to SelectedDataKey; returns the explicit value of the key as stored in the DataKey object.
SortDirection Read-only property; gets the direction of the column current sort.
SortExpression Read-only property; gets the current sort expression.
SummaryViewColumn Indicates the name of the data column used to provide the summary of the record when the control renders on mobile devices.
TopPagerRow Returns a GridViewRow object that represents the top pager of the grid.
UseAccessibleHeader Determines whether to render <TH> tags for the column headers instead of default <TD> tags.

The programming model is a bit different for button columns. In an ASP.NET 1.x DataGrid, you had to add a special type of column to create an Edit button—EditCommandColumn. To create a Delete or a Select button column, you had to add a generic button column and give it a predefined command name. The GridView object model is more consistent and simpler. It is based on three new Boolean properties: AutoGenerateEditButton, AutoGenerateDeleteButton, and AutoGenerateSelectButton. When any of these properties are set to true, an Edit, Delete, or Select command button column is added to the grid, respectively. For example, when the AutoGenerateEditButton property is set to true, a column with an Edit button for each data row is automatically added to the grid. These buttons can also be added manually by adding a CommandField object to your set of columns. The Columns property lists the column objects much like the Columns property of the DataGrid does. A few helper properties have also been added based on customer feedback. In particular, you can now store multiple key values for each displayed row. Basically, the DataGrid's DataKeyField string property has been expanded to an array of field names. The new property is named DataKeyNames and stores an array of strings made up of the field names that uniquely identify a data row. DataKeys is the corresponding array of values for a particular row. It returns a collection of DataKey objects. Each DataKey object contains the values for each key name, and there are as many DataKey objects in DataKeys as there are visible rows of the GridView.

SortDirection and SortExpression track the current sorting of the grid. These properties are used internally to implement auto-reverse sorting and to enhance the grid with a glyph that indicates the current sort order. PagerSettings groups in a single object all the properties that configure the user interface, behavior, and position of the pager. The pager now supports a navigation style that also includes first and last rows along with the next and previous rows.

The GridView control can also use a lighter-weight, callback-based mechanism for sorting and paging. You turn this feature on and off by setting the EnableSortingAndPagingCallbacks Boolean property. When a sorting or paging link is clicked and callbacks are enabled, the GridView requests the sorted data or next page with no observable page postback. (A round-trip happens, but there is no page refresh so you don't notice.) Note that this feature comes with a caveat: when selection is enabled in the GridView, the currently selected index is maintained on the new page. If there's an associated details page, the selection appears out of sync. Handling events like PageIndexChanging doesn't help because these events are never fired if callbacks are enabled. Finally, you should keep in mind that the callback-driven paging and sorting mechanism requires Microsoft Internet Explorer 5.0 or higher.

GridView Events

The GridView control doesn't feature methods other than the well-known DataBind method. In ASP.NET 2.0, many controls, and the Page class itself, feature pairs of Pre-load/Post-load events. Key operations in the control lifecycle are wrapped by a pair of events, one firing before the operation takes place and one firing immediately after the operation has completed. This is true of the GridView class as well. The list of new events is shown in Figure 8. Events that announce operations significantly enhance your programming power. For example, by hooking the RowUpdating event you can check what is being updated against the new values. You might want to handle the RowUpdating event by HTML-encoding the values supplied by the client before they are persisted to the underlying data store. This simple trick helps you fend off malicious script injections.

Figure 8 GridView Events

Event Description
PageIndexChanging Both events fire when one of the
and PageIndexChanged pager buttons is clicked. They fire before and after the grid control handles the paging operation.
RowCancellingEdit Fires when the Cancel button of a row in edit mode is clicked, but before the row exits edit mode.
RowCommand Fires when any button in a button column is clicked. Replaces the DataGrid's ItemCommand event.
RowCreated Fires when a row is created. Replaces the DataGrid's ItemCreated event.
RowDataBound Fires when a data row is bound to data. Replaces the DataGrid's ItemDataBound event.
RowDeleting and Both events fire when a row's
RowDeleted Delete button is clicked. They fire before and after the grid control deletes the row.
RowEditing Fires when a row's Edit button is clicked, but before the control enters edit mode.
RowUpdating and Both events fire when a row's
RowUpdated Update button is clicked. They fire before and after the grid control updates the row.
SelectedIndexChanging Both events fire when a row's
and SelectedIndexChanged Select button is clicked. The two events occur before and after the grid control handles the select operation.
Sorting and Sorted Both events fire when the hyperlink to sort a column is clicked. They fire before and after the grid control handles the sort operation.

The availability of a pre/post pair of events lets you cancel an ongoing event based on runtime conditions. Take a look at the following code snippet:

void PageIndexChanging(object sender, GridViewPageEventArgs e) { // Is this the sensitive page? (> 4) bool isSensitivePage = (e.NewPageIndex > 4); if (isSensitivePage && (User.Identity.Name != "username")) e.Cancel = true; return; }

Cancel is a read/write Boolean property present in all event argument classes derived from CancelEventArgs. Many of the GridView's event argument classes inherit CancelEventArgs, meaning that all those events can be canceled. The value of the Cancel property is always set to false when the "pre" event fires. By handling the event, you can check some conditions and optionally cancel the event by setting the Cancel property to true. For example, the code snippet just shown cancels the transition to the new page if the current user is not authorized to view pages with an index greater than 4.

Displaying, Sorting, and Paging

A grid is often used to display the results of a database query. This is easier than ever with the GridView control. You set up a data source object, provide the connection string and query text, and assign the ID of the data source to the DataSourceId property of the GridView. At run time, the GridView automatically binds to the source and generates appropriate columns of data. By default, all the columns in the query are displayed in the grid.

Like the DataGrid control, the GridView supports custom column fields through the Columns collection. If you want to display only a subset of the retrieved data fields, or if you just want to customize their appearance, you can populate the Columns collection with objects that represent columns of data to display. The GridView supports a variety of column types, including the new checkbox and image column types:

<columns> <asp:boundfield datafield="productname" headertext="Product" /> <asp:checkboxfield datafield="discontinued" headertext="Discontinued" /> <asp:buttonfield buttontype="Button" text="Buy" /> <asp:hyperlinkfield text="More Info..." datanavigateurlfields="productid,discontinued" datanavigateurlformatstring="more.aspx?id={0}&disc={1}" /> </columns>

Figure 9 shows a grid in action configured to use the fields listed in this code. The names of the GridView column classes differ slightly from the names of corresponding classes in the DataGrid interface. Basically, the suffix "column" is replaced with the suffix "field." Aside from this name change, the behavior of matching column classes is nearly identical. Some of the new column types save you from continually using templates. For example, the CheckBoxField column renders the specified data field using a checkbox, while the enhanced HyperLinkField column provides a long-awaited feature—support for multiple URL parameters. As in the code snippet just shown, the DataNavigateUrlFields property accepts a comma-separated list of field names and merges them with the text of the DataNavigateUrlFormatString property.

Figure 9 GridView with Fields in Action

Figure 9** GridView with Fields in Action **

Note the difference between ButtonField and CommandField. Both columns add a button to the user interface of the grid, but a CommandField displays command buttons to perform select, edit, insert, or delete operations. A ButtonField simply represents a field that is displayed as a button. Finally, the GridView can embed images through the ImageField column type.

<asp:imagefield datafield="photo" headertext="Picture" />

Figure 10 shows the ImageField column in action on the photo field of the Northwind Employees table. It is interesting to note that ImageField uses the ASP.NET 2.0 DynamicImage control to display images from both databases and URLs. Furthermore, when in edit mode the ImageField column pops up a Browse button so you can locate a new file on the local machine to upload.

Figure 10 Image Field Column

Figure 10** Image Field Column **

Template columns are supported as well and require syntax similar to that of a DataGrid in ASP.NET 1.x:

<asp:templatefield headertext="Product"> <itemtemplate> <b><%# Eval("productname")%></b> <br /> available in <%# Eval("quantityperunit")%> </itemtemplate> </asp:templatefield>

It is interesting to note the more compact syntax for data-bound expressions that is permitted in ASP.NET 2.0. To build a templated content in ASP.NET 1.x, you had to use the following expression:

DataBinder.Eval(Container.DataItem, "fieldname")

Thanks to a smarter data-binding mechanism, you can now avoid using the static Eval method on the DataBinder class. Instead, you now call a new protected method named Eval that's defined on the Page class. You pass Eval the name of the field to evaluate and the method, in turn, determines the current data item and prepares a regular call through DataBinder.Eval.

Eval is declared as a protected method on the TemplateControl class from which both Page and UserControl derive. The actual class that represents an .aspx page in action is an instance of a class that derives from Page; hence, it can call into protected methods. The same holds true for ASCX user controls.

As long as the focus is on plain data display, it's hard to imagine the need for a brand new grid control like the GridView. Sure, you can now bind data source controls to the GridView with little or no code, but does this necessitate a replacement for the DataGrid? If it doesn't, consider sorting and paging.

In the GridView control, autoreverse sorting and paging are enabled simply by turning on the AllowPaging and AllowSorting properties. If you have ever tried this in ASP.NET 1.x, you're beginning to get the picture.

Figure 11 Pageable Sortable Grid in Action

Figure 11** Pageable Sortable Grid in Action **

Figure 11 shows a pageable and sortable grid. The complete code for this grid is shown in Figure 12. (It is worth noting that the C# code is needed only if you want to add the glyph to the column header to indicate the sort direction.) So, sorting and paging work quite well without writing any code of your own. You control the data retrieval mode of a SQLDataSource through the DataSourceMode property. Feasible value types are DataSet (default) and DataReader. Data source controls may optionally cache the results of the SELECT command all the time when the DataSourceMode is DataSet. This enables rich GridView scenarios in which the control can offer codeless sorting, filtering, and paging capabilities. Caching is disabled by default and therefore must be enabled on the data source control.

Figure 12 Pageable and Sortable Grid

<%@ page language="C#" theme="SmokeAndGlass" %> <script runat="server"> void AddGlyph(GridView grid, GridViewRow item) { Label glyph = new Label (); glyph.EnableTheming = false; glyph.Font.Name = "webdings"; glyph.Font.Size = FontUnit.XSmall; glyph.Text = (grid.SortDirection==SortDirection.Ascending ?" 5" :" 6"); // Find the column you sorted by for(int i=0; i<grid.Columns.Count; i++) { string colExpr = grid.Columns[i].SortExpression; if (colExpr != "" && colExpr == grid.SortExpression) { item.Cells[i].Controls.Add (glyph); } } } void MyGridView_RowCreated (object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.Header) AddGlyph (MyGridView, e.Row); } </script> <html> <head runat="server"> <title>GridView</title> </head> <body> <form runat="server"> <asp:sqldatasource runat="server" id="MySource" connectionstring="SERVER=(local);DATABASE=northwind;Integrated Security=SSPI;" datasourcemode="DataSet" selectcommand="SELECT * FROM products"> </asp:sqldatasource> <asp:gridview runat="server" id="MyGridView" datasourceid="MySource" allowpaging="true" autogeneratecolumns="false" allowsorting="true" onrowcreated="MyGridView_RowCreated" > <pagersettings firstpagetext="7" lastpagetext="8" nextpagetext="4" prevpagetext="3" mode= "NextPreviousFirstLast" /> <pagerstyle font-name="webdings" /> <columns> <asp:boundfield datafield="productname" headertext="Product" sortexpression="productname" /> <asp:boundfield datafield="quantityperunit" headertext="Packaging" /> </columns> </asp:gridview> </form> </body> </html>

Caching data in memory can dramatically increase performance, but remember that the data is then somewhat vulnerable. You'll have to decide if the trade-off is worth it because the Cache object automatically discards least-used data if the system runs low on memory. In addition, in ASP.NET 2.0 the SQLDataSource control may optionally establish an automatic dependency with the database so that any change is promptly detected. This ensures that fresh data is always displayed. For more information on the features of data source controls, see my June 2004 article that I mentioned earlier. When the SQLDataSource control retrieval mode is DataReader, data is retrieved using an IDataReader object, which is a forward-only, read-only firehose-style cursor.

Editing Data

One of the most important shortcomings of the DataGrid—and conversely one of the major strengths of the GridView control—is the ability to of handle updates to the data source. When the bound data source supports updates, the GridView can automatically perform data operations, thus providing a real solution right out of the box. The data source control reveals these abilities through a number of Boolean properties such as CanUpdate, CanDelete, CanSort, and so forth.

For a GridView control, editing data means in-place editing and record deletion. As mentioned, in-place editing refers to the grid's ability to support changes to the currently displayed records. To enable in-place editing on a GridView, you turn on the AutoGenerateEditButton Boolean property:

<asp:gridview runat="server" id="MyGridView" datasourceid="MySource" autogenerateeditbutton="true"> ••• </asp:gridview>

When the AutoGenerateEditButton property is set to true, the GridView displays an additional column that looks like the leftmost column in Figure 13. Clicking the Edit button for a row puts that row in edit mode. When a row is in edit mode, each bound field in the row that is not read-only displays the appropriate input control, typically a TextBox. When you click to update, the GridView raises the RowUpdating event and checks the CanUpdate property on the data source. If CanUpdate returns false, an exception is thrown. Otherwise, a command object is created and configured after the UpdateCommand property of the data source object.

Figure 13 Edit Column in GridView

Figure 13** Edit Column in GridView **

Your involvement with SQL is limited to defining the structure of the command—you just define statements and the control does the rest. There's no need to work with ADO.NET or worry about commands or connections. To persist changes when the user clicks Update, write code like this:

<asp:sqldatasource runat="server" id="MySource" connectionstring="SERVER=...;DATABASE=northwind;Integrated Security=SSPI;" updatecommand="UPDATE employees SET firstname=@firstname, lastname=@lastname WHERE employeeid=@employeeid"> </asp:sqldatasource> <asp:gridview runat="server" id="MyGridView" DataSourceId="MySource" DataKeyNames="employeeid" AutoGenerateEditButton="true"> ••• </asp:gridview>

The UpdateCommand attribute of the data source is set to the SQL command that the GridView will use. You can use as many parameters as needed. If you take the time to use a particular naming convention, parameter values are automatically resolved. Parameters that represent fields to update, such as firstname, must match the name of DataField property of a grid column. The parameter used in the WHERE clause to identify the working record must match the DataKeyNames property, which is the key field for the displayed records. Finally, consider that if UpdateCommand isn't defined, CanUpdate returns false and an exception is thrown if you try to submit changes. The completion of an update command is signaled by the RowUpdated event. The number of rows updated by the execution of the update command can be retrieved in the AffectedRows property of the RowUpdated event arguments.

The GridView automatically collects values from the input fields and populates a dictionary of name/value pairs that indicate the new values for each field of the row. The GridView also exposes a RowUpdating event that allows you to modify the values being passed to the data source object. In addition, the GridView automatically calls Page.IsValid before invoking the Update operation on the associated data source. If Page.IsValid returns false, the operation is canceled. This is especially useful if you're using a custom edit template that includes validators.

A similar pattern occurs for the deletion of a row. The following SQL command is valid content for the DeleteCommand property of a data source object:

DELETE employees WHERE employeeid=@employeeid

Note that the delete operation fails if the record can't be deleted due to database-specific constraints. For example, the record can't be deleted if child records refer to it through a relationship. In this case, an exception is thrown.

The GridView control doesn't have automatic support for inserting data into a data source. The missing feature is entirely due to the GridView implementation and doesn't depend on the capabilities and characteristics of the underlying data source. The data source object, in fact, provides a CanInsert property and supports an InsertCommand property. Note that a combination of GridView and DetailsView controls enables you to achieve this functionality, as you'll see in a moment.

The DetailsView Control

Many applications need to work on a single record at a time. In ASP.NET 1.x, there is no built-in support for this scenario. Creating a single record view is possible but requires some coding effort on your part. You have to fetch the record first, then bind its fields to a data-bound form, and optionally provide paging buttons to navigate between records. I devoted three installments of the Cutting Edge column to this problem—April, May, and June 2002.

The need to display the contents of a single record is fairly common when you build master/detail views. Typically, the user selects a master record from a grid and the application drills down to show all the available fields. By combining GridView and DetailsView, you build hierarchical views with very little code.

The DetailsView control can automatically bind to any data source control and take advantage of its set of data operations. The control can automatically page, update, insert, and delete data items in the underlying data source as long as the data source supports these operations. In most cases, no code is required to set up any of these operations, as shown here:

<asp:detailsview runat="server" id="det" datasourceid="MySource" autogenerateeditbutton="true" autogenerateinsertbutton="true" autogeneratedeletebutton="true" allowpaging="true" headertext="Employees"> <pagersettings mode="NextPreviousFirstLast" firstpageimageurl="images/first.gif" lastpageimageurl="images/last.gif" nextpageimageurl="images/next.gif" previouspageimageurl="images/prev.gif" /> </asp:detailsview>

The user interface of the DetailsView control can be customized using data fields and styles in a way that is similar to the GridView. The DetailsView doesn't support custom templates as this specific capability has been entirely factored into the new FormView control. The DetailsView can have a command bar with any combination of Edit, Delete, and New buttons. When you click Edit or New, the control renders in Edit or Insert mode and the contents of fields are displayed in textboxes. The working mode can be controlled through the Mode and DefaultMode properties.

The DetailsView control lends itself very well to implementing no-code master/details scenarios. Along with Edit and Delete buttons, the GridView control supports the Select button, which is also predefined. You enable this button on a per-row basis by setting the AutoGenerateSelectButton property to true. When the users click on this button, the current row enters the selected state and its zero-based index is assigned to the SelectedIndex property of the GridView. In addition, the GridView control raises the SelectedIndexChanged event. Applications can hook up to this event and run custom code.

In ASP.NET 2.0 there's no need to handle the SelectedIndexChanged event if you want to build a master/detail view. You can drop a GridView and a DetailsView control on the page and bind each to a data source. The trick for codeless master/detail is to bind the detail control to a data source represented by the currently selected record, as shown in the following:

<asp:sqldatasource runat="server" id="MyDetailSource" ••• selectcommand="SELECT * FROM customers" filterexpression="customerid='@customerid'"> <filterparameters> <asp:ControlParameter Name="customerid" ControlId="masterGrid" PropertyName="SelectedValue" /> </filterparameters> </asp:sqldatasource>

The FilterExpression property of a data source object defines the WHERE clause for the base query specified by SelectCommand. Parameter values can be specified in a variety of ways, including through direct binding with a control property. The <ControlParameter> object sets the @customerid parameter to the value stored in the SelectedValue property of the master grid control. The code in Figure 14 shows the configuration of the master grid and the detail view controls. Figure 15 shows the page in action. Note that no program code was required to achieve this.

Figure 14 Configuration of the Master Grid

<%@ page language="C#" theme="SmokeAndGlass" %> <html> <head runat="server"> <title>Master-Detail View</title> </head> <body> <form runat="server"> <asp:sqldatasource runat="server" id="MySource" connectionstring="SERVER=(local);DATABASE=northwind;Integrated Security=SSPI;" selectcommand="SELECT customerid, companyname, country FROM customers" /> <asp:sqldatasource runat="server" id="MyDetailSource" connectionstring="SERVER=(local);DATABASE=northwind;Integrated Security=SSPI;" selectcommand="SELECT * FROM customers" filterexpression="customerid='@customerid'"> <filterparameters> <asp:ControlParameter Name="customerid" ControlId="Master" PropertyName="SelectedValue" /> </filterparameters> </asp:sqldatasource> <table><tr><td> <asp:gridview runat="server" id="Master" width="100%" datasourceid="MySource" pagesize="10" selectedindex="0" allowpaging="true" datakeynames="customerid" autogenerateselectbutton="true" autogeneratecolumns="false"> <columns> <asp:boundfield datafield="companyname" headertext="Customer" /> <asp:boundfield datafield="country" headertext="Country" /> </columns> </asp:gridview> </td></tr> <tr><td> <asp:detailsview runat="server" id="Detail" width="100%" datasourceid="MyDetailSource"> </asp:detailsview> </td></tr></table> </form> </body> </html>

Figure 15 Master Grid in Action

Figure 15** Master Grid in Action **

The FormView Control

FormView is a new data-bound control that works like the templated version of the DetailsView. It renders one record at a time picked from the associated data source and optionally provides paging buttons to navigate between records. Unlike the DetailsView control, FormView doesn't use data control fields but allows the user to define the rendering of each item using templates. FormView supports any basic operation its data source provides.

Designed to be used mostly as an update and insert interface, the FormView control is unable to validate against a data source schema and doesn't supply advanced editing features like foreign key field dropdowns. However, by using templates you can easily provide this functionality. Two functional aspects mark the difference between FormView and DetailsView. First, the FormView control has ItemTemplate, EditItemTemplate, and InsertItemTemplate properties that the DetailsView lacks entirely. Second, the FormView lacks the command row, a toolbar on which available functions are grouped. Unlike the GridView and DetailsView controls, the FormView has no default rendering of its own. At the same time, its graphical layout is completely customizable using templates. Therefore, each template will include all command buttons needed by the particular record. The following code snippet shows the typical code you write to embed a FormView in your pages.

<asp:FormView ID="EmpDetails" runat="server" DataSourceId="MySource" AllowPaging="true"> <ItemTemplate> ••• </ItemTemplate> <EditItemTemplate> ••• </EditItemTemplate> <InsertItemTemplate> ••• </InsertItemTemplate> </asp:FormView>

Figure 16 illustrates a page that uses a FormView control. The Edit button is added using an <asp:Button> element with the Edit command name. This will cause the FormView to switch from read-only to edit mode and display using the EditItemTemplate, if any is defined. A command name of New will force the control to change to its insert mode and render the contents defined for the InsertItemTemplate. Finally, if you add a button with the Delete command name to the item template, then the FormView invokes the Delete command on the data source when the user clicks it.

Figure 16 FormView Control

Figure 16** FormView Control **

How do you retrieve values to update or insert a record? You use a new data binding keyword—Bind—specifically designed for two-way binding:

<asp:TextBox Runat="server" ID="TheNotes" Text='<%# Bind("notes") %>' />

The Bind keyword works like Eval to display data and can retrieve the value you've entered when updating or inserting a record. In addition, Bind is very useful in TemplateFields used in the GridView and DetailsView.

Bind stores the value of the bound control property into a collection of values that the FormView control automatically retrieves and uses to compose the parameter list of the insert or edit command. The argument passed to Bind must match the name of a field in the data container. For example, the textbox in the last code snippet provides the value for the notes field. Finally, bear in mind that the edit and insert templates must contain buttons to save changes. These are ordinary buttons named Update and Insert to save and Cancel to abort.

FormView's events work the same way that DetailsView's and GridView's do. So if you need to do more sophisticated things like pre- or post-processing data (for example, filling dropdowns) then you should write appropriate event handlers for ItemCommand, ItemInserting, ModeChanging, and the like.

Conclusion

Data-bound controls are an essential part of most, if not all, Web apps. Data-bound controls should be simple and powerful. Ideally, they should provide advanced features in a few clicks and use a limited amount of code. Although ASP.NET 2.0 is still in the works, its new generation of data-bound controls meet this requirement. The key shortcoming of ASP.NET 1.x data binding was that it required too much code for common data operations. This has been resolved with the introduction of data source objects and the GridView control. The DetailsView and the FormView are the perfect complement to the GridView and represent an important improvement to the ASP.NET 1.x data toolbox.

Dino Esposito is an instructor and consultant based in Italy. Author of Programming ASP.NET and Introducing ASP.NET 2.0 (both from Microsoft Press), he spends most of his time teaching classes on ADO.NET and ASP.NET and speaking at conferences. Reach Dino at cutting@microsoft.com or join the blog at http://weblogs.asp.net/despos.