asp tutorials, asp.net tutorials, sample code, and Microsoft news from 15Seconds
Data Access  |   Troubleshooting  |   Security  |   Performance  |   ADSI  |   Upload  |   Email  |   Control Building  |   Component Building  |   Forms  |   XML  |   Web Services  |   ASP.NET  |   .NET Features  |   .NET 2.0  |   App Development  |   App Architecture  |   IIS  |   Wireless
 
Pioneering Active Server
 Power Search










Active News
15 Seconds Weekly Newsletter
• Complete Coverage
• Site Updates
• Upcoming Features

More Free Newsletters
Reference
News
Articles
Archive
Writers
Code Samples
Components
Tools
FAQ
Feedback
Books
Links
DL Archives
Community
Messageboard
List Servers
Mailing List
WebHosts
Consultants
Tech Jobs
15 Seconds
Home
Site Map
Press
Legal
Privacy Policy
internet.commerce














internet.com
IT
Developer
Internet News
Small Business
Personal Technology
International

Search internet.com
Advertise
Corporate Info
Newsletters
Tech Jobs
E-mail Offers

HardwareCentral
Compare products, prices, and stores at Hardware Central!

Beyond the DataGrid: An Architectural View of the Data Source Model in ASP.NET 1.x and 2.0
By Dino Esposito
Rating: 3.5 out of 5
Rate this article


  • email this article to a colleague
  • suggest an article

    Introduction

    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:

    void PageIndexChanged(object sender, DataGridPageChangedEventArgs e)
    {
        DataGrid grid = (DataGrid) sender;
        grid.CurrentPageIndex = e.NewPageIndex;
    
        // Bind data and refresh
        grid.DataSource = ...;
        grid.DataBind();
    }
    

    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:

    protected virtual void OnPageIndexChanged(
        object sender, DataGridPageChangedEventArgs e)
    {
        this.CurrentPageIndex = e.NewPageIndex;
    
        // LOOK OUT: Bind data and refresh
        this.DataSource = ...;
        this.DataBind();
    }
    

    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.

    protected virtual void OnPageIndexChanged(
        object sender, DataGridPageChangedEventArgs e)
    {
        this.CurrentPageIndex = e.NewPageIndex;
        OnUpdateView(EventArgs.Empty); 
    }
    

    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:

    void UpdateView(object sender, EventArgs e)
    {
        grid.DataSource = ...;
        grid.DataBind();
    }
    

    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:

  • Rate This Article
    Not HelpfulMost Helpful
    1 2 3 4 5
    Other Articles
    Apr 21, 2005 - Building a FAQ Module for the ASP.NET Community Starter Kit
    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]
    Aug 25, 2004 - Developing Web Parts with the ICellProvider Interface
    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]
    Aug 4, 2004 - Accessibility Improvements in ASP.NET 2.0 - Part 2
    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]
    Jul 30, 2004 - Connectable Web Parts in SharePoint Portal Server 2003 - Part 1
    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]
    Jul 27, 2004 - Accessibility Improvements in ASP.NET 2.0 - Part 1
    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]
    Jun 30, 2004 - Simplified and Extended Data Binding Syntax in ASP.NET 2.0
    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]
    May 18, 2004 - ASP.NET 2.0 Caching Features
    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]
    May 11, 2004 - SharePoint Security and .NET Impersonation
    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]
    May 4, 2004 - Creating a Flexible Configuration Section Handler
    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]
    Apr 21, 2004 - Creating a Web Custom Control
    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.

    Support the Active Server Industry



    JupiterOnlineMedia

    internet.comearthweb.comDevx.commediabistro.comGraphics.com

    Search:

    Jupitermedia Corporation has two divisions: Jupiterimages and JupiterOnlineMedia

    Jupitermedia Corporate Info


    Legal Notices, Licensing, Reprints, & Permissions, Privacy Policy.

    Advertise | Newsletters | Tech Jobs | Shopping | E-mail Offers