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.