Two way data binding in ASP.NET






4.68/5 (66 votes)
Feb 25, 2004
5 min read

315270

4732
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
'sBindToWebForm
method. - In order to get the data from the WebForm, you have to call to the
BindingManager
'sBindFromWebForm
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 theIDataBindingAccessor
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 aDataGrid
), they don't get added to the binding collection of theBindingManager
, because controls like theDataGrid
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.