Implementing two-way Data Binding for ASP.NET

Page 3 of 6
  1. Introduction
  2. How things work now
  3. Subclassing the Web controls for better data bindi
  4. How it works
  5. How it works (contd)
  6. Wrapping Up

Subclassing the Web controls for better data bindi

As you can see by the process above, there are a number of shortcomings in the data binding process. The process is quite repetitive and if we can delegate some of this functionality into the control itself or some helper class we could make life on us a whole lot easier.

My solution to this problem is to subclass the various Web form controls and add  functionality to them natively. The classes I'll describe now provide the following functionality:

  • Simple property based control binding
    Instead of the embedded script code that performs binding I added three properties to each control: BindingSource, BindingSourceProperty and Binding Property. The control source is an object on the Web Form – it could be a plain object
  • Two way binding
    Controls can be bound one way, two way or not at all. The binding process is controlled with a method call on a generic helper method or if you use a custom WebForm subclass a method call to the form (DataBind(), UnbindData()).
  • Error Handling
    When forms are rendered an error message is automatically set into the form property to let you see that the control could not be bound. When you perform unbinding any bind back errors are automatically handled and dumped into an error list that can easily be parsed and generated into an error message to display in your Web Form. I'll also show a simple way to display error hints on the control themselves.
  • Basic Display Formatting
    You can apply basic .Net display formatting to controls as they are rendered. Using standard format expressions ( such as {0:c} for currency or {0:f2} for fixed ) etc.

The implementation of this mechanism is based on a Strategy pattern where the actual controls have only small wrapper methods that call back to a worker class that actually performs the , unbinding for both the controls individually and the form as a whole.

The key classes involved in this solution are:

  • IwwWebDataControl
    This is the interface that each of the controls must implement it includes the BindingSourceObject, BindingSourceProperty, BindingProperty.
  • wwWeb<Control> subclasses
    All of the controls that are to be databound are subclassed from the standard control classes and implement the IwwWebDataControl interface. In the project provided TextBox, CheckBox, RadioButtonGroup, ListBox and DropDownList are subclassed since these are the most common ones you do two-way data binding with.
  • wwWebDataHelper
    This is the strategy class that handles all of the dirty work of actually binding the controls both individually and for the entire form. All the methods on this class are static and they receive parameters of the Web Page object and the controls they are binding.

Figure 2 – The custom data binding scheme uses custom subclasses of each of the form controls and optionally the Web Page itself. These controls implement the IwwWebDataControl interface the methods of which call out to the wwWebDataControlHelper class to perform the actual binding work.

Figure 2 shows the relationship between these classes. The concept is straight forward: each control is subclassed and implements the IwwWebDataControl interface. This interface serves two purposes: It provides the properties needed to handle the data binding in an easy property sheet based input mechanism using plain properties. It also serves as an identifier for the controls we want to bind when binding all controls of a Web form. The interface also has BindData() and UnbindData() methods which typically do nothing more than forward their property values to the wwWebDataHelper class and its methods. For example, an implementation of the BindData method in wwWebTextBox looks like this:

public void BindData(Page WebForm)
{
  wwWebDataHelper.ControlBindData(WebForm,this);
}

Listing 2 (a little further down) shows a full implementation of the wwWebTextBox class so you can get an idea of what goes into a full class that implements this interface. The idea is that the wwWebDataHelper class does all the logic for data binding rather than the control methods themselves, so that we can reuse the same code for binding. All the controls including list controls like the ListbBox and DropDownList can use the same mechanism for binding. Controls like the ListBox and DropDownList continue to work with list based binding as well as implementing the updated functionality so you can bind value based data to them as well.

You can optionally use the wwWebForm class which implements BindData() and UnbindData() and also overrides DataBind() to automatically call BindData(). Using this class is optional though as you can directly call the wwDataHelper.FormBindData() or wwDataHelper.FormUnbindData() methods.

The process to use these controls then is:

  1. Create a form and optionally subclass it from wwWebDataForm
  2. Add the wwWebDataControls.dll to your Toolbox in VS.Net
  3. Use the controls on your form
  4. Set the Binding properties to bind to data, objects or properties
  5. Call the appropriate form level binding methods

Figure 3 shows an example of a simple data entry form that displays Inventory data and allows editing of that data. It contains data that is retrieved from a SQL Server database using a business object which loads data into an internally exposed DataSet and DataRow member. These are then bound to the data.

Figure 3 – The sample form demonstrates the base elements of . Each of the controls on the form is bound to data. To bind or unbind the controls only a single method call each is required to the form's BindData and UnbindData methods respectively.

Adding Controls to the form

The first step before we get started is to add the wwWebDataControls.dll to the Toolbar. To do so follow these steps:

  1. Select the toolbox.
  2. Right click and select Add New Tab
  3. Type the name for this tab (West Wind Web Controls)
  4. Select the new tab
  5. Right click again and select Add/Remove Items
  6. Browse for the Installation directory for the samples and select the /wwWebDataControls/bin/Debug/wwWebDataControls.dll
    (for now) – later you'll probably want to install this to the GAC. For now it's handy to use the debug version so you can change the controls.
  7. Drag and drop the controls onto the form.

The controls use a default script tag prefix of ww which is registered against the DLL. This looks like this in script code for the header:

<%@ Register TagPrefix="ww" Namespace="Westwind.Web.Data"
Assembly="wwWebDataControls"%>

And like this for a control:

<ww:wwwebtextbox id="txtPrice" runat="server" size="20" 
BindingSourceObject="Inventory.DataRow"
BindingSourceProperty="Price" UserFieldName="Price"   
DisplayFormat="{0:c}">
</ww:wwwebtextbox>

