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 10 comment(s)

My first channel 9 video!

Wow, I haven't blogged in a while...but that will end soon. Watch me talk about the QueryExtender control. It's a new control we added in ASP.NET 4.0 to make Linq queries even simpler. The new control works with LinqDataSource and EntityDataSource. We also built all of the new DynamicData filters on top of this new control.

Check it out here
Posted by davidfowl with 4 comment(s)
Filed under:

Dynamic Data in Regular Websites/Web Applications

Update: David Ebbo has a great video on channel9 about this. You can watch it here.

Today I'm excited to share that we've released DynamicData Preview 4 on codeplex. Check out the latest bits here.

This release is particularly interesting not only for people that have been using Dynamic Data for a while now, but anyone that has an existing application today who wants to use some of the niceties Dynamic Data offers without having to take all the junk associated. Take a look at the SimpleDynamicData project for examples.

Existing Sites

Here are 2 good reasons to use Dynamic Data:

  • Rich model validation
  • Rich templating support via FieldTemplates 

If you've ever written a data driven app in webforms using our data controls, you would have realized that we are lacking a lot when it comes to validation. You can enable all of this goodness with a magic extension method.

protected void Page_Init() {
    GridView1.EnableDynamicData(your type here);
}

Note: The method requires that you pass in the type that may have annotations in order for us to read Metadata. If you're not familiar with the way annotations work in Dynamic Data then watch the videos here.

This method call enables DynamicControl/DynamicField to work within any of the data controls which makes use of FieldTemplates, and enables the rich validation support.

Making it work

So we know about this magic method call and somehow calling it with a type makes it all just work. Let's walk through an example of how we would use this. Here is my model:

public class Student {
    [Required]
    [Range(0, 100)]
    public int Age { get; set; }
 
    [Range(0.0, 4.0)]
    public double GPA { get; set; }
 
    [Required]
    public string FirstName { get; set; }
 
    [Required]
    public string LastName { get; set; }
 
    [DisplayFormat(DataFormatString = "{0:d/M/yyyy}")]
    public DateTime BirthDate { get; set; }
}

This is the type we are going to use for metadata. Using the attributes from System.ComponentModel.DataAnnotations we can add useful annotations to our model that will be used for validation and display formatting. Adding these attributes allows Dynamic Data to enable the appropriate validator. i.e RangeValidator, RequiredFieldValidator etc.

Now we're going to enable this on our GridView using the same method call as above in conjunction with an ObjectDataSource to complete our application:

protected void Page_Init() {
    GridView1.EnableDynamicData(typeof(Student));            
}

Markup

<asp:GridView ID="GridView1" runat="server" DataSourceID="ObjectDataSource1" AutoGenerateEditButton="true">        
    </asp:GridView>
    <asp:ObjectDataSource ID="ObjectDataSource1" runat="server" 
        DataObjectTypeName="Student" DeleteMethod="DeleteStudent" 
        InsertMethod="InsertStudent" SelectMethod="GetStudents" 
        TypeName="StudentsRepository" UpdateMethod="UpdateStudent">
    </asp:ObjectDataSource>

Note: Dynamic Data takes care of the Metadata not the data. You still need databind the data control against some data source/data source control.

Here are the results when we are editing:

As you can see, the attributes we specified on our Student class directly affect the grid and validation is enabled.

There's more cool stuff to talk about but I'll mention those in upcoming posts. For now, download the preview and read up on Dynamic Data!

Posted by davidfowl with 14 comment(s)

External ITemplates and Hierarchical Databinding

Ever wish you could declare a template outside of the control you were defining the template for? We always get requests to have FormView's InsertItem template fall back on the EditItemTemplate and vice versa. That would be easy if we could do what was mentioned above.

Consider:

<asp:FormView ID="myFormView" runat="server" DefaultMode="Edit" 
    EditItemTemplate="editTemplate" 
    InsertItemTemplate="editTemplate">
