Implementing two-way Data Binding for ASP.NET

How it works

To make this simplified data binding code happen requires a little work, and the main concept behind this is subclassing and then delegation to worker classes that do the dirty work. The hardest part to using this stuff most likely will be to remember to use these subclassed controls rather than the built in ones.

To see how this works let's start by looking at the wwWebTextBox class and how it subclasses the standard Web TextBox. The code for this is shown in Listing 2. Note that there is a little bit of code omitted here that deals with a few non-related issues such as password value assignments and rendering error messages. What you see in Figure 2 is the core code needed to implement a two-way data binding control (you can review the samples code for the full source).

Listing 2 – TextBox implementation of the IwwWebDataControl interface

[ToolboxBitmap(typeof(TextBox)),
DefaultProperty("Text"),
ToolboxData("<{0}:wwWebTextBox runat='server' size='20'></{0}:wwWebTextBox>")]
public class wwWebTextBox : WebControls.TextBox,IwwWebDataControl
{

[Category("Data"),
Description("The object that the control is bound to (DsCustomers or Customer.DataRow or Customer)"),
DefaultValue("")]
public string BindingSourceObject
{
    get { return this.cBindingSourceObject; }
    set { this.cBindingSourceObject = value; }
}
string cBindingSourceObject = "";

[Category("Data"),
Description("The property of the object that the control is bound to ("),
DefaultValue("")]
public string BindingSourceProperty
{
    get { return this.cBindingSourceProperty; }
    set { this.cBindingSourceProperty = value; }
}
string cBindingSourceProperty = "";

[Category("Data"),
DefaultValue("Text")]
public string BindingProperty
{
    get { return this.cBindingProperty; }
    set { this.cBindingProperty = value; }
}
string cBindingProperty = "Text";

/// <summary>
/// Error message set when a data unbinding error occurs.
/// Optional - Default message: 'Invalid Data Format for 'control name'
/// </summary>
[Category("Data"),
DefaultValue("")]
public string BindingErrorMessage
{
    get { return this.cBindingErrorMessage; }
    set { this.cBindingErrorMessage = value; }
}
string cBindingErrorMessage;

/// <summary>
/// the format string used to format this field when binding
/// </summary>
[Category("Data"),
Description("The format string used to format this field when binding"),
DefaultValue("")]
public string DisplayFormat
{
    get { return this.cDisplayFormat; }
    set { this.cDisplayFormat = value; }
}
string cDisplayFormat = "";

/// <summary>
/// The format string used to format this field when binding.
/// Defaults to fieldname with txtStripped.
/// </summary>
[Category("Data"),
Description("The format string used to format this field when binding."),
DefaultValue("")]
public string UserFieldName
{
    get
    {
          if (this.cUserFieldName == String.Empty)
                this.UserFieldName = this.ID.Replace("txt","");
          return this.cUserFieldName;
    }
    set {this.cUserFieldName = value;}
}
string cUserFieldName = "";



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

public void UnbindData(Page WebForm)
{
    wwWebDataHelper.ControlUnbindData(WebForm,this);
}

}

The key here is the implementation of the properties and methods of the IwwWebDataControl interface which is defined as follows:

IwwWebDataControl Property

Description

BindingSourceObject

The object that the control is bound to. This will be a DataSet, DataRow, DataTable/View or it could be a custom object on the form. Syntax can use . syntax like: Customer.DataRow.

BindingSourceProperty

This is the property or field that the data is bound to.

BindingProperty

This is the property of the control that the binding occurs against.

DisplayFormat

A format string that is compatible with String.Format() for the specified type. Example: {0c} for currency or {0:f2} for fixed 2 decimals

UserFieldName

Descriptive name of the field. Used if an error occurs to provide an error message.

BindingErrorMessage

Internally used value that gets set if a unbinding error occurs. Controls that have this set can optionally generate error information next to them.

IwwWebDataControl Method

Description

BindData()

Binds data to the control from the BindingSource

UnbindData()

Unbinds data back into the BindingSource

If you look at the code for wwWebTextBox you'll see that there really is nothing there except forwarding calls to wwWebDataHelper, which actually performs all the hard work of doing the .

