How <%# Bind %> Works

In my last post I spoke about 2-way databinding and how it can be used to extract values from control properties. How does this all work? Lets take a look at a page with 2-way databinding:

<asp:LinqDataSource ID="productsSource"

    runat="server"

    ContextTypeName="FowlerSamples.NorthwindDataContext"

    EnableDelete="True"

    EnableInsert="True"

    EnableUpdate="True" TableName="Products">

</asp:LinqDataSource>       

<asp:GridView ID="products"

    DataKeyNames="ProductID,CategoryID"

    AutoGenerateColumns="False"

    runat="server" DataSourceID="productsSource">

    <Columns>

        <asp:CommandField ShowEditButton="True" />               

        <asp:BoundField DataField="ProductName" />

        <asp:TemplateField>

            <EditItemTemplate>

                <asp:LinqDataSource

                    ID="categoriesSource"

                    runat="server"

                    ContextTypeName="FowlerSamples.NorthwindDataContext"

                    TableName="Categories" AutoGenerateWhereClause="true">

                </asp:LinqDataSource>

                <asp:DropDownList

                    runat="server"

                    ID="categories"

                    DataSourceID="categoriesSource"

                    DataTextField="CategoryName"

                    DataValueField="CategoryID"

                    SelectedValue='<%# Bind("CategoryID") %>'>                           

                </asp:DropDownList>                       

            </EditItemTemplate>                   

        </asp:TemplateField>

    </Columns>

</asp:GridView>

 In the above example, the GridView has a template field with an EditItemTemplate that has a DropDownList that is 2-way databound. We're going to introduce a small error in the page in order to see what the generated code looks like:

<EditItemTemplate>

     <asp:LinqDataSource

         ID="categoriesSource"

         runat="server"

         ContextTypeName="FowlerSamples.NorthwindDataContext"

         TableName="Categories" AutoGenerateWhereClause="true">

     </asp:LinqDataSource>

     <asp:DropDownList

        runat="server"

        ID="categories"

        DataSourceID="categoriesSource"

        DataTextField="CategoryName"

        DataValueField="CategoryID"

        SelectedValue='<%# Bind("CategoryID") %>'>                           

    </asp:DropDownList>

    <%# Eval(3) %>

</EditItemTemplate>


When we try to run this page we'll get a compile error and the famous ASP.NET YSOD(Yellow Screen of Death):



Click on Show Complete Compilation Source, if your curious about how ASP.NET converts your the markup to code.

When examining the source, we see a rather interesting method:

[System.Diagnostics.DebuggerNonUserCodeAttribute()]

public System.Collections.Specialized.IOrderedDictionary @__ExtractValues__control8(System.Web.UI.Control @__container) {

    System.Collections.Specialized.OrderedDictionary @__table;

    System.Web.UI.WebControls.DropDownList categories;

 

    categories = ((System.Web.UI.WebControls.DropDownList)(@__container.FindControl("categories")));

 

    @__table = new System.Collections.Specialized.OrderedDictionary();

 

    if ((categories != null)) {

        @__table["CategoryID"] = categories.SelectedValue;

    }

 

    return @__table;

}


As you can see in the above method, an OrderedDictionary is created and the SelectedValue property of the DropDownList is pushed into the "CategoryID" field. But how does this get all the way to the data control? Each control has a BuildControl method associated with it, if we examine the BuildControl method for the TemplateField it becomes a bit more clear how things get hooked up.

 

[System.Diagnostics.DebuggerNonUserCodeAttribute()]

private global::System.Web.UI.WebControls.TemplateField @__BuildControl__control7() {

    global::System.Web.UI.WebControls.TemplateField @__ctrl;

 

    @__ctrl = new global::System.Web.UI.WebControls.TemplateField();

 

    @__ctrl.EditItemTemplate = new System.Web.UI.CompiledBindableTemplateBuilder(new System.Web.UI.BuildTemplateMethod(this.@__BuildControl__control8), new System.Web.UI.ExtractTemplateValuesMethod(this.@__ExtractValues__control8));

 

    return @__ctrl;

}

The EditItemTemplate property is of type ITemplate. CompiledBindableTemplate implements both ITemplate and IBindableTempalte.

public interface IBindableTemplate : ITemplate {           

    IOrderedDictionary ExtractValues(Control container);

}

It's slowly coming together. So lets put together what we're learnt so far:

  1. Code Gen creates ExtractValues method that returns the dictionary of values for each ITemplate that has a Bind expression.
  2. The BuildControl method for the ITemplate's container (TemplateField in this case) assigns a new CompiledBindableTemplate to an ITemplate (EditItemTemplate in this case)
  3. CompiledBindableTemplate implements IBindableTemplate, which has a method, ExtractValues which returns the dictionary given a container.

It almost all makes sense now. Each data control uses this mechanism to extract values from template fields with 2-way databinding expressions.
What can you do with your new found knowledge?

<asp:FormView

    runat="server"

    ID="formView"

    DefaultMode="Edit">

    <EditItemTemplate>

        <asp:TextBox ID="textBox" runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>

        <asp:TextBox ID="textBox1" runat="server" Text='<%# Bind("Age") %>'></asp:TextBox>

        <asp:Button runat="server" ID="updateButton" CommandName="Update" Text="Update" />

    </EditItemTemplate>

</asp:FormView>


And the code behind:

 

class Person {

    public string Name { get; set; }

    public int Age { get; set; }

}

 

protected void Page_Load() {

    formView.ItemUpdating += formView_ItemUpdating;

    if (!IsPostBack) {

        formView.DataSource = new Person[] { new Person { Name = "David", Age = 22 } };

        formView.DataBind();

    }

}

 

protected void formView_ItemUpdating(object sender, FormViewUpdateEventArgs e) {

    IBindableTemplate template = formView.EditItemTemplate as IBindableTemplate;

    if (template != null) {

        IOrderedDictionary values = template.ExtractValues(formView);

        Response.Write(values["Name"]);

        Response.Write(values["Age"]);

    }

}


How cool is that? :)

1 Comment

Comments have been disabled for this content.