Programming Microsoft ASP.NET 2.0 Core Reference
The DataGrid Control
The DataGrid is a column-based control that supports various types of data-bound columns, including text, templated, and command columns. You associate the control with a data source using either the DataSource property or, in ASP.NET 2.0, the DataSourceID property. The simplest way of displaying a table of data using the ASP.NET grid is as follows:
<asp:DataGrid runat="server" />
Once the control has been placed into the page, you bind it to the data source and have it display the resulting markup.
The DataGrid Object Model
The DataGrid control provides a grid-like view of the contents of a data source. Each column represents a data source field, and each row represents a record. The DataGrid control supports several style and visual properties; in a more realistic scenario, the markup required to embed the control in a page is significantly larger and complex enough to include all those attributes. In ASP.NET 2.0, themes can wrap control-specific visual settings and apply them seamlessly while leaving the markup as slim as possible.
Properties of the DataGrid Control
Table 10-1 lists the properties of the control, except those that the control inherits from Control and WebControl.
Property | Description |
---|---|
AllowCustomPaging | Gets or sets whether custom paging is enabled. AllowPaging must be set to true for this setting to work. |
AllowPaging | Gets or sets whether paging is enabled. |
AllowSorting | Gets or sets whether sorting is enabled. |
AlternatingItemStyle | Gets the style properties for alternating rows. |
AutoGenerateColumns | Gets or sets whether columns are automatically created and displayed for each field in the data source. True by default. |
BackImageUrl | Gets or sets the URL of the image to display as the background of the control. |
Caption | The text to render in the control's caption. Not available in ASP.NET 1.x. |
CaptionAlign | Alignment of the caption. Not available in ASP.NET 1.x. |
CellPadding | Gets or sets the space (in pixels) remaining between the cell's border and the embedded text. |
CellSpacing | Gets or sets the space (in pixels) remaining, both horizontally and vertically, between two consecutive cells. |
Columns | Gets a collection of DataGridColumn objects. |
CurrentPageIndex | Gets or sets the index of the currently displayed page. |
DataKeyField | Gets or sets the key field in the bound data source. |
DataKeys | Gets a collection that stores the key values of all records displayed as a row in the grid. The column used as the key is defined by the DataKeyField property. |
DataMember | Indicates the specific table in a multimember data source to bind to the grid. The property works in conjunction with DataSource. If DataSource is a DataSet object, it contains the name of the particular table to bind. |
DataSource | Gets or sets the data source object that contains the values to populate the control. |
DataSourceID | Indicates the data source object to populate the control. Not available in ASP.NET 1.x. |
EditItemIndex | Gets or sets the index of the grid's item to edit. |
EditItemStyle | Gets the style properties for the item being edited. |
FooterStyle | Gets the style properties for the footer section of the grid. |
GridLines | Gets or sets whether all cells must have the border drawn. |
HeaderStyle | Gets the style properties for the heading section of the grid. |
HorizontalAlign | Gets or sets the horizontal alignment of the text in the grid. |
Items | Gets the collection of the currently displayed items. |
ItemStyle | Gets the style properties for the items in the grid. |
PageCount | Gets the number of pages required to display all bound items. |
PagerStyle | Gets the style properties for the paging section of the grid. |
PageSize | Gets or sets the number of items to display on a single page. |
SelectedIndex | Gets or sets the index of the currently selected item. |
SelectedItem | Gets a DataGridItem object representing the selected item. |
SelectedItemStyle | Gets the style properties for the currently selected item. |
ShowFooter | Indicates whether the footer is displayed. False by default. |
ShowHeader | Indicates the header is displayed. True by default. |
UseAccessibleHeader | Indicates whether the control's header is rendered in an accessible format that is, using <th> tags instead of <td>. Not available in ASP.NET 1.x. |
VirtualItemCount | Gets or sets the virtual number of items in the DataGrid control when custom paging is used. |
The characteristic traits of the DataGrid control are the Columns and Items collections, the style and data-binding properties. All columns in the grid are represented by an object with its own set of properties and methods. Several types of columns are available to implement the most common tasks. In general, not all rows in the bound data source are included in the HTML code for the client. The Items collection returns a collection of DataGridItem objects, one per each displayed row. The DataGridItem class is a specialized version of the TableRow class.
Note | In ASP.NET 2.0, a bunch of new properties make their debut to improve the usability of the control especially for users with accessibility problems. Caption, CaptionAlign, and UseAccessibleHeader let you tweak the markup that the control generates to make it easier to users of Assistive Technology devices. |
Constituent Elements of a DataGrid
The output of a DataGrid control is made of several constituent elements grouped in the ListItemType enumeration. Each element plays a clear role and has a precise location in the user interface of the control, as Figure 10-1 shows.
The DataGrid user interface comprises the logical elements listed in Table 10-2. Each element has its own style property that is, the set of graphical settings that are automatically applied by the control.
Item Type | Description |
---|---|
AlternatingItem | Represents a data-bound row placed in an odd position. Useful if you want to use different styles for alternating rows. AlternatingItemStyle is the property that lets you control the look and feel of the element. |
EditItem | Represents the item currently displayed in edit mode. EditItemStyle lets you control the look and feel of the element. |
Footer | Represents the grid's footer. The element can't be bound to a data source and is styled using the settings in the FooterStyle property. |
Header | Represents the grid's header. The element can't be bound to a data source and is styled using the settings in the HeaderStyle property. |
Item | Represents a data-bound row placed in an even position. Styled through the ItemStyle property. |
Pager | Represents the pager element you use to scroll between pages. The element can't be bound to a data source and is styled using the settings in the PagerStyle property. The pager can be placed at the top or bottom of the grid's table and even in both places. |
SelectedItem | Represents the item, or alternating item, currently selected. The property that defines its look and feel is SelectedItemStyle. |
Each time one of the constituent elements is about to be created, the grid fires an ItemCreated event for you to perform some application-specific tasks. We'll return to grid events in a moment.
Data Source Rows and Displayed Rows
By design, the DataGrid control displays the data stored in a data source object be it an enumerable data object or, in ASP.NET 2.0, a data source control. Each row in the bound data source is potentially a row in the grid. However, this one-to-one mapping doesn't always correspond to reality. Each displayed grid row finds a place in the Items collection. Each element in the Items collection is an instance of the DataGridItem class a slightly richer table row object and supplies a DataItem property set to the object that corresponds to the row in the bound data source. Note that only bindable items are contained in the Items collection. The header, footer, and pager are not included in the collection.
The index properties of the DataGrid refer to the rows displayed rather than to the underlying data source. When the item with an index of 1 is selected, the second displayed item is selected, but this piece of information says nothing about the position of the corresponding source record. The data source index for the item object is stored in the DataSetIndex property on the DataGridItem class. DataSetIndex returns the absolute position in the overall data source of the record represented by the current item. Although functional, this method isn't especially handy in some common scenarios, such as when you want to select a row and retrieve a bunch of associated records. In such a case, you need to know the value of the key field in the underlying data source row.
The DataKeys collection and the DataKeyField property provide an effective shortcut designed specifically to work in similar situations. When you configure a DataGrid, you can store the name of a key field in the DataKeyField property. During the data-binding phase, the control extracts from the data source the values for the specified key field that correspond to the rows being displayed. As a result, the index of the selected row in the Items collection can be used with DataKeys to get the key value for the underlying data source row. Let's consider the following declaration, which refers to a grid that displays information about the employees of a company:
<asp:DataGrid runat="server" DataKeyField="employeeid" ... >
To get the ID of the selected employee to be used to implement, say, a drill-down view you simply use the following code:
// empID is the key of the currently selected item int empID = grid.DataKeys[grid.SelectedIndex];
The DataKeys collection is automatically filled by the control based on the value of the DataKeyField property and the bound data source.
Events of the DataGrid Control
The DataGrid control has no specific methods worth mentioning. Table 10-3 lists the events that the control fires during its life cycle.
Event | Description |
---|---|
CancelCommand | The user clicked to cancel any updates made on the current item being edited. |
DeleteCommand | The user clicked to start a delete operation on the current item. |
EditCommand | The user clicked to put the current item in edit mode. |
ItemCommand | The user clicked any command button within the grid control. |
ItemCreated | This event occurs after a new grid item is created. |
ItemDataBound | This event occurs after a grid item is bound to data. |
PageIndexChanged | The user clicked to see a new page of data. |
SelectedIndexChanged | The user clicked to select a different item. |
SortCommand | The user clicked to start a sort operation on a column. |
UpdateCommand | The user clicked to save any updates made on the current item being edited. |
The CancelCommand and UpdateCommand events are fired under special circumstances that is, when an item is being edited. (We'll cover the DataGrid in-place editing feature later in the chapter.) The CancelCommand event signals that the user clicked the Cancel button to cancel all pending changes. The UpdateCommand event denotes the user's intention to persist all the changes. The other command events EditCommand, DeleteCommand, and SortCommand indicate that the user required a particular action by clicking on command buttons within the user interface of the grid.
In addition to the events just listed, the DataGrid control fires all the standard events of Web controls, including Load, Init, PreRender, and DataBinding. In particular, you might want to write a handler for PreRender if you need to modify the HTML code generated for the grid. The DataBinding event, on the other hand, is the entry point in the grid's binding process. The event is fired as the first step before the whole binding process begins, regardless of the type of object bound be it an enumerable object or a data source control.
Note | These command events mark a key difference between the DataGrid and the newer GridView control. While the DataGrid is limited to firing an event to let the page know the user's intention, the GridView proactively handles the event by executing the configured command through the bound data source control. The DataGrid supports data source controls too, but the support is limited to showing read-only data. |
Binding Data to the Grid
A DataGrid control is formed by data-bindable columns. By default, the control includes all the data source columns in the view. You can change this behavior by setting the AutoGenerateColumns property to false. In this case, only the columns explicitly listed in the Columns collection are displayed. The DataGrid control supports a variety of column types, which mostly differ from one another in how each represents the data. You are required to indicate the type of the column if you add it to the Columns collection; otherwise, if automatic generation is used, all columns are of the simplest type the BoundColumn column type. Table 10-4 details the various types of columns supported.
Column Type | Description |
---|---|
BoundColumn | The contents of the column are bound to a field in a data source. Each cell displays as plain text. |
ButtonColumn | Displays a button for each item in the column. The text of the button can be data-bound and buttons have a common command name. |
EditCommandColumn | Particular type of button column associated with a command named Edit. When in edit mode, the whole row is drawn using text boxes rather than literals. |
HyperLinkColumn | Displays the contents of each item in the column as a hyperlink. The text of the hyperlink can be bound to a column in the data source or it can be static text. The target URL can be data-bound, too. Clicking a hyperlink column causes the browser to jump to the specified URL. |
TemplateColumn | This type displays each cell of the column following a specified ASP.NET template. It also allows you to provide custom behaviors. |
Note that the AutoGenerateColumns property and the Columns collection are not mutually exclusive. If both properties are set to true and the collection is not empty, the grid will show the user-defined columns followed by all the ones that auto-generation would produce.
You normally bind columns using the <columns> tag in the body of the <asp:datagrid> server control, as the following code demonstrates:
<asp:datagrid runat="server" ... > ... <columns> <asp:BoundColumn runat="server" DataField="employeeid" HeaderText="ID" /> <asp:BoundColumn runat="server" DataField="firstname" HeaderText="First Name" /> <asp:BoundColumn runat="server" DataField="lastname" HeaderText="Last Name" /> </columns> </asp:datagrid>
Alternately, you can create a new column of the desired class, fill its member properly, and then add the class instance to the Columns collection. Here is some code to add a BoundColumn object to a grid:
BoundColumn bc = new BoundColumn(); bc.DataField = "firstname"; bc.HeaderText = "First Name"; grid.Columns.Add(bc);
The order of the columns in the collection determines the order in which the columns are displayed in the DataGrid control.
Note | The Columns collection doesn't persist its contents to the view state, and it is empty whenever the page posts back. To preserve any dynamically added column, you need to re-add it on each and every postback. |
Data-Bound Columns
All grid column types inherit from the DataGridColumn class and have a few common properties such as the header text, footer and item style, and visibility flag. Table 10-5 details the properties shared by all types of columns.
Property | Description |
---|---|
FooterStyle | Gets the style properties for the footer of the column |
FooterText | Gets or sets the static text displayed in the footer of the column |
HeaderImageUrl | Gets or sets the URL of an image to display in the header |
HeaderStyle | Gets the style properties for the header of the column |
HeaderText | Gets or sets the static text displayed in the header of the column |
ItemStyle | Gets the style properties for the item cells of the column |
SortExpression | Gets or sets the expression to sort the data in the column |
Visible | Gets or sets whether the column is visible |
The BoundColumn class represents a column type that is bound to a data field. The key properties to set up a grid column are DataField, which represents the name of the column to bind, and DataFormatString, which allows you to format the displayed text to some extent. The ReadOnly property has an effect only if an edit command column is added to the grid. In this case, the cells in the column are switched to edit mode according to the value of the property.
The following code snippet adds two columns and specifies the header text and the source column for each. In addition, the second column is given a format string to make it look like a currency value with right alignment.
<asp:boundcolumn runat="server" datafield="quantityperunit" headertext="Packaging" /> <asp:boundcolumn runat="server" datafield="unitprice" headertext="Price" DataFormatString="{0:c}"> <itemstyle width="80px" horizontalalign="right" /> </asp:boundcolumn>
Graphical settings for a column must be specified using a child style element.
HyperLink Columns
The HyperLinkColumn class is a column type that contains a hyperlink for each cell. The programmer can control the text of the hyperlink and the URL to navigate. Both fields can be bound to a column in the data source. The DataTextField takes the name of the field to use for the text of the hyperlink. DataNavigateUrlField, on the other hand, accepts the field that contains the URL. Another property, named DataNavigateUrlFormatString , defines the format of the final URL to use. By combining the two properties, you can redirect users to the same page, passing row-specific information on the query string, as shown in the following code:
<asp:hyperlinkcolumn runat="server" datatextfield="productname" headertext="Product" datanavigateurlfield="productid" datanavigateurlformatstring="productinfo.aspx?id={0}" target="ProductView"> <itemstyle width="200px" /> </asp:hyperlinkcolumn>
The hyperlinks will point to the same page productinfo.aspx each with the product ID associated with the corresponding row of the bound data. The column class is responsible for building the real URL correctly.
Note | By using the DataNavigateUrlField and DataNavigateUrlFormatString properties together, you can make the URL of the hyperlink parametric. However, by default you are limited to just one parameter the value of the field bound through the DataNavigateUrlField property. To use a hyperlink bound to any number of arguments, you should resort to templated columns or use a GridView. |
Button Columns
The ButtonColumn class represents a command column and contains a user-defined button for each cell in the column. Functionally similar to hyperlink columns, button columns are different because they generate a postback event on the same URL. Although the caption of each button can be bound to a data-source column, more often than not a button column has static text displayed through all the cells.
The key idea behind the button column is that you execute a particular action after the user clicks on a row. All buttons in the column are associated with some script code that posts the page back and executes the ItemCommand server-side procedure. Within that procedure, you use the command name (the CommandName property) to distinguish between multiple button columns, and you use the ItemIndex property of the DataGridItem class to know the particular row that was clicked. A reference to a DataGridItem object is passed through the ItemCommand event.
The select column is a special type of button column. It is a normal button column with the command name of select. When you click on such a column, the DataGrid automatically redraws the selected row using a different class of settings those determined by the SelectedItemStyle property. There is no need for you to write an ItemCommand handler; the described behavior is built in:
<asp:ButtonColumn runat="server" text="Select" CommandName="Select" />
The style of the selected row at most one at a time is set using the SelectedItemStyle property. It can be as easy as the following code:
<selecteditemstyle backcolor="cyan" />
The change of the selected item is signaled with the SelectedIndexChanged event. However, before this event is fired, the application can handle the related ItemCommand event. When SelectedIndexChanged reaches the application, the SelectedIndex property indicates the new selected index.
Templated Columns
Templated columns allow you to create combinations of HTML text and server controls to design a custom layout for any cells in the column. The controls within a templated column can be bound to any combination of fields in the data source. In particular, you can group more fields in a single expression and even embellish it with HTML attributes such as bold-face or italic style. Templates are column-specific and cannot be applied to auto-generated columns. If you want more columns to share the same template, you can duplicate the code only in the ASP.NET page for each column.
A templated column is recognized by the <TemplateColumn> tag and rendered by the TemplateColumn class. The body of the tag can contain up to four different templates: ItemTemplate, EditItemTemplate, HeaderTemplate, and FooterTemplate. Just as with any other column type, a templated column can have header text and a sort expression. Templated columns, though, do not have an explicit data source field to bind. To bind a templated column to one or more data fields, you use a data-binding expression. (See Chapter 9.) In particular, you use the Eval method to evaluate data-bound expressions at run time and return the value properly cast. For example, the following code snippet shows a templated column that mimics the behavior of a BoundColumn object associated with the lastname column:
<asp:templatecolumn runat="server" headertext="Last Name"> <itemtemplate> <asp:label runat="server" Text='<%# DataBinder.Eval(Container.DataItem, "lastname") %>' /> </itemtemplate> </asp:templatecolumn>
By using DataBinder.Eval (or simply Eval in ASP.NET 2.0), you can access any number of fields in the currently bound data source. In addition, you can combine them in any order to obtain any sort of expression, which is otherwise impossible using a simpler bound or button column.
Working with the DataGrid
The DataGrid control is not simply a tool to display static data; it also provides advanced functionalities to page, sort, and edit bound data. The interaction that is established between a DataGrid and the host page is limited to exchanging notifications in the form of postback events. The DataGrid lets the page know that something happened and leaves the page free to react as appropriate. This pattern is common to most supported operations, with the notable exception of item selection. As mentioned, in fact, if you add a Select button column to the grid and define a proper style for selected items, clicking on a Select button makes the page post back and forces the DataGrid to change the appearance of the corresponding row. Other operations for which the DataGrid simply fires an event to the page are paging, sorting, and in-place editing.
As you can see, these are relatively common operations that plenty of pages need to accomplish. If you choose to use a DataGrid control, be ready to write much more boilerplate code than you would with the newer GridView control.
Paging Through the Data Source
In real-world scenarios, the size of a data source easily exceeds the real estate of the page. Data paging is the contrivance that many applications adopt to both gain in scalability and present a more helpful page to the user. Especially on the Web, displaying only a few rows at a time is a more effective approach than downloading hundreds of records that stay hidden most of the time. The DataGrid control provides some built-in facilities to let the programmer easily switch to a new page according to the user's clicking.
The control needs to know how many items should be displayed per page, what type of functionality is required for the pager, and the data source to page through. In return for this, the control tracks the current page index, extracts the rows that fit into the particular page, and refreshes the user interface. Whenever the page index changes, an event is fired to the application the PageIndexChanged event.
Note, however, that the host page is still responsible for ensuring that all the rows that fit into the new page are bound to the control. This holds true even if the DataGrid is bound to a data source control or a classic enumerable object. With a DataGrid, a handler for the PageIndexChanged event is always required. What you do in the handler might be different, though, depending on the actual data source. Here's the code you need to use if the DataGrid is bound to a data source control:
protected void grid_PageIndexChanged(object sender, DataGridPageChangedEventArgs e) { grid.CurrentPageIndex = e.NewPageIndex; // Must be repeated to force a refresh grid.DataSourceID = "SqlDataSource1"; }
Note that you still need to reassign DataSourceID to trigger an internal data source changed event and cause the control to load its new dataset. If the grid is bound to an enumerable object, you simply assign a new bunch of rows to the DataSource property.
Overall, paging is a tough feature and a potential scalability killer. If you leave grid controls in charge of handling paging more or less automatically, caching data is a must. A data source control makes it as easy as turning on the EnableCaching property, as you saw in Chapter 9. Caching a lot of data, though, might pose a serious problem, especially if you have to do that for each user.
DataGrid controls also support custom paging, an alternative and cost-effective approach to paging that binds to the control only the records that fit in the current page:
protected void grid_PageIndexChanged(object sender, DataGridPageChangedEventArgs e) { grid.CurrentPageIndex = e.NewPageIndex; grid.DataSource = GetRecordsInPage(grid.CurrentPageIndex); } protected object GetRecordsInPage(int pageIndex) { // Retrieve and return data that fit in the given page }
As we'll see later, the GridView doesn't explicitly support custom paging. On the other hand, the GridView doesn't prevent server paging from working if it is supported by the underlying data source control or the (data access layer) DAL.
Sorting Columns of Data
The AllowSorting property enables sorting on all or some of the DataGrid's displayed columns. Just as for paging, clicking to sort data by a column doesn't really produce any visible effect unless you add a handler for the SortCommand event. Here's a simple handler you can use if the DataGrid is bound to a data source control:
protected void grid_SortCommand(object sender, DataGridSortCommandEventArgs e) { SqlDataSource1.SelectCommand += " ORDER BY " + e.SortExpression; grid.DataSourceID = "SqlDataSource1"; }
Sorting is a potentially slow operation to accomplish and can have significant repercussions on scalability. For this reason, it is important to understand how it really works in the context of grids. In ASP.NET 1.x, you can employ in the SortCommand event handler only your own logic to sort. You can sort in memory using the Sort method of the DataView object (which is a very slow process, indeed); you can rely on the database sort capabilities (which is typically the fastest approach to sort data, but note that communication latency and network bandwidth may serve to slow things down from the user's perspective); sometimes, you can also maintain presorted caches of data. Whatever you choose, you need to know what you're doing.
In ASP.NET 2.0, data source controls tend to hide some details. If the data source control is configured to retrieve data via a DataSet (the default setting), sorting happens in memory via the Sort method. This approach is not really efficient, and it should be avoided unless you have only a few records to sort. If the data source control works via data readers and stored procedures, sorting can take place on the server and data will be returned in the correct order. In the end, sorting is a delicate operation no matter which controls you use. Only careful benchmarks and an application-specific combination of tools and options can deliver the perfect result. To get this, you need to understand how controls work internally.
Editing Existing Rows
A DataGrid control displays mostly read-only data. If editing is needed, you select the row to update and post a request for editing. The new page contains an edit form with input fields and links to persist or reject the changes. This pattern is probably the most effective one for editing data over the Web, and it's certainly the pattern that provides the highest level of flexibility. With DataGrid controls, though, a simpler model of data editing is possible. The new model is known as in-place editing and mimics the behavior of a Microsoft Office Excel worksheet. When you trigger the event that begins the editing phase, the visible part of the grid is redrawn and like cells in Excel the row selected for editing is rendered in a different way, using text-box controls instead of literals and labels. At the same time, the DataGrid control completes its own user interface with a couple of button links to allow you to commit or roll back changes.
In-place editing does not require much work to be completely set up, but at the same time it is not appropriate for all types of applications, and it is not functional in all operating contexts. All in all, if you have to edit the content of single and relatively small tables that have no special validation or business logic to apply, in-place editing is extremely handy and powerful.
The key object for in-place editing is the EditCommandColumn class. The column adds a link button to all rows of the grid. When the link is clicked, the page posts back and the cells of the row are drawn in edit mode. How a column behaves in edit mode depends on the column type. For example, button and hyperlink columns are completely ignored in edit mode. Bound and templated columns, on the other hand, change their rendering when the row is being edited. In particular, bound columns are rendered using text boxes in place of literals, whereas templated columns display the contents of the <EditItemTemplate> section, if any.
As with paging and sorting, code is required to have the DataGrid complete an in-place editing operation, too. You typically need to write three event handlers EditCommand, to put the grid in edit mode; CancelCommand, to put the grid back in read-only mode; and UpdateCommand, to persist changes and refresh the grid. Handlers for EditCommand and CancelCommand are relatively simple and standard. Writing a handler for UpdateCommand might not be that easy, though.
Basically, the UpdateCommand handler must accomplish two key operations retrieving input data and persisting changes. Both operations are hard-coded in the GridView, performed in collaboration with the underlying data source control, and mostly configured at design time by the page author.
Important | Admittedly, this section about DataGrid controls didn't get into the nitty-gritty details of how the control works and deliberately avoided describing how to implement paging, sorting, and editing properly in real-world scenarios. The reason for this approach lies in the structural difference that exists between DataGrid and GridView controls. To a large extent, the two controls provide the same set of abstract features grid-like display, paging, sorting, editing, and templates. How each control implements individual features and binds to data is radically different. In one word, the philosophy behind each control is different. Now, the GridView control is newer, richer, and smarter, and it would probably have been the only grid control in ASP.NET 2.0 if it weren't for compatibility issues. If you have an existing ASP.NET application to maintain, and you don't feel like leaping to GridView, you already know all the details and techniques omitted here. If you're building a new application and want to take advantage of grids, you don't need to know about DataGrid controls and are better off focusing entirely on GridView controls. The purpose of this section is to help people in the middle make a decision about which control to use while explaining why Microsoft decided to go with a new control that is designed to complement the changes in the data-binding model we explored in Chapter 9. The GridView control is also complemented by other view controls specifically, FormView and DetailsView that we'll cover in the next chapter. |