</asp:FormView>
 
<asp:Template runat="server" ID="editTemplate">
    Name : <asp:TextBox runat="server" Text='<%# Bind("Name") %>'></asp:TextBox>
    Age : <asp:TextBox runat="server" Text='<%# Bind("Age") %>'></asp:TextBox>
</asp:Template>

Then you could do things like hierarchical databinding pretty easily; just define the template in terms of itself. Today, properties typed as ITemplate are treated specially by the ASP.NET parser, and what is written above will not work.

How would you do this with what asp.net offers now? Well check out this sample:

<cc:SpecialRepeater runat="server" ItemTemplateID="folderTemplate" DataSource='<%# GetDirectories() %>' />        
<cc:Template runat="server" ID="folderTemplate">
    <ItemTemplate>
        <ul>
            <li>
                <%# Eval("Name") %>
                <cc:SpecialRepeater runat="server" DataSource='<%# GetDirectories((string)Eval("FullName")) %>' ItemTemplateID="folderTemplate" />
                <ul>
                    <cc:SpecialRepeater runat="server" DataSource='<%# GetFiles((string)Eval("FullName")) %>' ItemTemplateID="fileTemplate" />
                </ul>
            </li>
        </ul>
    </ItemTemplate>
</cc:Template>
 
<cc:Template runat="server" ID="fileTemplate">
    <ItemTemplate>
        <li> <%# Eval("Name") %>
        </li>
    </ItemTemplate>
</cc:Template>

We have a SpecialRepeater that understands how to hookup template properties through their ID (it's just FindControl) and a Template control that defines our file template and folder template. We define the folder template in terms of itself. Can you think of any more uses for something like this?

Get the code here.

What do you think?

Posted by davidfowl with 8 comment(s)

Invalid postback or callback argument

I'm sure many of you have seen this error message when developing your web application:

Server Error in '/' Application.
--------------------------------------------------------------------------------

Invalid postback or callback argument.  Event validation is enabled using <pages enableEventValidation="true"/> in configuration or <%@ Page EnableEventValidation="true" %> in a page.  For security purposes, this feature verifies that arguments to postback or callback events originate from the server control that originally rendered them.  If the data is valid and expected, use the ClientScriptManager.RegisterForEventValidation method in order to register the postback or callback data for validation.

I'm going to discuss this in the context of the data controls. This happens when a control that isn't registered for event validation causes a postback, but surely that can't be the case.. right?

Let's look at a small repro:

Markup:

<asp:GridView ID="GridView1" runat="server">
    <Columns>
        <asp:TemplateField>
            <ItemTemplate>
                <asp:Button runat="server" Text="Button" />
            </ItemTemplate>
        </asp:TemplateField>
    </Columns>
</asp:GridView>

Code behind:

public partial class _Default : System.Web.UI.Page {
    protected void Page_Load(object sender, EventArgs e) {
        GridView1.DataSource = Enumerable.Range(0, 5);
        GridView1.DataBind();
    }
}

Now click on the button and see the dreaded error message. Why does this happen? EventValidation was added in ASP.NET to ensure that controls causing the postback came from the same page being rendered. Take a look at __EVENTVALIDATION hidden field on the page. It is a serialized version of all of the controls registered for postbacks(read more here). You might be wondering how they got in there and why is the button inside of a GridView a special case. It's not a special case, in fact, Button registers itself with the current page.

The reason this happens is because we rebind the data control in Page_Load every time which means that we will lose all of the posted data and viewstate. As a result, the ID of the button is different and when the event is validated there will be no matching unique id and hence event validation will fail. We are acutally raising an event for a button that is no longer in the control tree.

You can work around this by wrapping that code in if (!IsPostBack). This is a good proof of why you should use DataSource controls.

Hope this helps

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

A new way to DataBind()

The Problem 

I've been thinking alot recently about the problems with data binding (and they're alot). There are some patterns that play well with ASP.NET (and are repeated everywhere) and some that don't quite fit the model. One of those patterns that don't mesh well is, setting the DataSource property of any of the DataControls. Before ASP.NET 2.0 and DataSource controls we'd have to set the DataSource property and manually call DataBind.

