Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Two way data binding in ASP.NET

0.00/5 (No votes)
10 Jun 2004 8  
How two use the design time services to provide 2 way data binding in ASP.NET

Introduction

Aren't you tired of writing code like this in your WebForms?

...

// populate the webform with the customer data

lName.Text = cus.Name;
lSurname.Text = cus.Surname;
lEmail.Text = cus.Email;
lAddress.Text = cus.Address;
lAge.Text = cus.Age.ToString();

...

// get the customer data from the webform

cus.Name = lName.Text;
cus.Surname = lSurname.Text;
cus.Email = lEmail.Text;
cus.Address = lAddress.Text;
cus.Age = Int32.Parse(lAge.Text);

...
    

Me too. Unfortunately ASP.NET databinding is only one way so it isn't useful to avoid writing the above code (You can avoid some code using the DataBind method, but not all). After reading this article you'll be able to turn the above code to something like this:

...

// populate the webform with the customer data

bindingManager1.BindToWebForm(this);

...

// get the customer data from the webform

bindingManager1.BindFromWebForm(this); 

...
    

Background

The ASP.NET data binding mechanism works only in one way: When you add a databinding to an ASP.NET WebForm in design time, Visual Studio .NET adds a databinding expression to the ASPX file (<%# %><%# ... %> ). When the WebForm is requested for the first time, the ASPX file is parsed, JIT compiled and executed. For each data binding expression in a control, a handler is added to its DataBinding event. The DataBinding event is fired when the control's DataBind method is called, and the handlers added by the ASP runtime evaluates the data binding expression and performs the actual data binding. So far, so good.

At runtime we can only rely on ASP.NET to do one way data binding. If we only want to display information this works fine, but if we need to get data from a form, we have to get the values from the controls ourselves, which is a non-orthogonal mechanism and a tedious task.

If we were able to get the data bindings at run time, we could use reflection to perform 2 way databinding. However there is no way to get the data bindings at runtime. Of course, we can add properties in our controls to store the data bindings in order to fix that problem but i don't like that idea.

What to do then? Thanks to the design time architecture and reflection you'll have 2 way data binding without modifying any control (see Points of Interest).

Using the code

Using the BindingManager in order to have 2 way data binding is very easy:

  • Add the BindingManager component to the toolbox.
  • Add a BindingManager component to a WebForm.
  • Use the designer to set the databindings.
  • When you need to load the WebForm with your data call to the BindingManager's BindToWebForm method.
  • In order to get the data from the WebForm, you have to call to the BindingManager's BindFromWebForm method.

And that's all!

Sometimes you don't need 2 way data binding in all controls (for example, in a form to show/update customer data you can have a label that shows when was the customer account was created). If you click on the BindingManager component, and locate the data binding in the DataBinding collection you can set the TwoWay property to false to provide one way support only.

Points of Interest

How does the BindingManager work?

As we said before, there's no way to access to the data bindings at run time. Luckily for us, in design time, we can get the data binding information because the Control class implements the IDataBindingsAccessor interface. IDataBindingsAccessor has 2 properties: HasDataBindings and DataBindings, that can be used to obtain the databindings. The BindingManager stores the data binding information in a collection (DataBindingInfoCollection) and thanks to the services offered to the component designers, we could monitor any component change to keep the data binding information updated using the IComponentChangeService and the IReferenceService interfaces.

protected void UpdateDataBindings() 
{
  // create a new collection to store the new bindings found

  DataBindingInfoCollection newBindings = new DataBindingInfoCollection();

  // gets all web controls from the form

  IReferenceService service = (IReferenceService)GetService(
    typeof(IReferenceService));
  object[] references = service.GetReferences(typeof(Control));

  foreach(Control control in references){
    // if the control isn't in the page but it's a naming container, skip it

    if ((control.NamingContainer == null) || 
       (control.NamingContainer.GetType() != typeof(Page))) continue;
    
    // get the interface related to data binding

    IDataBindingsAccessor dba = (IDataBindingsAccessor)control;

    if (dba.HasDataBindings){
      foreach (DataBinding db in dba.DataBindings){
        // get the binding information for the control

        DataBindingInfo dbi = GetBindingInformation(db, control);

        // if the entry isn't new, set the old values

        UpdateDataBindingInfo(dbi, bindingManager.DataBindings);

        newBindings.Add(dbi);
      }
    }
  }

  // if the data bindings have changed

  if (CheckBindingChanges(bindingManager.DataBindings, newBindings)){
    // notify that the component is going to change

    RaiseComponentChanging(null);

    // update the bindings

    bindingManager.DataBindings.Clear();
    foreach(DataBindingInfo dbi in newBindings){
      bindingManager.DataBindings.Add(dbi);
    }

    // notify that the component has changed

    RaiseComponentChanged(null, null, null);
  }
}

We need to make available the data binding information to our WebForm in run time so we serialize that information to code thanks to a custom TypeConverter.

The class diagram is:

The actual data binding is implemented in two methods in our BindingManager: BindToWebForm and BindFromWebForm. BindToWebForm is easy to implement, because it uses the DataBinding architecture of ASP.NET so we have nearly all the work done. BindFromWebForm is more complex, because it has to iterate through the data bindings, and for each databinding, get the control's property type and compare it with the destination property. If they match, then it can perform the data binding. If not, it tries to convert from the source property type to the destination type before assigning the data. This conversion can be done successfully if the destination type has a TypeConverter that does the job or if it has a static method called Parse.

public void BindFromWebForm(Page form) 
{
  // iterate through the data bindings

  foreach (DataBindingInfo dbi in _dbic){
    // if the binding isn't two way, exit

    if (!dbi.TwoWay) continue;

    // get property to bind from

    object sourceProp = GetFieldOrPropertyEx(dbi.Control, dbi.PropControl);
    
    // if we can't get the property, error

    if (sourceProp == null) throw new DataBindingException(
        "DataBinding error, can't find: " + dbi.PropControl, dbi.Control);
    
    // get the full property name

    string fullPropObject = dbi.Object;
    if (dbi.PropObject != null){
      fullPropObject += "." + dbi.PropObject;
    }

    // check source and destination types

    Type sourcePropType = sourceProp.GetType();
    Type destPropType = GetFieldOrPropertyTypeEx(form, fullPropObject);

    if (destPropType == null){
      throw new DataBindingException(
        "DataBinding error, can't find: " + fullPropObject, dbi.Control);
    }

    // if the types doesn't match try to make a conversion

    if (!sourcePropType.Equals(destPropType)){
      try {
        sourceProp = ConvertTypes(sourceProp, destPropType);
      } catch (Exception e){
        throw new DataBindingException(
          "DataBinding error, can't convert types: ", e, dbi.Control);
      }

      if (sourceProp == null){
        throw new DataBindingException(
         "DataBinding error, can't convert bound property: " + 
          dbi.PropControl + " for: " + fullPropObject, dbi.Control);
      }
    }
    
    // do the data binding

    if (!SetFieldOrPropertyEx(form, fullPropObject, sourceProp)){
      throw new DataBindingException("DataBinding error, can't set: " 
           + fullPropObject, dbi.Control);
    }
  }
}

Notes

The actual implementation has some limitations:
  • When the BindingManager parses the data bindings through the IDataBindingAccessor interface it doesn't expect a complex data binding expression. For example: If you want to bind the full name of a customer to a label with something like <%# DataBinder.Eval(customer1, "Name") + " " + DataBinder.Eval(customer1, "Surname") %> it will fail. You can do the same with a panel and two labels, each one with a simple data binding expression (Label1 with: <%# DataBinder.Eval(customer1, "Name") %> and Label2 with <%# DataBinder.Eval(customer1, "Surname")) %>
  • By design, if the bound controls are inside a NamingContainer (for example, if you have a template column in a DataGrid), they don't get added to the binding collection of the BindingManager, because controls like the DataGrid aren't designed to support two way databinding. You can change this behaviour if you want

History

  • v1.0. Initial Release.
  • v1.1. Added support for int and string indexers and better type conversion.

Last comments

This is my first article and I don't have a lot of free time to write articles, so if you have enjoyed this one, please rate it and it will encourage me to write more.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here