Avoiding DataBinder.Eval in ASP.NET

I've used this tip at least thrice, so following Phil's "Rule of Three" it's time to do something with it. Link to it now, I shall.

It would be easy to pass this one up if you're not using ASP.NET 2.0, but this is applicable for ASP.NET 1.x, too. Key points:

  1. The DataBinder.Eval syntax uses reflection and should be avoided if you can determine the object type at design time (see Scott Galloway's writeup on this here and here).
  2. A quick way to see what the Container object holds is to bind directly to it: <asp:Label ID="Label2" runat="server" Text='<%# Container.DataItem%>'></asp:Label>

Sure, the syntax got easier. Instead of the cumbersome:

<%# DataBinder.Eval(Container.DataItem, "url") %>

We get to save some strokes and remove the entire confusion around “what the heck is Container.DataItem?“:

<%# Eval("url") %>

But, this isn't all its cracked up to be. Eval() STILL uses reflection to evaluate expressions, therefore for every bound column/row displayed in your ASP.NET pages, you are adding overhead, unnecessarily. Of course, what this really means is, just like with 1.1, you should be using explicit casts to cast Container.DataItem to its actual type:

<%# ((System.Data.DataRowView)Container.DataItem)["url"]) %>

Of course the trick is to know...you guessed it...what the heck is Container.DataItem??? A quick way to find this out for various objects you may choose to employ in binding, is to bind just to Container.DataItem as a test. In the attached example I bound the GridView control to the Web configuration sections:

Configuration webConfig = System.Web.Configuration.WebConfigurationManager.OpenWebConfiguration(Request.ApplicationPath);

ConfigurationSectionCollection webConfigSections = webConfig.Sections;

GridView1.DataSource = webConfigSections;

In the GridView declaration I included these labels in a template column:

<asp:Label ID="Label2" runat="server" Text='<%# Container.DataItem%>'></asp:Label>:

<asp:Label ID="Label3" runat="server" Text='<%# ((ConfigurationSection)Container.DataItem).SectionInformation.SectionName %>'></asp:Label>

Now you can consider yourself early bound.

ConfigurationUtility.zip (60.58 KB)

Source: dasBlonde (Michele Leroux Bustamante)

9 Comments

  • Hi Jon,

    Typo on



    Should be



    Andy

  • Curious...

    How would I access something like this?

  • you did'nt tell me why we should avoid using DataBinder.Eval and what's the use of it and the Container.DataItem

  • If the .ToString() is overridden on your object you will not get the type. To ensure you get the type I like

    <asp:Label ID="Label2" runat="server" Text=''>

    better - my 2 cents :D Thanks for the info. I did not know about the reflection.

  • The following are the options when having to render a table using a repeater or similar control:

    1. The "infamous" DataBinder.Eval
    aspx:
    <asp:Literal ID="s" runat="server" Text=''>

    2. Handle the ItemDataBound event and fill the inner controls in there
    codebehind:
    protected override void OnInit(EventArgs e)
    {
    repeater.ItemDataBound += new RepeaterItemEventHandler(rpt_ItemDataBound);
    base.OnInit(e);
    }

    void rpt_ItemDataBound(object sender, RepeaterItemEventArgs e)
    {
    DataRowView row = ((DataRowView)(e.Item.DataItem));
    if (e.Item.ItemType == ListItemType.Item | e.Item.ItemType == ListItemType.AlternatingItem)
    {
    Literal objLiteral = (Literal)e.Item.FindControl("s");
    objLiteral.Text = (string)row["foo"];
    }
    }

    3. Use a rendering helped in the codebehind
    Example:
    aspx:
    <asp:Literal ID="s" runat="server" Text=''>
    codebehind:
    protected string DoFoo(RepeaterItem objRepeaterItem)
    {
    DataRowView row = ((DataRowView)(objRepeaterItem.DataItem));
    // ... processing here
    return ... [result];
    }

    4. Build the table from scratch, putting together HTML tags and content from the underlying data source

    Obviously method #1 is the slowest, as it involves reflection. However, I ran some tests and for a small set of rows, all the approaches take about the same time to execute. I built a test harness using a repeater rendering a table of 5 columns. Up to around 10-20 records all take about the same time (around 5 milliseconds). Beyond that #4 is working faster and #1 slower than #2 and #3. The differences are noticeable: at 200 records #1 is about 50% faster and #4 30% slower than #2 and #3 (6ms vs. 9ms vs. 12ms).

    But beyond the performance itself, #1 is a bad method as it incurs putting code in the aspx page. Who would want to use scripting instead of compiled code? Who would want to not take advantage of the compiler support for catching various code errors at compile time rather running into those issues at run time?. Also, when the data is more complex and needs formatting, processing and so on, there may be cases when #1 would be totally impractical because of the amount of code that may be required.

  • Apologies. Correction to my previous post: it should read:
    The differences are noticeable: at 200 records #1 is about 30% slower and #4 50% faster than #2 and #3 (12ms vs. 9ms vs. 6ms).

    Addendum:
    Building the table from scratch yields the smallest viewstate. The difference is significant. My test generated a 69 KB page for methods 1, 2 and 3, and 32 KB for method #4. There are methods to reduce the viewstate, yet they will not entirely get rid of all unnecessary data. Not only that the page built from scratch will execute faster, it will also load faster into the client browser. So if the page is a simple read-only report style page with no inner controls raising events, and the site is a high traffic site, method #4 is the best solution.

  • An interesting point and not one many would consider unless building scalable applications. With few records the performance is comparable, but if you scale up any performance detriment by thousands of users it suddenly becomes very important!

    Option #4 presented by smartcatxx is undesirable although fastest because it allows no easy customisation of the format of items. This is as undesirable as casting data items in the aspx (or ascx) itself.

    I guess Option #2 is pretty fast even though it uses FindControl to reference the subcontrols. But the performance of that against data binding and event firing is probably minimal. A winner for me. Keep the code in the code and the interface configurable - that way consumers can tweak the layout.

  • Another way to find out the class of Container.DataItem in Visual Studio is to simply cast it to a guessed class.

    If your page crashes Visual Studio will tell you that "something (<- this is the class you are looking for) cannot be cast to something else".

  • I know this is an oldie, but comes up in Google at the top, so someone might find this solution a bit useful, since it doesn't detract from the existing API (and could probably be wrapped in a custom base class if desired):

    Within UserControl:

    protected T GetDataItem() where T : class
    {
    return Page.GetDataItem() as T;
    }

    // then replace (new) Eval, without using Relfection

    public new object Eval(string expression)
    {
    return GetDataItem()[expression];
    }

    public new string Eval(string expression, string format)
    {
    return string.Format(format, GetDataItem()[expression]);
    }

Comments have been disabled for this content.