What's wrong with this you may ask? When are you supposed to DataBind? Page_Load ? Page_Init? Page_PreRender? I'm sure anyone that has had to manually data bind has had the problem of figuring out which code goes in and out of the IsPostBack block.

Whats the solution?

I'm looking at an alternative approach to this problem. Event handlers are nice because we all feel confident that the author of the event knows when that event should be raised. No need to figure out the postback logic because the control author already thought about that. All you have to worry about is handling the event.

I propose a new hypothetical event OnDataRetrieveing, that would be on all DataControls in conjunction with a new flag AutoBind to enable it.

Let's take a look at a sample implementation of a derived GridView control that has this new behavior. First the markup:

<custom:AutoGridView 
    OnDataRetrieving="OnDataRetrieving"
    OnPageIndexChanging="OnGridViewPageIndexChanging"
    OnRowEditing="OnGridViewRowEditing"
    OnRowCancelingEdit="OnGridViewRowCancelingEdit"
    AllowPaging="true" 
    runat="server" 
    ID="GridView1" 
    AutoGenerateEditButton="true"
    AutoBind="true">
</custom:AutoGridView>

Looks pretty similar to the regular GridView besides the AutoBind="true" flag and OnDataRetrieving event.

And the code behind

protected void OnGridViewRowCancelingEdit(object sender, GridViewCancelEditEventArgs e) {
    GridView1.EditIndex = -1;
}
 
protected void OnGridViewPageIndexChanging(object sender, GridViewPageEventArgs e) {
    GridView1.PageIndex = e.NewPageIndex;
}
 
protected void OnGridViewRowEditing(object sender, GridViewEditEventArgs e) {
    GridView1.EditIndex = e.NewEditIndex;
}
 
protected void OnDataRetrieving(object sender, DataBindingEventArgs e) {
    NorthwindDataContext db = new NorthwindDataContext();
    e.DataSource = db.Products;
}

We write code as we normally would if we'd been binding manually, but instead, we assign our data to the DataSource property in the DataBindingEventArgs (Which gets called at the "right" time).

So what is the right time to Databind and why does this control do it better than you? I'm not sure :) but let's look at the source of this control.

public override object DataSource {
    get {
        return base.DataSource;
    }
    set {
        if (!_settingDataSource && AutoBind) {
            throw new InvalidOperationException("When AutoBind is enabled, setting the DataSource property explicitly is not allowed.");
        }
        base.DataSource = value;
    }
}
 
protected override void PerformSelect() {
    if (AutoBind) {                
        DataBindingEventArgs args = new DataBindingEventArgs();
        OnDataRetrieving(args); 
        _settingDataSource = true;
        DataSource = args.DataSource;
        _settingDataSource = false;
    }
    base.PerformSelect();
}
 
protected override void EnsureDataBound() {
    if (RequiresDataBinding && (AutoBind || IsBoundUsingDataSourceID)) {
        DataBind();
    }
}

The most interesting method is EnsureDataBound. This method is called from PreRender and the original condition for making the control DataBind is:

if (this.RequiresDataBinding && ((this.DataSourceID.Length > 0) || this._requiresBindToNull)) {
    this.DataBind();
    this._requiresBindToNull = false;
}

In the case of manually binding this would never be called unless _requiresBindToNull is true. Our AutoGridView control changes this logic by detecting the AutoBind flag and continues to data bind as usual in PreRender.

If you interested in when the event gets called you can put some break points in the control's code. What do you think about this alternative?

Here is a link to the source.

Posted by davidfowl with 12 comment(s)

Client side devevelopment in ASP.NET

