Sunday, June 1, 2008

DataSource VS. DataSourceID (Internals)

An Old Question

I know .NET v3.5 has been around for a while now and that I should have all the v2.0 stuff down by now, but to be perfectly honest I don't. This was made extremely clear to me when a colleague asked me what the difference was between setting the DataSource and DataMember on a control vs. setting the DataSourceID. Maybe this post should have been made in 2005 (and it probably was by some other developer), but at the risk of reinventing an already aged wheel I'll go ahead and write this down for (at least) my own benefit.

We (this colleague and I) ended up getting into a discussion about Object/SQL DataSources and "new" .NET v2.0 controls like Grid/Form View, but then I got stumped on this one question. We were talking about how you don't need to explicitly call the DataBind() method when you assign DataSourceID but you do when you assign DataSource/DataMember. But that behavior begs the question:

"How do DataSourceID enabled controls know when to re/bind and when in the page life cycle do they call DataBind on themselves?"

An Old Answer

Since this is a two year old question I got pointed in the right direction by a blog post done by Manuel Abadia. I then turned to Reflector to validate what I read and make sense of everything.

DataSource and DataMember

You probably already know this one. Using these properties you explicitly set the DataSource and (potentially the DataMember). When you call DataBind() on the control it takes that bound data and uses it to go set a series of properties or build out a series of sub controls so that by the time Page Render comes around the control is all built out and ready to render out a bunch of markup driven by your datasource.

DataSourceID

When setting a DataSourceID a pretty similar behavior happens. The control takes note that the DataSourceID has changed and when PreRender comes around it calls DataBind() just like in .NET v1.x.

Here's some snippets from Reflector. The code below is from System.Web.UI.BaseDataBoundControl which .NET v2.0+ DataSourceID enabled controls inherit from.

public virtual string DataSourceID
{
    //omitted for brevity...
    set
    {
        if (string.IsNullOrEmpty(value) &&         !string.IsNullOrEmpty(this.DataSourceID))
        {
            this._requiresBindToNull = true;
        }
        this.ViewState["DataSourceID"] = value;
        this.OnDataPropertyChanged();
    }
}

Essentially the DataSourceID gets set in ViewState and then OnDataPropertyChanged gets fired (which really just sets a flag telling the control to call DataBind() when PreRender rolls around). Just in case you don't believe me the code is below (same class as before).

protected virtual void OnDataPropertyChanged()
{
    //omitted for brevity...
    if (this._inited)
    {
        this.RequiresDataBinding = true;
    }
}

Finally when PreRender comes around for the control we see pretty straight forward code telling the control to DataBind() if the _requiresDataBind flag as been set. The actual call to DataBind() happens in EnsureDataBound().

protected internal override void OnPreRender(EventArgs e)
{
    this._preRendered = true;
    this.EnsureDataBound();
    base.OnPreRender(e);
}
protected virtual void EnsureDataBound()
{
    try
    {
        this._throwOnDataPropertyChange = true;
        if (this.RequiresDataBinding &&
        ((this.DataSourceID.Length > 0) ||         this._requiresBindToNull))
        {
            this.DataBind();
            this._requiresBindToNull = false;
        }
    }
    finally
    {
        this._throwOnDataPropertyChange = false;
    }
}

So that's it. Not too bad really. The more I think about it the more I like it. It reminds me a lot of what it was like setting DataSources on WinForms controls, all that explicitly calling DataBind() goes away. Ideally it's one less thing to think about.

My Best,
Tyler

3 comments:

Cameron said...

Hey Tyler, there's a difference between the two I can't get my head around. I have a Gridview with AutoGenerateColumns disabled, Sorting enabled and a few string fields being pulled from SQL server by an SqlDataSource. When I set the DataSourceID (either programmatically or in the aspx) sorting works fine. When I set the DataSource and manually DataBind I receieve the error The GridView 'GridView1' fired event Sorting which wasn't handled. Any ideas?

Tyler Holmes said...

Hey Camron this is actually a really good comment with respect to the article and illustrates a point quite well.

When you set a DataSourceID on a control, the control will call DataBind() on itself come PreRender. When you just set a DataSource/DataMember YOU the Page developer have to explicitly call DataBind() on the control.

Also when you use a DataSourceID the GridView will set the SortExpression and SortDirection for you in private void GridView.HandleSort(string, SortDirection). When you're using DataSource/DataMember YOU need to set the sort expression and sort direction yourself. You do this in the method handler for the sort event. You also need to handle paging events yourself.

A HOW TO for these is available at: http://forums.asp.net/t/956540.aspx.

Hope that helps,
Tyler

GoogleAcctHasToBe said...

Thanks for the knowledgable post. I experienced the similar behavior with Updating the data (editable gridview). So, I kind of understand now that when the DataSourceID is set, it triggers a lot of built-in stuff (event is handled in the style of subclass overriden), but setting the DataSource will require event to be explicitly handled in the "***_Updating(...)" style.