Friday, May 4, 2012

Displaying data with <%# %>, OnDataItemBound or use OnDataBinding in ASP.net?

This has been on my mind for years and I have seen both and used both in different projects the last 10 years. But which is better or preferred?

As an example consider a ListView which will bind to a poco called Employee which has properties Name and EmployedSince. The ListView will display the name of the employee as well as how long the user has been employed in years.

public class Employee
{
    public string Name { get; set; }
    public DateTime EmployedSince { get; set; }
}

A simple template for displaying this using <%# %> syntax is show below.

<asp:ListView runat="server" ID="employeeList">
    <LayoutTemplate>
        <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
    </LayoutTemplate>
    <ItemTemplate>
        <div>
        Name: <%#((Employee)Container.DataItem).Name%>
        - Years Employed: <%# (int)((DateTime.Now - ((Employee)Container.DataItem).EmployedSince).TotalDays / 365)  %>
        </div>
    </ItemTemplate>
</asp:ListView>

There are two other alternatives. One is to use the OnItemDataBound event on the ListView, which I don’t like myself as you have to use FindControl to find and bind each specific control. It sort of goes against my nature to look for controls by a name. I like my code strongly typed.

The other approach is to use OnDataBinding per element in the ItemTemplate as shown below.

<asp:ListView runat="server" ID="employeeList">
    <LayoutTemplate>
        <asp:PlaceHolder ID="itemPlaceholder" runat="server"></asp:PlaceHolder>
    </LayoutTemplate>
    <ItemTemplate>
        <div>
            Name: <asp:literal runat="server" OnDataBinding="NameOnDataBinding" />
            - Years Employed: <asp:literal runat="server" OnDataBinding="YearsOnDataBinding" />
        </div>
    </ItemTemplate>
</asp:ListView>

And with the following code behind.

protected void NameOnDataBinding(object sender, EventArgs e)
{
    ListViewDataItem bindingContainer = (ListViewDataItem)(((Control)sender).BindingContainer);
    Employee employee = (Employee)bindingContainer.DataItem;
    Literal literal = (Literal)sender;
    literal.Text = employee.Name;
}

protected void YearsOnDataBinding(object sender, EventArgs e)
{
    ListViewDataItem bindingContainer = (ListViewDataItem)(((Control)sender).BindingContainer);
    Employee employee = (Employee)bindingContainer.DataItem;
    Literal literal = (Literal)sender;
    literal.Text = ((DateTime.Now - employee.EmployedSince).TotalDays/365).ToString("0");
}

So, even though the latter requires more lines of code I prefer it above the first one. The reason why is that adding controls with good binding names clearly states the intent, which is lost when writing code logic in the markup.

This is especially true for the number of years which have logic in it. I can agree that the Name property works for the first approach as well.

I know that there is quite a lot of casting going on in the code behind, but this is boilerplate code which you get used to both reading and writing after a while, and in my eyes it’s still readable.

What are your take on binding data in markup or in code behind, and which approach do you prefer?

4 comments:

  1. I received a question on twitter if the same approach can be used on an asp:repeater, and yes it can. Replace ListViewDataItem with RepeaterItem and you are all set.

    ReplyDelete
  2. I actually prefer adding a read-only property to the business object that returns the formatted string and then using a simple declarative binding to display the value. In your example I'd add the following property to the Employee class:

    public string EmployedFor
    {
    get { return string.Format("Name: {0} - Years Employed: {1:F0}", this.Name, (DateTime.Now - this.EmployedSince).TotalDays / 365); }
    }

    And data bind like this:

    <%# Eval("EmployedFor") %>

    ReplyDelete
  3. Vassili: Although it's a good idea I just don't like to use Eval. And why? It's loosely typed and uses reflection behind the scenes, which uses more CPU cycles than needed.

    And yes... I know this is premature optimization ;), but the loosely typed bind reference is a good enough reason for me. Strings are evil :)

    ReplyDelete
  4. I was using in-line databinding for a user control within a repeater. We recently had the need to add an update panel around these user controls (update panels inside the repeateritem with the user control).

    I found that, for some reason, an in-line eval of BindingContainer no longer works when this is the case. While debugging, I saw that the type of the BindingContainer was a RepaterItem, but the DataItem property was null. Using your second option of code did work, but I figured this may help someone else using the same search terms for this issue.

    Thanks.

    ReplyDelete