If you use the toolbar though you won't have to do any of this manually. What's nice about this form is that other than the property assignments which are made in the property sheet (Figure 4) there's no code involved in the . It's very quick and easy to create the s in this fashion.

Figure 4 – All settings for data binding are made in one place in the property sheet.

Note that the data binding in this example binds against the retrieved DataRow of the business object, which is just a plain DataRow object. The BindingSourceProperty in this case references a field. But you can also bind to a DataSet and specify the TableName.Field syntax that you traditionally use. In this case the binding occurs against the first row of the table. The same is true if you use a DataTable or DataView as your binding source.

In addition you can also bind to objects or properties of the form. You can set the BindingSourceObject to this or me for example, and then bind against a property you've exposed on the form. If you have an object and properties you'd like to bind to you can do:

BindingSourceObject: Customer.Address
BindingSourceProperty: Street

Notice that you can step down the object hierarchy which implicitly starts at the form level (so this is actually this.Customer.Address). This is flexible if you use business objects that don't expose the underlying data directly or if you need to bind against objects that simply don't map to data (like configuration object or wizards etc.).

In addition notice that you can also specify a format flag. If you look at Figure 3 again you'll see for example that the Price is displayed with the $ in front of it. This field is using the {0:c} format flag to format currency, and when you save the data in this format the controls automatically allow the conversion back into the numeric value using Parsing. If an error occurs during the bindback the field is flagged – I'll come back to that a little later.

Binding the entire form

Besides setting the properties of the controls in the property sheet there are only two things that you need to do in code: Call the appropriate method to bind and then unbind the data when you save. Listing 1 shows the key elements of the Inventory Form.

Listing 1 – The Inventory Form's key action methods

private void Page_Load(object sender, System.EventArgs e)
{
    if (!this.IsPostBack) {
          Inventory = new busInventory();

          // *** Get Item list into a table TItemList
          Inventory.GetItemList("sku,descript");

          // *** Do standard  for the list
          this.txtSearchSku.DataSource = Inventory.DataSet.Tables["TItemList"];
          this.txtSearchSku.DataBind();
    }
}
private void btnSearch_Click(object sender, System.EventArgs e)
{
    string Sku = this.txtSearchSku.SelectedValue;
    if (Sku == null || Sku.Length < 1)
    {
          this.ShowErrorMessage("Invalid Sku selected...");
          return;
    }

    Inventory = new busInventory();
 
    // *** Load the item - will load Item.DataSet and Item.DataRow
    if ( !Inventory.LoadBySku(Sku)  )
    {
          this.ShowErrorMessage(Inventory.ErrorMsg);
          return;
    }

    // Save the current Sku so we can retrieve it
    // later when we save
    ViewState.Add("Sku",Sku);

    InvTable = Inventory.DataSet.Tables["wws_items_Record"];

    // *** Now bind to data
    wwWebDataHelper.FormBindData(this);
}

private void btnSubmit_Click(object sender, System.EventArgs e)
{
    Inventory = new busInventory();

    string Sku =(string)  ViewState["Sku"];
    if (Sku == null) {
          this.ShowErrorMessage("Can't save this item - invalid Sku");
          return;
    }

    // Load the existing item
    // Creates Inventory.DataRow member that the form is bound to
    Inventory.LoadBySku(Sku);

    // Now Update the bound fields (DataRow) from the form
    wwWebDataHelper.FormUnbindData(this);

    if (!Inventory.Save())
    {
          this.ShowErrorMessage(Inventory.ErrorMsg);
          return;
    }

    // *** Rebind with the new values as saved
    wwWebDataHelper.FormBindData(this);

    this.ShowErrorMessage("Inventory Item Saved...");
    //Response.Redirect("Itemlist.aspx");
}

Page_Load does traditional list based binding for the dropdown list against a DataTable. This mechanism actually uses the built-in data binding which is automatically inherited and still works as you would expect normally. But that control can also act as a simple data binding control against the SelectedValue. When the form first loads the drop down is loaded up, but the item below is left blank until a selection is made.

The selection is handled by the btnSearch_Click method, which instantiates a business object and based on the selection in the list retrieves the required item. The result of this operation is the Inventory.DataSet and Inventory.DataRow are set and this is going to be the target of our .

When the data has loaded the data binding is activated with a call to:

wwWebDataHelper.FormBindData(this)

That's it. If you use wwWebForm then you can also call this.DataBind() to accomplish the same thing but also cause any standard data binding to occur.

The process to get the data back out is not any more complicated and shown in btnSubmit_Click. Here the business object is loaded up with the sku again (this time retrieved from ViewState as an extra check to make sure an item is actually selected). Once the item is loaded there is again an Inventory.DataSet and Inventory.DataRow object in place which matches what the form controls are bound to. Now a call is made to:

wwWebDataHelper.FormUnbindData(this);

And the data is bound back into the underlying DataSet. After that a simple call to the business object's Save() method causes the data to actually be written to the database.

Couldn't be easier, right? There's very little UI code in this block, and because of the business object there's no SQL code splattered over this WebForm code either. And that really is the concept behind this process – you don't ever have to write bind back code manually again as this takes care of it automatically. Not only that, this process also handled the data conversions and error handling (which I'll describe shortly).

The combination of using even this simple business object (the source code for this sample busSimpleBusObj class is provided with the samples) and this data binding mechanism reduce the amount of code that has to happen for form management logic drastically.

You might also like...

Comments

Contribute

Why not write for us? Or you could submit an event or a user group in your area. Alternatively just tell us what you think!

Our tools

We've got automatic conversion tools to convert C# to VB.NET, VB.NET to C#. Also you can compress javascript and compress css and generate sql connection strings.

“UNIX is basically a simple operating system, but you have to be a genius to understand the simplicity.” - Dennis Ritchie