wwWebDataHelper is a class with all static members. The class works essentially by using Reflection to evaluate the value in the data source and in the control and then assigning the value into one or the other depending on whether you are binding or unbinding. To help with the Reflection tasks there's another helper class – wwUtils – which includes wrapper methods that do things like GetProperty, GetPropertyEx, SetProperty and SetPropertyEx. These methods use the PropertyInfo (or FieldInfo) classes to retrieve the values. The Ex versions provide a little more flexibility by allowing you to walk an object hierarchy and by retrieving and setting value further down the object chain. For example you can do:

wwUtils.SetProperty(this,"Customer.Address.Street","32 Kaiea")

which is lot more friendly than the 3 long Reflection calls you'd have manually write to get there. Let's start with Control binding and unbinding which is shown in Listing 3.

Listing 3 – Binding a control with data from a datasource

public static void ControlBindData(Page WebPage,
                IwwWebDataControl ActiveControl)  {
    string BindingSourceObject = ActiveControl.BindingSourceObject;
    string BindingSourceProperty = ActiveControl.BindingSourceProperty;
    string BindingProperty = ActiveControl.BindingProperty;

    try
    {
          if (BindingSourceObject == null || BindingSourceObject.Length == 0 ||
            BindingSourceProperty == null || BindingSourceProperty.Length == 0)
                return;

          // *** Get a reference to the actual control source object
          object loBindingSource = null;

          loBindingSource = wwUtils.GetPropertyEx(WebPage,BindingSourceObject);

          if (loBindingSource == null)
                return;

          // *** Retrieve the control source value
          object loValue;

          if (loBindingSource is System.Data.DataSet)
          {
                string lcTable = BindingSourceProperty.Substring(0,
                                    BindingSourceProperty.IndexOf("."));
                string lcColumn = BindingSourceProperty.Substring(
                                  BindingSourceProperty.IndexOf(".")+1);
                DataSet Ds = (DataSet) loBindingSource;
                loValue = Ds.Tables[lcTable].Rows[0][lcColumn];
          }
          else if(loBindingSource is System.Data.DataRow)
          {
                DataRow Dr = (DataRow) loBindingSource;
                loValue = Dr[BindingSourceProperty];
          }
          //… DataTable, DataView omitted
          else // we have a property
                loValue = wwUtils.GetPropertyEx(loBindingSource,
                                            BindingSourceProperty);

          /// *** Figure out the type of the control we're binding to
          object loBindValue = wwUtils.GetProperty(ActiveControl,
                                                    BindingProperty);
          string lcBindingSourceType = loBindValue.GetType().Name;

          if (loValue == null || loValue == DBNull.Value)
                if (lcBindingSourceType == "String")
                      wwUtils.SetProperty(ActiveControl,BindingProperty,"");
                else if (lcBindingSourceType == "Boolean")
                      wwUtils.SetProperty(ActiveControl,BindingProperty,false);
                else
                      wwUtils.SetProperty(ActiveControl,BindingProperty,"");
          else
          {
                if (lcBindingSourceType == "Boolean")
                  wwUtils.SetProperty(ActiveControl,BindingProperty,loValue);
                else
                {
                      if (wwUtils.Empty(ActiveControl.DisplayFormat))
                        wwUtils.SetProperty(ActiveControl,BindingProperty,
                                              loValue.ToString());
                      else
                        wwUtils.SetProperty(ActiveControl,BindingProperty,
                                  String.Format(ActiveControl.DisplayFormat,
                                  loValue));
                }
          }
    }
    catch(Exception ex)
    {
          string lcException = ex.Message;
          throw(new Exception("Can't bind " + ((Control) ActiveControl).ID );
    }
}

The code starts by retrieving the BindingSourceObject and tries to get a reference to the object. If that works it retrieves the property string. At this point a check is performed on what type of object is being bound against, which determines where the data comes from. If it's a DataSet – use the field of the first row of the table specified in the property string. If it's DataRow use the field. If it's an object use Reflection to retrieve the actual value.

Once we have a value we can then try and assign that value to the property specified in the BindingProperty. But before we can do that a few checks need to be made for the type of the property as well as checks for null values which would crash the controls if bound to. Yup this code actually automatically handles nulls by assigning empty values to display. The assignment of the value is done using Reflection again by using SetProperty(). Note that if a format string is provided the format is applied to the string as it's written out.

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.

“My definition of an expert in any field is a person who knows enough about what's really going on to be scared.” - P. J. Plauger