November 2009 - Posts

More Data Binding

Last time I tried to solve one of the deficiencies of data binding by taking advantage of expando attributes. Today I want to throw an idea out there that I’ve been playing with since that blog post.I was looking at WPF’s data binding and wondered what it would take to have data binding in ASP.NET be as first class as data binding in WPF.

There is no imperative (unless you mimic the generated code) way to setup 2 way data binding in ASP.NET, but what if there was? Maybe we should be able to setup bindings in the markup as well as code.

SetBinding

This hypothetical re-design of ASP.NET data binding includes a SetBinding method on the base control class that allows users to specify bindings imperatively.

public partial class _Default : System.Web.UI.Page {

    protected void Page_Init() {           

        textBox.SetBinding("Text", new DataBinding("FirstName"));

    }

}

 

The above code would setup 2 way binding for the text property to the FirstName property in the current item being databound (if we were using some data control).

<asp:TextBox runat="server" Text="{Binding FirstName}"></asp:TextBox>

This is the equivalent markup of the code above.

Unlike the current data binding story that’s all parse time magic, all the work here would be done in the runtime. The binding syntax would just be syntactic sugar.

So we’ve re-implemented the current binding that ASP.NET supports today but what else can we do. Let’s take this concept of a binding and extend it.

Sometimes it’s useful to have control’s properties be dependant on each other. Imagine some UI where you have a pager and a drop down list of possible page sizes. Whenever the page size changes you want to update the pager’s page size so that the list updates and shows the correct number of items. This can be done today with a bunch of code but wouldn’t it be nice to have this support natively in the framework?

 

<asp:ListView ID="ProductsList" runat="server">

    <ItemTemplate>

        ...              

    </ItemTemplate>

</asp:ListView>

 

<asp:DataPager runat="server"

    PagedControlID="ProductsList"

    PageSize="{ControlBinding pageSizes, Property=SelectedValue}">

</asp:DataPager>       

 

<asp:DropDownList ID="pageSizes" runat="server">

    <asp:ListItem Text="10"></asp:ListItem>

    <asp:ListItem Text="20"></asp:ListItem>

    <asp:ListItem Text="30"></asp:ListItem>

    <asp:ListItem Text="40"></asp:ListItem>

</asp:DropDownList>

We’re using a control binding to bind the pageSizes.SelectedValue property to the pager’s PageSize property.

The prototype

Can’t have a blog post without code right :)? I’ve put together a little prototype of what this could look like but there are some gotchas.

  • There is only one bindable control in the prototype <asp:BindableTextBox> since I don’t have the power (I do but I wanted to give out a sample) to change the base Control class.
  • The binding syntax only works for text properties. The ASP parser doesn’t like it when you supply invalid values for properties. i.e  <asp:BindableTextBox TextMode=”{Binding Foo}” /> won’t work since it will complain that it can’t convert that text to a TextMode enum.

However you have full power in the code behind to do all of these things.

To use the bindable controls just add the following to the <pages> section in web.config

<add tagPrefix="asp" namespace="Web.Binding.Controls" assembly="Web.Binding"/>

What’s in the package?

There is a BindableControl base class just in case you want to write more bindable controls :).

The BindableTextBox wraps a BindableControl since C# doesn’t support multiple inheritance and we want to get the behavior of both of those classes. Ideally this would be baked into the base Control class so all of the controls get this behavior for free.

The BindableControlBuilder parses the binding expressions and generates the right binding code.

 

public class BindableControlBuilder : ControlBuilder {

    private HashSet<BindingExpression> _bindings = new HashSet<BindingExpression>();

 

    public override void Init(TemplateParser parser, ControlBuilder parentBuilder, Type type, string tagName, string id, IDictionary attribs) {

        foreach (DictionaryEntry entry in attribs.Cast<DictionaryEntry>().ToList()) {

            string key = (string)entry.Key;

            string value = entry.Value as string;

            BindingExpression expr;

            if (value != null && BindingExpression.TryParse(key, value, out expr)) {

                // Add to our list of bindings

                _bindings.Add(expr);

                // Remove the attribute so the binding expression doesn't show up as a property value

                attribs.Remove(key);

            }

        }

        base.Init(parser, parentBuilder, type, tagName, id, attribs);

    }

 

    public override void ProcessGeneratedCode(CodeCompileUnit codeCompileUnit, CodeTypeDeclaration baseType, CodeTypeDeclaration derivedType, CodeMemberMethod buildMethod, CodeMemberMethod dataBindingMethod) {

        if (buildMethod != null) {

            foreach (var binding in _bindings) {

                // Generate code foreach binding and add it to the

                // build method for this control

                var statement = binding.GenerateCode(buildMethod);

                int len = buildMethod.Statements.Count;

                buildMethod.Statements.Insert(len - 1, statement);

            }               

        }

    }

}

 

Also included in the package are 3 types of bindings:

  • Databinding – What ASP.NET has today.
  • ControlBinding – Bind a control property to another control’s property
  • ValueBinding – Bind a control property by executing a delegate to get the value.

The first 2 can be used declaratively but the ValueBinding is only supported imperatively.

I’d love to get feedback on what people think about this. You can download the prototype here.

Posted by davidfowl with 11 comment(s)
Filed under: , ,

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

Posted by davidfowl with 13 comment(s)
More Posts