| ast month (May 2001) I dug into the fundamentals of bilding ASP.NET interactive DataGrids using the <asp:DataGrid> server control. But it was clear that the editing support in the control is only useful in some scenarios. The control can only understand a one-to-one relationship between the data you're displaying in a given column and a database field. If you formatted the content that a column displays, you used some sort of special logic that is specific to the application and, as such, is impossible for the control to handle in a general-purpose way. |
In this column, I'll show how to manage editable tables of data in a number of common circumstances. I'll carefully consider the cases in which special formatting applies to one or more columns, where validation is required, and where the value must be chosen from a list.
In reading, you'll realize that the key issue to be aware of is the DataGrid's ability to support templated columns. If you need to put some custom logic and user interface elements into the editing process, then just define the ASP.NET template that suits your needs and let the DataGrid know about it.
Templating is an important feature of list-bound ASP.NET controls, of which the DataGrid is the most feature-rich and versatile. In fact, other list-bound controls such as <asp:datalist> and <asp:repeater> also support templated rows and columns and end up being a sort of general-purpose engine to process collections of data and shape them for display. I'll cover <asp:datalist> and <asp:repeater> in my next column.
Let's start the in-depth analysis of the grid's in-place editing with one of the most common situations: the field you display is the result of special formatting rules. The standard behavior, which replaces the read-only <asp:label> control with an interactive <asp:textbox> control, just doesn't fit because the information in the buffer belongs to one or more data fields. For an effective and consistent update, you must know how to extract and process the various chunks of information.
Figure 1 ASP.NET DataGrid with Editing
Figure 1 shows an ASP.NET DataGrid when standard in-place editing is activated. The first editable column contains the text originated from the following SQL SELECT clause:
EmployeeName is a virtual column obtained by concatenating three different database columns. One possible solution is that you let the user freely edit the text and then parse it into distinct variables while saving it. I don't like to recommend this approach because you never know what a user might enter in a textbox. You should avoid having to rely on validation and post-checking data. If the column you're using is a computed column, you should try to keep it read-only.
titleofcourtesy + ' ' +
firstname + ' ' +
lastname AS EmployeeName,
To enable updates of formatted columns, define an edit template and populate the column cell with as many HTML controls and as much code as needed for effective data entry. In this case, you can have a dropdown list and two distinct textboxes. The list holds the values you could use for title of courtesy (such as Ms., Mr., and so on). Such a list can reasonably be a closed collection read from a separate database. The two textboxes will take care of handling the first and the last name in the row being edited (see Figure 2).
Figure 2 Dropdown List
While custom templates are powerful, if you choose to use them you are forced to make use of them anywhere in the column. In other words, you cannot have a bound or a button column with a custom template for editing. Only fully templated columns can have a custom user interface for in-place editing. Now, having a computed column in the query doesn't make sense. It's mandatory that you have available all the fields you want to edit. This field
is simply an extra field you query for, as the query must now include titleofcourtesy, firstname, and lastname for editing purposes.
titleofcourtesy + ' ' +
firstname + ' ' +
lastname AS EmployeeName
The column with the employee name is now better rendered through a template column:
Notice the role of the inner <template> tag. The <asp:TemplateColumn> server control defines a new column for the DataGrid, but the actual HTML schema for the cell is set within the body of <template>. A template must target a type of item. Put another way, a templated column must have a chance to define how its cells have to be rendered under all possible circumstances. This includes the normal and the alternating item and the item's appearance when the row is selected or edited. The name attribute of the <template> tag provides for this. ItemTemplate is the keyword which identifies the HTML template to be used for normal rendering. AlternatingItemTemplate is the template name for rows rendered on even positions. For rows under editing, the template name is EditTemplateName.
<asp:label runat="server" Text='<%#
DataBinder.Eval(Container.DataItem, "titleofcourtesy") + " " +
DataBinder.Eval(Container.DataItem, "firstname") +
DataBinder.Eval(Container.DataItem, "lastname") %>' />
To accomplish this, the code shown in the previous snippet changes to the code shown in Figure 3.
Instead of a single, all-encompassing label, the edit template provides three different controls: a dropdown list for picking up any of the possible titles and two distinct textboxes to handle first and last names, as was shown in Figure 2.
Don't forget to assign a unique ID to all the controls involved. Later on, IDs will be necessary to retrieve the current values from the controls and instruct the update procedure.
Basic ASP.NET data-binding techniques let you associate an inline control (such as a label or textbox) with text coming from a data source. I covered the DataBinder objectwhich is mainly responsible for such data-bindingin the March 2001 column. The magic formula
lets you retrieve the content of the given field for the row being processed. You can combine this information with operators, other field values, constants, and whatever else you may need. When the necessary formula becomes too complex, you can resort to user-defined functions. You're free to decide the number and type of the input arguments. However, the return value must match the type that the target attribute you're processing expects. For instance, in the following code
the local function TitlesOfCourtesy must return a type derived from ICollection because that is what the DataSource property expects to receive. Here is a possible body for such a function:
When it comes to saving changes, the code running after the OnUpdateCommand event will be responsible for picking values up from these controls and arranging the physical update.
ArrayList a = new ArrayList();
Picking Values from a List
The <asp:dropdownlist> control is similar to the HTML <select> tag that ASP.NET renders through the HtmlSelect server control. The main difference is in the namespace that hosts the two controls. HtmlSelect is part of the standard HTML namespace. As such, the output it generates, and the way in which it behaves, are identical to the select element you can find in both HTML and ASP pages.
By contrast, like all the controls in the asp namespace, the dropdown list component abstracts the programming interface of the tag. It offers a nearly identical set of properties and methods but through different names. The output that the ASP.NET runtime generates for asp namespace controls can vary according to the browser type you target in your page. This feature is also referred to as "smart server controls" and will let you use just one abstract kind of code to target HTML 3.2-compatible browsers as well as uplevel, DHTML-compatible browsers like Microsoft Internet Explorer 5.5 and higher.
Both controls support data-binding through the DataSource property and the DataBind method. While you can define a selection changed server event handler for both controls, only the dropdown list control supports the autopostback feature. When the autopostback property is set to true like this
the page gets automatically posted back to the server whenever the control raises the SelectedIndexChanged event. As the name suggests, this happens when users select an item. This feature is disabled by default, but it turns out to be helpfulespecially if you have a piece of code that refreshes a portion of the user interface according to the value selected in the list.
<asp:dropdownlist id="dd1" runat="server" autopostback="true" />
An important aspect of data entry is the ability to check that the information the user provided is valid and consistent. For example, let's suppose that you need to add a column to the grid of Figure 1 showing the hire date of each employee. Let's also assume that this field can be modified. Since it contains a date, you might want to ensure that what the user enters is really a date, and you want to let her continue only when a correct date has been entered.
ASP.NET has a set of validation controls that you can use to check for errors and display warning messages. ASP.NET validation controls perform common tasks such as testing for valid dates, required fields, and values that fall within a given range. In addition, the framework allows you to provide custom validation rules and custom ways to display error messages. Validation controls are ordinary ASP.NET components and can validate the input of both asp and html namespace server elements (see Figure 4).
Each validation control points to an input control that can be placed anywhere on the page. When the OK button is pressed to submit changes, the values get passed to the appropriate validator. The validation controls test the data and set their IsValid property to indicate the result of the test. As you may have guessed already, IsValid is a boolean property. When all the validators have been called, the ASP.NET runtime sets the IsValid property for the page. Of course, the value of Page.IsValid is the logical AND of the results of all the single controls. The Page.IsValid property serves the purpose of a quick and easy test to see if you can safely proceed with the rest of the code.
For in-place editing, the best place to test the state of validation is in the code that handles the OnUpdateCommand event.
If you detect an inconsistent situation, just bypass the update and return the page to the user with an error message (see Figure 5).
void UpdateGrid(Object sender,
statusbar.Text = "An error occurred"
DataGrid1.EditItemIndex = -1;
// update the data source here
DataGrid1.DataSource = CreateDataSource();
Figure 5 Returning an Error Message
Editing without Templates
So far, the in-place editing feature of ASP.NET DataGrids forces you to think in terms of per-column update. This might not be a major issue, since the template mechanism allows you to put all the controls you need in a column cell. The custom code that you may need to write to initialize the edit procedure presents a bigger problem. In spite of the fact that editing takes place on a per-column basis, you actually update the row as a whole. So, why not use a separate form to encompass the entire editing process? To display the form, you can use an ordinary command button column, as opposed to the EditCommandColumn necessary to fire the standard editing procedure. Once the command button has been clicked, you post the page back and return it with an extra form with all the fields ready for update. Figure 6 gives you an idea of what I mean.
When you click the button designed to enable the editing, the page appears with a form specific to the row you clicked. This doesn't happen automatically as it does with the DataGrid's in-place editing feature. You're responsible for providing the form (as a collection of UI elements), and the code to outfit it. There's a little more code to write here, but I think that this approach will give you all the flexibility you will need when you have special formatting or computed columns in the grid.
If the grid cells have a one-to-one relationship with the data source fields, then the built-in editing capabilities will meet your needs with a minimum amount of coding on your part.
Such a form-based, template-free approach makes it easier for you to implement features such as the selection of the intended item in the dropdown list when the user types in a few letters. It requires a short code snippet like the following:
In a template-based scenario, the initialization of the edit controls is performed by the runtime. You have to figure out where you can run your own initialization code. You won't have such a problem when you use a form-based approach.
String s = dr["titleofcourtesy"].ToString();
l = new ListItem(s);
ddTitles.SelectedIndex = ddTitles.Items.IndexOf(l);
To complete the discussion of in-place editing in an ASP.NET DataGrid, let's look at the insertion of a new row. Let's assume that the built-in in-place editing mechanism fulfills your expectations. What about new insertions? You still need a button to fire a new operation. Here you have no choice but to create a custom form. A good place to put the button is in the pager or footer. Of course, if you're already using a form to edit the content of the grid, you can just add a new button to the form.
Both the footer and the pager are table rows displayed at the bottom of the grid. The footer is hidden by default and the pager must be explicitly enabled through the AllowPaging property. To display custom information on the pager, you can only replace the standard one with your own. I demonstrated this in the April 2001 issue of MSDN® Magazine. The footer is supposed to display a summary of information on a per-column basis. However, there's really nothing that prevents you from using the footer of a column to display a link to a function. You have two ways to do this. First, you can take advantage of the FooterText property of the Column class.
However, this approach forces you to use client-side script or jump to another ASP.NET page to finalize the insertion.
Text="<img border=0 align=absmiddle src=closed.gif>"
A more flexible approach is to customize the footer template for a certain templated column. Together with the item and the edit template, you could also specify the footer template, the piece of HTML displayed at the bottom of the column.
Notice that a footer template requires a templated column. The user-defined NewRow function executes whenever the button link is clicked. What this function does depends on the application. It can be one of the following: either filling out a form with default values and prompting the user for further changes or inserting a new row with default or empty values.
<asp:linkbutton runat="server" id="btnNewRow"
Text="New row..." OnClick="NewRow()" />
The link to insert a new row is located on the footer and displays for each page. If you're positioned on page one and add a new row, you probably want this new row placed at the bottom of the list, not on the page you're currently viewing. To handle this, you could enable the New Row link only on the last page of the grid by tying its Visible property to the IsLastPage function.
The IsLastPage function can be designed like this:
<asp:linkbutton runat="server" id="btnNewRow"
Text="New row..." OnClick="NewRow()"
Visible='<%# IsLastPage() %>' />
The same technique can be used to deselect the item that is currently selected. The default behavior of the DataGrid, in fact, doesn't allow for interactive deselection once one item has been selected. You must do that programmatically. Here's the code:
if (DataGrid1.CurrentPageIndex+1 == DataGrid1.PageCount)
The Deselect function will simply set the value of SelectedIndex to -1, as shown here:
asp:linkbutton runat="server" id="btnClearSel"
Text="Deselect" OnClick="Deselect" />
Figure 7 shows a DataGrid that employs this kind of footer.
void Deselect(Object sender, EventArgs e)
DataGrid1.SelectedIndex = -1;
To update the content of an editable DataGrid, you first need to retrieve the fresh information from the textboxes involved in the update. In the page method that handles the update you can use code like this to locate the actual editing controls:
Notice that ColumnXControl is the standard ID of the textboxes that ASP.NET generates for you. X refers to the zero-based ordinal position of the column. If you're using templates for editing, then you generate the controls yourself, so you know the necessary IDs. FindControl is a method of the Control class that searches for a child control with the specified name. In this case, you're searching for a control in the specified DataGrid cell.
void Grid_Update(Object Sender, DataGridCommandEventArgs e)
tb = (TextBox) e.Item.FindControl(
To enter changes to your database you normally pass through the data access components. ADO.NET allows you to submit changes through the Update method of the DataSetCommand object. Bear in mind that this object has two actual flavors as of Beta 1: ADODataSetCommand, which takes care of providing managed access to all existing OLE DB providers and SQLDataSetCommand, which works against the managed provider for SQL Server. If you are going to access SQL Server tables, be aware that the managed provider can be significantly faster for two main reasons. First, it knows the exact XML format needed by the ADO.NET classes to create datasets. Second, the managed provider for SQL Server goes straight against the internal SQL Server file system. By contrast, the managed provider for OLE DBall ADO classes in .NETutilizes OLE DB and pays the price of the COM Interop bridge. OLE DB providers are COM objects and are accessed "as is" from .NET code.
Here is the typical code to update a data source:
Update takes a DataSet object and the name of the DataTable whose content has to be stored. Behind such a simple programming interface there's a lot of complexity. I'll summarize the main points here. For all the modified rows in the specified DataTable, Update prepares and runs ad-hoc INSERT, DELETE, and UPDATE SQL statements. It figures out the structure of the database and the fields involved and writes proper commands. But, if you have joined tables, the Update method can't autogenerate SQL commands for your data store.
oCMD = new SQLDataSetCommand(strCmd, strConn);
When autogeneration is possible, you need to set the SelectCommand property of the SQLDataSetCommand object:
SelectCommand is of type SQLCommand and enables Update to know about the physical database tables and fields behind the rather abstract DataTable object. The variable strSelectCmd must contain a query command. This command runs before the actual update instructions to grab table metadata. Naturally, the metadata obtained in this way is then used to construct INSERT, DELETE, and UPDATE commands. Bear in mind that only the fields returned by the SelectCommand query will actually be inserted or modified. For example, if SelectCommand points to
oCMD.SelectCommand.CommandText = strSelectCmd;
oCMD.SelectCommand.ActiveConnection = oCN;
then all the UPDATE statements will have the form of:
SELECT * FROM Names
Conversely, should SelectCommand look like this
UPDATE Names SET f1=v1,..., fn=vn
then the resulting UPDATE statement takes another form:
SELECT f1, f2 FROM Names
All the other values are discarded. While the Update method can infer the content for InsertCommand, DeleteCommand, and UpdateCommand, you must always specify a value for the SelectCommand property.
UPDATE Names SET f1=v1, f2=v2
On the other hand, properties like InsertCommand, DeleteCommand, and UpdateCommand provide a lot of flexibility. In fact, you can use stored procedures or custom commands to submit your changes to the actual data store. This is where ADO.NET significantly improves upon the ADO behavior.
Bear in mind that all the changes made to a DataSet or a DataTable object, even when committed through AcceptChanges, are only made in memory. To make these changes permanent in your data store, you must use Update and become familiar with SelectCommand and the other properties.
It took four column installments to cover ASP.NET data-binding and DataGrids, so a recap seems in order. Here are some concepts you need to remember:
In my next column I'll be back again in the world of ASP.NET data-binding to explore and compare two other list-bound server controls: the DataList and the Repeater.
- Save the data you need to display through the DataGrid in a session item. You can do this either in the global.asax file or when the page first loads. Retrieve such data from the Session object whenever you need it to refresh the user interface and rebind data-bound controls.
- The DataGrid server control is extremely versatile and allows you to do just about anything you can think of. You can explicitly bind columns to a data field as well as create link or button columns that fire an action when clicked. You can also exploit the built-in capability for paging and improve and customize it.
- If you think you need a custom layout for all the cells of a given column (because you want to transform a URL into an image, or a boolean value into a checkbox, for example), then templated columns are just what you need.
- Columns can be sorted in all directions, and there are ways to indicate this to the user through underlines and glyphs. Rows support selection and you can use different CSS styles to make this feature clear to the user. Selection makes it really straightforward to implement master/detail views.
- The DataGrid controls provide for inline data editing, and through templates you can use any sort of control and business logic. In doing so, the validator controls of ASP.NET play an important role. However, I also covered alternative approaches, such as separate forms where you display current data and allow for changes to be made.
- When you finish modifying a certain row, be aware that you are responsible for updating. The DataGrid doesn't provide any facility for this.
Send questions and comments for Dino to email@example.com.