Most developers learnt it in the field already, found their own countermeasures, and live happy today.
Other developers just wait for Microsoft to fix it in the next version of ASP.NET and perhaps live restless
and uneasy today. What am I talking about? The leading topic of this article is the code needed around the
DataGrid control when you use it in the real world of rich and data-driven Web applications.
As a matter of fact, the Datagrid control requires too much code even for simple scenarios. This is
largely boilerplate code with just a few entry points; mostly SQL commands, parameters, and connection
strings. In ASP.NET 1.x, the DataGrid control provides a rich infrastructure that backs a variety of
events and actions, including readonly views of data, selection and detail views, sorting, paging,
and in-place editing. Being a data-bound control, the DataGrid obviously requires some sort of
interaction with the data source to complete any of these operations. That's just where the rub
lies. How should the programmer code these actions? Or, better yet, should the programmer get the
call to write it?
In ASP.NET 2.0, a bunch of new data-bound controls (specifically, GridView, DetailsView, and FormView)
support a new data model and provide basic operations (Select, Insert, Update, Delete -- collectively
known as SIUD from their initials) with virtually no programming efforts on your own. To get the same,
in ASP.NET 1.x you need a thick layer of (repetitive) code to be reused over and over again.
In ASP.NET 1.x, you find two controls which support a rich programming interface around a data
source -- the DataList and DataGrid controls. Both provide an extensive eventing model for callers
to be notified of any user action; both controls, though, lack a deeper layer of code to take care
of SIUD actions. These controls fire an event when the user clicks to save any changes, but that's
all. Attaching some non-trivial code to events is still left to the programmer.
In this article, I'll review the mechanics of the DataGrid control in ASP.NET 1.x when it comes to
finalizing common SIUD operations. I'll illustrate and analyze bits and pieces of the necessary code
and build a wrapper control that incorporates it in the folds of a few protected, virtual methods.
Where Did I Write This Code Already?
As far as the DataGrid control is concerned, the code that handles data-related events is nearly identical
in all scenarios, and can be easily labeled as 'boilerplate code', at least in its underlying pattern.
Let's review a quick example: paging.
To have the DataGrid show a different page, you need to add a handler for the PageIndexChanged event.
This isn't an issue per se but, wait, look at the required code:
As you can see yourself, nothing in the preceding code is really context-sensitive and specific to the
application's logic and problem domain. The handler needs to accomplish two tasks: set the new page
index and rebind the control to its data to reflect the new page. You never need to do less; in most
common scenarios, you don't even need to do anything more either. Is there any valid reason why the
developers have to write this same code over and over again, for each and every DataGrid they find on the way?
Sure, there might be situations in which a more careful handling of the event is in order.
For example, you might have the need to compare old and new page numbers and block the transition
based on any combination of runtime conditions. How would you deal with this requirement?
I'm not saying that a page-changed event is the absolute incarnation of the evil; I'm simply suggesting
that developers should be kept away from writing "repetitive" code that could be otherwise
embedded in some sort of framework. What would be a better approach? Take a look below:
Imagine a new class derived from DataGrid that implements an internal handler for the PageIndexChanged
event. This method would be marked as protected and virtual thus allowing for further derived classes
to override it at will. The member would do the usual job that the page-changed event requires on a
grid -- setting the new page index and refreshing the data in the grid.
So why did the team decide to expose PageIndexChanged as a dummy event with no embedded, basic logic?
One of the best reasons I can think of is that there's no commonly accepted way to retrieve and bind
data to controls. By leaving the burden of writing the handler on the programmer's shoulders, the
ASP.NET team hit two birds with a single stone, so to speak. On one hand, they kept the size of the
control lean and mean; on the other hand, they left everybody free of using the data binding mechanism
of choice. To exemplify, developers can either execute a new query per each request or cache some data
in the session or application state.
In my humble opinion, a better way to approach things with no intervention on the underlying architecture
(like in ASP.NET 2.0) does exist. It entails separating data retrieval from other data operations.
The new DataGrid, therefore, will fire an additional event to collect data. Let's call this event UpdateView.
There's a clear benefit in using this code. The page developer only needs to write the UpdateView event
handler once and it will serve any events you handle internally (e.g., sorting, updating, deleting).
Here's a typical implementation of the UpdateView event in the host page:
When you use a DataGrid-based control like this, you only need to define how it retrieves and binds data,
and whether or not you want it to page and sort data. All the details of these operations will be handled
automatically by hard-coded handlers. Should you have the need to override the way in which paging or
sorting is accomplished, you just derive a new class and override the corresponding virtual methods.
Easy, effective and, more importantly, object-oriented.
What if you need to differentiate the way data binding takes place based on the event which triggered it?
Good point. The best thing you can do is add some custom data to the UpdateView event to include a
value from an enumerated type. This value would indicate the reason for the call.
Here's an example of the delegate:
public delegate void UpdateViewHandler(
object sender, UpdateViewEventArgs e);
The event data structure would look like the class below:
public class UpdateViewEventArgs : EventArgs
{
// Indicates the latest operation performed:
// Update, Delete, Page, Sort, ...
UpdateViewMode Mode;
// More members here
:
}
Especially when the grid's view is refreshed after an update, there might be the need to reload data from disk.
This condition is notified by assigning the Mode property an appropriate value.
The Long and Winding Road to Update Records
All in all, paging and sorting are simple operations to accomplish, at least from the DataGrid's perspective.
Paging requires only a few lines of code and raises no significant issues during implementation.
Sorting is different, though. There are two key ways in which sorting can be accomplished: in memory
or through the underlying database system. How would you write the internal handler for the SortCommand
event? That mostly depends on your preferences and expectations. You can opt for the implementation
that you reckon most likely and leave the door open to any in-flight modification through an event.
In doing so, you at least set a default implementation that can save programmers code in the average case.
Updating records is another story that presents two key differences: you need to collect data from the
input controls in the DataGrid and execute a command against a data source. The input data must be
bound to command parameters in order for the command to run; in addition, a variety of data sources
exist each requiring a different API (read, ADO.NET data provider).
The issue is brilliantly solved in ASP.NET 2.0 where the introduction of the provider model makes
it possible to expose a unique programming interface to serve virtually any data source -- be it
SQL Server, Oracle, XML files, and so on. The figure below illustrates the big picture of data
source objects in ASP.NET 2.0.
Figure 1 - The big picture of data-bound controls and data source objects in ASP.NET 2.0
How can you simulate the above pattern in ASP.NET 1.x? Designing a downlevel provider model is
still an option; however, it might be overkill if you know exactly what type of data source you'll
be targeting. For a more general look at the provider model for ASP.NET 1.1 applications, take a
look at the following two articles on the ASP.NET Developer Center:
For more information about ASP.NET 2.0 data source controls, you can check out Chapter 6 and 7
of my book "Introducing Microsoft ASP.NET 2.0" from Microsoft Press, 2004.
Let's go for a simpler, but not less effective, model largely inspired by the ASP.NET 2.0 GridView
control. The new DataGrid control incorporates an handler for the UpdateCommand event. The handler
does some internal work and then fires a more specific event to the host page. The new event,
tentatively named UpdateCommandCreated, will give page developers a chance to instantiate the
command object to use to carry out the update. The page event handler receives a dictionary
filled with the new values typed in the editable fields and builds a command object with connection
and parameters information. When the handler returns, the control has enough information to execute
the command and refresh the user interface. Would a control like this be as flexible as the original
DataGrid? You bet. The original UpdateCommand event is still available and can be used to override
any pre-set behavior. Let's see some code.
Building a Super DataGrid Control in ASP.NET 1.x
The new grid class inherits from DataGrid and wires up to some events in the constructor. Here's how:
public class GridView : DataGrid
{
public GridView() : base()
{
PageIndexChanged += new
DataGridPageChangedEventHandler(DataGrid_PageIndexChanged);
UpdateCommand += new
DataGridCommandEventHandler(GridView_UpdateCommand);
EditCommand += new
DataGridCommandEventHandler(GridView_EditCommand);
CancelCommand += new
DataGridCommandEventHandler(GridView_CancelCommand);
}
:
}
The new GridView class has now built-in capabilities to handle Page-Changed, Edit, Update, and Cancel events
internally. No handler code is required on the host page; yet a standard and acceptable behavior is guaranteed.
This is nearly identical to what happens with the GridView control in ASP.NET 2.0.
The sample grid control provides a bunch of new custom events, as below:
public event UpdateViewEventHandler UpdateView;
public event UpdateCommandCreatedEventHandler UpdateCommandCreated;
protected virtual void OnUpdateCommandCreated(object sender,
UpdateCommandCreatedEventArgs e)
{
if (UpdateCommandCreated != null)
UpdateCommandCreated(sender, e);
}
protected virtual void OnUpdateView(object sender,
UpdateViewEventArgs e)
{
if (UpdateView != null)
UpdateView(sender, e);
}
As mentioned earlier, the UpdateView event asks the page code to refresh the view; the UpdateCommandCreated
event gives the page code a chance to define the command to save any changes. The UpdateCommandCreated event
simplifies the programming model of the 1.x DataGrid when it comes to persisting the changes.
Let's take a look at a few of these built-in handlers:
private void DataGrid_PageIndexChanged(object sender,
DataGridPageChangedEventArgs e)
{
// Set the new page index
CurrentPageIndex = e.NewPageIndex;
// Update the view
UpdateViewEventArgs uvea = new UpdateViewEventArgs();
uvea.Mode = UpdateViewMode.PageChanged;
OnUpdateView(this, uvea);
}
private void GridView_EditCommand(object source,
DataGridCommandEventArgs e)
{
// Determine the row in edit mode
EditItemIndex = e.Item.ItemIndex;
// Update the view
UpdateViewEventArgs uvea = new UpdateViewEventArgs();
uvea.Mode = UpdateViewMode.Refresh;
OnUpdateView(this, uvea);
}
private void GridView_CancelCommand(object source,
DataGridCommandEventArgs e)
{
// Cancel edit mode for the current row
EditItemIndex = -1;
// Update the view
UpdateViewEventArgs uvea = new UpdateViewEventArgs();
uvea.Mode = UpdateViewMode.Refresh;
OnUpdateView(this, uvea);
}
The UpdateView event passes a custom data structure to the handler. The Mode property of this
structure -- named UpdateViewEventArgs -- indicates the reason why the view needs to be updated.
To enter in edit mode, it suffices that you assign a 0-based value to the EditItemIndex property
and refresh the view. To cancel editing, assign -1 to the same property. What if, instead,
changes have been entered?
Through the UpdateCommandCreated event, the GridView control asks the page code to create and
return a fully configured command object. This command object will be then accessed through
the IDbCommand generic interface and executed. Here's the data structure that accompanies the event:
public class UpdateCommandCreatedEventArgs : EventArgs
{
public IDbCommand Command;
public bool ContinueUpdate;
public HybridDictionary NewValues;
}
The Command property contains the command object to execute to persist changes; the Boolean
ContinueUpdate property indicates whether the control should remain in edit mode after saving.
Finally, the NewValues dictionary contains the changed values to be used to prepare the command.
The most critical point is, how would you collect the new values for the command?
When the grid enters edit mode, it displays the contents of the various cells through dynamically
generated textboxes. These textboxes occupy a fixed position in the overall layout but have a
random generated ID. It is not safe to try to guess their ID; it is acceptable to access them
by position. The following listing shows the UpdateCommand internal handler:
private void GridView_UpdateCommand(object source,
DataGridCommandEventArgs e)
{
// Get a dictionary of new values in the row being edited
HybridDictionary newValues = ExtractRowValues(e.Item);
UpdateCommandCreatedEventArgs uccea;
uccea = new UpdateCommandCreatedEventArgs();
uccea.NewValues = newValues;
uccea.ContinueUpdate = false;
OnUpdateCommandCreated(this, uccea);
// Check the command object returned by the handler
IDbCommand cmd = uccea.Command;
if (cmd == null)
return;
// Execute the command
cmd.Connection.Open();
cmd.ExecuteNonQuery();
cmd.Connection.Close();
// Continue update?
if (!uccea.ContinueUpdate)
EditItemIndex = -1;
// Update the view
UpdateViewEventArgs uvea = new UpdateViewEventArgs();
uvea.Mode = UpdateViewMode.RecordUpdated;
OnUpdateView(this, uvea);
}
The function ExtractRowValues retrieves the values typed in the textboxes and stores them
in the specified dictionary for further use.
private HybridDictionary ExtractRowValues(DataGridItem item)
{
HybridDictionary newValues = new HybridDictionary(true);
// Works if bound columns have been explicitly added
bool shouldExtractValues = true;
if (Columns.Count >0)
{
// Check the type and ensure there's no r/w template field
for (int i=0; i<Columns.Count; i++)
{
DataGridColumn col = Columns[i];
if (col is TemplateColumn)
{
if ((col as TemplateColumn).EditItemTemplate != null)
shouldExtractValues = false;
}
if (col is BoundColumn)
{
if (!(col as BoundColumn).ReadOnly)
{
BoundColumn bc = (col as BoundColumn);
TextBox txt = (item.Cells[i].Controls[0] as TextBox);
if (txt != null)
newValues.Add(bc.DataField, txt.Text);
}
}
}
// If any editable template columns are found,
// let the page extract values
if (!shouldExtractValues)
return null;
// Add data-key field
if (DataKeyField != "")
newValues.Add(DataKeyField, DataKeys[item.ItemIndex]);
return newValues;
}
}
The function scans the objects in the Columns collection to see if there's any template column.
If so, the dictionary is returned empty and the page code is responsible for retrieving new values
programmatically. This is exactly what happens today with the DataGrid's standard UpdateCommand and
is identical to what will happen tomorrow with the ASP.NET 2.0 GridView control.
Note that if column autogeneration is on (the AutoGenerateColumns property), the Columns collection
is always empty and so it is the dictionary of changed values. By design, the DataGrid hides the real
collection of columns it is going to render and there's no trick I can think of to know about that
collection at the time the UpdateCommand event is normally fired. (Due to architectural differences,
this limitation ceases to exist with the 2.0 GridView control.)
In the end, changed values are successful read only if columns are bound through the <columns>
section -- which, incidentally, is the most common case. The NewValues dictionary also includes an
entry for the field bound to the DataKeyField property and used as the primary key of the data set
displayed. The following listing shows a possible page code handler for the UpdateCommandCreated event:
void grid_UpdateCommandCreated(object sender,
Expoware.UpdateCommandCreatedEventArgs e)
{
SqlConnection conn = new SqlConnection("…");
SqlCommand cmd = new SqlCommand(
"UPDATE customers SET country=@country " +
"WHERE customerid=@customerid", conn);
cmd.Parameters.Add("@country", e.NewValues["country"]);
cmd.Parameters.Add("@customerid", e.NewValues["customerid"]);
// Pass the command object back to the control
e.Command = cmd;
// Stop update
e.ContinueUpdate = false;
}
In this case, the database to update is SQL Server; hence, a new SqlCommand object is created and configured
with parameters. Values for the parameters are read out of the NewValues dictionary from the event data. When
done, the command object is attached to the Command property of the event data structure and passed back to
the control's code.
The figure below demonstrates a sample grid that provides paging and editing capabilities. Setting up this
grid requires far less code than it is necessary with classic DataGrid control. Take this approach to the
limit and you'll get the virtually codeless data binding of ASP.NET 2.0.
Figure 2 - The sample GridView control in action on a sample page
Pondering Over
The ASP.NET 1.x DataGrid control is extremely powerful and highly customizable but requires developers to write
a lot of code even for common scenarios. The DataGrid allows you to control every single step of its activity
through events. While this provides you with great flexibility, it also represents a relevant drag. With a bit
of abstraction, basic principles of object-oriented programming, and a new and more specific eventing model you
can come up with a new grid control that maintains the same core functionalities and saves you several lines of code.
How do you get to that? Elementary; you just apply to ASP.NET 1.x some design concepts introduced with ASP.NET
2.0 -- the road ahead. The companion code available for download demonstrates a 1.x GridView control with an
enhanced eventing model that puts the control in the middle between the 1.x DataGrid and 2.0 view controls.
As mentioned at the beginning of the article, I believe that most developers have implemented similar solutions
already. (As I see it, it was a sort of survival trick.) The next big question is, any way to reuse some of
this code in ASP.NET 2.0? No hope you can reuse anything as long as your code lives inside host pages or
related code-behind classes. If you have factored it out and clearly separated host pages from update logic,
you have better chances to reuse it to some extent by writing your own data source object to bind to ASP.NET 2.0
view controls. For more information, look here:
This sample chapter from Packt Publishing's "Building Websites with the ASP.NET Community Starter Kit"
illustrates how to build a new module on top of the existing code in the ASP.NET Community Starter Kit (CSK).
Using a Frequently Asked Questions (FAQ) module as an example, it shows how creating a new module allows you
to add entirely new features which integrate seamlessly with the rest of the framework. [Read This Article][Top]
Most default SharePoint Server Web Parts can be connected across organizations. The second article in this series shows how
to develop connectable Web Parts that provide information to other Web Parts. [Read This Article][Top]
Alex Homer continues to highlight some of the new ASP.NET 2.0 accessibility features. These features make it easier for visually impaired users to view and navigate Web sites and provide better support for alternative types of browsers and user agents. [Read This Article][Top]
Most default SharePoint Server Web Parts can be connected across organizations. The first article in this series explains how to connect existing Web Parts using the connection Interface classes in the SharePoint architecture. [Read This Article][Top]
Alex Homer highlights some of the new ASP.NET 2.0 accessibility features. These features make it easier for visually impaired users to view and navigate Web sites and provide better support for alternative types of browsers and user
agents. [Read This Article][Top]
Alex Homer discusses the simplification of, and extensions to, the ASP.NET 1.x data binding syntax, the new two-way data binding syntax for updating data sources, and the new syntax for binding to XML data in ASP.NET 2.0. [Read This Article][Top]
This article examines some of the new and exciting caching features in ASP.NET 2.0 and shows how to implement them in Web applications. [Read This Article][Top]
When implementing custom components that require access to restricted resources, implicit impersonation must be used. Jay Nathan shows how to create a class that makes using .NET Impersonation a snap. [Read This Article][Top]
Jeff Gonzalez demonstrates how to create a flexible configuration section
handler using C#. He provides a summary background of the .NET
configuration system, explains why the system is useful, and shows how it can be extended. [Read This Article][Top]
Conrad Jalali shows how to build Web custom controls by creating one that displays checkboxes in a categorized, hierarchical view. [Read This Article][Top]
Mailing List
Want to receive email when the next article is published? Just Click Here to sign up.