Databinding 3.0
There was a post on our internal discussion group recently where a customer pointed out one of the weaknesses of 2 way data binding not working within user controls. Consider the following page:
<asp:ObjectDataSource runat="server" ID="personSource"
SelectMethod="GetPersons"
UpdateMethod="Update"
DataObjectTypeName="ExtendedDatabinding.Person"
TypeName="ExtendedDatabinding.PersonRepository">
</asp:ObjectDataSource>
<asp:FormView runat="server" ID="personFormView" DataSourceID="personSource" DefaultMode="Edit" DataKeyNames="Id">
<EditItemTemplate>
<custom:personedit runat="server" ID="personForm" /> <br /> <br />
<asp:Button runat="server" Text="Update" CommandName="Update" />
</EditItemTemplate>
</asp:FormView>
I have an ObjectDataSource that points to some business object and is bound to a form view. Also note that we have one usercontrol is the form view, <custom:personedit> which contains UI for an edit form. This is pretty useful since I may want the same UI for edit and insert I could just use the same usercontrol.
The problem is this doesn’t work with 2 way binding, so the FormView won’t extract the values from within the usercontrol even if there are <%# Bind() #> expressions declared. Here’s what the user control looks like:
<strong>First Name:</strong><asp:TextBox runat="server" ID="TextBox1" Text='<%# Bind("FirstName") %>'></asp:TextBox> <br /> <br />
<strong>Last Name:</strong><asp:TextBox runat="server" ID="TextBox2" Text='<%# Bind("LastName") %>'></asp:TextBox> <br /> <br />
<strong>Address:</strong><asp:TextBox runat="server" ID="TextBox3" TextMode="MultiLine" Rows="5" Text='<%# Bind("Address") %>'></asp:TextBox> <br /> <br />
<strong>State:</strong><asp:DropDownList runat="server" ID="DropDownList1" SelectedValue='<%# Bind("State") %>'>
<asp:ListItem Text=""></asp:ListItem>
<asp:ListItem Text="FL"></asp:ListItem>
<asp:ListItem Text="WA"></asp:ListItem>
<asp:ListItem Text="CA"></asp:ListItem>
</asp:DropDownList>
The sad thing is all of those Bind expression will be ignored when we hit update.
I don’t want to get into exactly why this is the case (you should read about how bind works), instead I’d like to introduce a new way to do 2 way databinding.
In Dynamic Data 3.5 sp1 we introduced an interface IBindableControl which has an ExtractValues method that takes a dictionary to populate with name value pairs. FormView and ListView know about this interface and will look recursively for controls that implement IBindinableControl and call ExtractValues when performing an update, insert or delete (also to store the edit values in viewstate). We could make our usercontrol implement this interface and manually extract data from each control and put it into the dictionary, but that is tedious.
Enter databinding 3.0. Using the hidden gem ProcessGeneratedCode we can build a base class which I call BindableUserControl that supports a different Bind syntax to make this scenario work.
The idea exploits the fact that most controls derive from WebControl so we take advantage of their ability to use expando attributes and use it to declare a bogus Binding attribute with a some bind expression as the value. But enough talk lets dive into some code!
<%@ Control Language="C#" Inherits="Web.Binding.BindableUserControl" %>
<strong>First Name:</strong><asp:TextBox runat="server" ID="firstName" Binding="{Text=FirstName}"></asp:TextBox> <br /> <br />
<strong>Last Name:</strong><asp:TextBox runat="server" ID="lastName" Binding="{Text=LastName}"></asp:TextBox> <br /> <br />
<strong>Address:</strong><asp:TextBox runat="server" ID="address" TextMode="MultiLine" Rows="5" Binding="{Text=Address}"></asp:TextBox> <br /> <br />
<strong>State:</strong><asp:DropDownList runat="server" ID="ddl" Binding="{SelectedValue=State}">
<asp:ListItem Text="FL"></asp:ListItem>
<asp:ListItem Text="WA"></asp:ListItem>
<asp:ListItem Text="CA"></asp:ListItem>
</asp:DropDownList>
We’re going to use the Binding attribute to determine what to bind and also the kind of binding to do. From just looking you can pretty much follow the syntax:
{ControlPropertyName=PropertyName}
There is also an enum you can use to specify what kind of binding you want to do:
{Text=Description, Mode=TwoWay}
{Text=Description, Mode=In}
{Text=Description, Mode=Out}
It basically tells the ControlBuilder what to generate i.e. Eval or ExtractValues statements or both.
The special base class BindableUserControl has a FileLevelControlBuilder that does all of the magic. The good news is that binding will work as expected now.
You can download the sample project here:
ExtendedDatabinding.zip