We ASP.NET developers know how much of a pain it is writing javascript in any app we have today because of naming container madness! You've probably done something like this:

function DoSomeThingCool() {
    var textBox = document.getElementById('ct100_contentplaceholder1_TextBox1');
}

or something not so hardcoded

function DoSomeThingCool() {
    var textBox = document.getElementById('<%= TextBox1.ClientID %>');
}

Matthew Osborn, QA on the ASP.NET team has a great post on a new ASP.NET 4.0 feature that gives developers more control over how ClientIDs are generated.

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

Follow me on twitter!

I've finally got sucked into twitter after avoiding it for many months. Check me out:

http://twitter.com/davidfowl/

Since my name is so common it really annoys me when I sign up for any new service because I have to get accept some partially mangled version of my name (i.e. davidfowl).

Anyways, stay tuned!

Posted by davidfowl with no comments
Filed under: ,

Dynamic ListView LayoutTemplate

There are times when you want to let the user change layout dynamically. You can use css to do this but lets look at what the ListView control offers. To get started with the ListView you need a LayoutTemplate and ItemTemplate.

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

    <LayoutTemplate>

        <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>

    </LayoutTemplate>

    <ItemTemplate>

        <%# Eval("CategoryName") %>

    </ItemTemplate>

</asp:ListView>

The ListView replaces the control with ID="itemPlaceholder" with the zero or more instances of the Selected/Alternating/ItemTemplate.

There is a method on TemplateControl (LoadTemplate) which allows users to dynamically load a user control as a ITemplate. Lets use this to load our LayoutTemplate from a user control.

User Control:


<%@ Control Language="C#" AutoEventWireup="true" CodeBehind="FlowLayout.ascx.cs" Inherits="ListViewLayouts.FlowLayout" %>

 

<div id="flow">

    <asp:PlaceHolder runat="server" ID="itemPlaceholder"></asp:PlaceHolder>

</div>

Code Behind:

protected void Page_Init(object sender, EventArgs e) {

    listView.LayoutTemplate = LoadTemplate("~/ListViewLayouts/FlowLayout.ascx");

}

When we run the page we get the following exception:

An item placeholder must be specified on ListView 'listView'. Specify an item placeholder by setting a control's ID property to "itemPlaceholder". The item placeholder control must also specify runat="server".

The problem here is that user controls are naming containers and when the ListView internally tries to find the control with ID="itemPlaceholder" it fails. So what is the id of the itemPlaceholder? We'll we could start guessing that it might be something like ctl001$itemPlaceholder, but that doesn't seem like a good solution. Instead we can create our own template that will allow us to specify the ID of the user control so that the itemPlaceholderID is more predictable.

public class CustomTemplate : ITemplate {

    private string _virtualPath;

    private string _controlID;

 

    public CustomTemplate(string virtualPath, string controlID) {

        _virtualPath = virtualPath;

        _controlID = controlID;

    }

 

    public void InstantiateIn(Control container) {           

        Control control = (Control)BuildManager.CreateInstanceFromVirtualPath(_virtualPath, typeof(Control));

        control.ID = _controlID;

        container.Controls.Add(control);

    }

}


This template gives us the opportunity to specify a controlID for the user control we are going to load. Now we instantiate a new CustomTemplate and specify the control ID as well as path to the user control.

protected void Page_Load(object sender, EventArgs e) {

    listView.LayoutTemplate = new CustomTemplate("~/ListViewLayouts/FlowLayout.ascx", "flowLayout");           

}

Don't forget to set the ItemPlaceHolderID proprety on the ListView.

<asp:ListView runat="server" ID="listView" ItemPlaceholderID="flowLayout$itemPlaceholder">

        <ItemTemplate>

            <%# Eval("CategoryName") %>

        </ItemTemplate>

</asp:ListView>

Now we can load the LayoutTemplate at runtime.

Posted by davidfowl with 3 comment(s)
More Posts Next page »