This is the last post in the series about data source controls and the new data binding infrastructure.
Just to sum up:
In Part 1 I explained the classes involved in the new data bound controls that ship with ASP.NET 2.0, and the "magic" behind DataSourceId and automatic data binding.
Part 2 was about the IDataSource interface and the internal details of the DataSourceControl class.
In Part 3 I talked about DataSourceView, which is the most important class for a data source control.
Now I'm going to explain what does the framework internally to support two way data binding.
A data binding expression is contained withing <%# and %> delimiters. Inside those delimiters you can place code that return a value, an Eval expression or a Bind expression. You have two way data binding only if you use a Bind expression.
The regular expressions associated with Eval and Bind are:
^\s*eval\s*\((?<params>.*)\)\s*\z
^\s*bind\s*\((?<params>.*)\)\s*\z
As you can see, they're similar, but as I'll point out later, unfortunately the framework limits Bind expressions.
I'll use an example to continue the explanation: a GridView data bound to an ObjectDataSource with some template columns with two way data binding (using Bind):
<asp:GridView ID="GridView1" runat="server" AutoGenerateColumns="False" DataKeyNames="Id"
DataSourceID="ObjectDataSource1" AutoGenerateDeleteButton="True" AutoGenerateEditButton="True">
<Columns>
<asp:TemplateField HeaderText="Id" SortExpression="Id">
<EditItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Eval("Id") %>'></asp:Label>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label1" runat="server" Text='<%# Eval("Id") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
<asp:TemplateField HeaderText="Name" SortExpression="Name">
<EditItemTemplate>
<asp:TextBox ID="TextBox2" runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>
</EditItemTemplate>
<ItemTemplate>
<asp:Label ID="Label2" runat="server" Text='<%# Eval("Name") %>'></asp:Label>
</ItemTemplate>
</asp:TemplateField>
</Columns>
</asp:GridView>
When the DataBind method is called (either explicitly or implicitly), data binding from the data source to the control is performed. This is accomplished using the control's DataBinding event. When DataBind is called on a control, it fires the DataBinding event and calls DataBind in child controls.
When a page is compiled, for each control with the runat="server" attribute that is found, the framework creates a method to create an instance of that control and sets the properties that were specified in the ASPX (or ASCX) file. It does so in a method called @__BuildControl__XXX. If the control also contained a data binding expression, the DataBinding event is handled and what it does is to generate code to extract the value from the data source.
For our example, let's focus on what the framework generates for the Column where we show the Name in the EditItemTemplate:
private TextBox @__BuildControl__control17()
{
TextBox @__ctrl;
@__ctrl = TextBox();
@__ctrl.ID = "TextBox2";
@__ctrl.DataBinding += new System.EventHandler(this.@__DataBinding__control17);
return @__ctrl;
}
public void @__DataBinding__control17(object sender, System.EventArgs e)
{
TextBox dataBindingExpressionBuilderTarget;
IDataItemContainer Container;
dataBindingExpressionBuilderTarget = ((TextBox)(sender));
Container = ((IDataItemContainer)(dataBindingExpressionBuilderTarget.BindingContainer));
if ((this.Page.GetDataItem() != null)) {
dataBindingExpressionBuilderTarget.Text = Convert.ToString(this.Eval("Name"), CultureInfo.CurrentCulture);
}
}
When DataBind is called you end up with TextBox2.Text = dataBindingExpressionBuilderTarget.Text = Convert.ToString(this.Eval("Name"), CultureInfo.CurrentCulture);
Now it's time to explore the other way, from the control to the data source.
A Bind expression can only appear inside a template of a data bound control. The values inside a template can be extracted because the template must implement the IBindableTemplate interface that has only one method:
IOrderedDictionary ExtractValues(Control container);
That's used to return a dictionary with the control's values that have Bind expressions.
When a page is compiled, ASP.NET internally creates a CompiledBindableTemplateBuilder (this class implements IBindableTemplate) instance that is passed two delegates, one for building the template and another for extracting the values for Bind expressions (if the template doesn't have Bind expressions, null is passed).
Going to the source code we can see that clearly:
private void @__BuildControl__control16(Control @__ctrl)
{
IParserAccessor @__parser = ((IParserAccessor)(@__ctrl));
@__parser.AddParsedSubObject(new LiteralControl("\r\n "));
TextBox @__ctrl1;
@__ctrl1 = this.@__BuildControl__control17();
@__parser.AddParsedSubObject(@__ctrl1);
@__parser.AddParsedSubObject(new LiteralControl("\r\n "));
}
public IOrderedDictionary @__ExtractValues__control16(Control @__container)
{
OrderedDictionary @__table;
TextBox TextBox2;
TextBox2 = ((TextBox)(@__container.FindControl("TextBox2")));
@__table = new OrderedDictionary();
if ((TextBox2 != null)) {
@__table["Name"] = TextBox2.Text;
}
return @__table;
}
private TemplateField @__BuildControl__control15()
{
TemplateField @__ctrl;
@__ctrl = TemplateField();
@__ctrl.EditItemTemplate = new CompiledBindableTemplateBuilder(new BuildTemplateMethod(this.@__BuildControl__control16), new ExtractTemplateValuesMethod(this.@__ExtractValues__control16));
@__ctrl.ItemTemplate = new CompiledBindableTemplateBuilder(new BuildTemplateMethod(this.@__BuildControl__control18), null);
@__ctrl.HeaderText = "Name";
@__ctrl.SortExpression = "Name";
return @__ctrl;
}
Data bound controls that support two way data binding will call the IBindableTemplate's ExtractValues method to pick up the control's values just before calling the DataSourceView's Insert, Update or Delete methods with the extracted values.
Unfortunately a Bind expression isn't as powerful as you'd like. A valid Eval expression is: <%# Eval("Address.Street") %> but unfortunately <%# Bind("Address.Street") %> isn't valid.
This is a big limitation and IMHO it doesn't have much sense because when you call DataBind a Bind expression is evaluated using Eval, so in that way there isn't any problem. When you call IBindableTemplate's ExtractValues you could return a dictionary with an entry that has a key "Address.Street" without any problem. It's up to the DataSourceView to handle that syntax properly or not but the framework is crippled. I hope they fix it in SP1.
This concludes the four part tutorial explaining most of the theory behind the new data source controls. Once you understand the theory it's time to practise, so in a future post I'll blog about the ObjectDataSource control.