protected void Button1_Click(object sender, EventArgs e)
{
pnlGrid.Controls.Clear();
MyDataSet someDataSet = new MyDataSet();
...get some data from somewhere and populate "someDataSet"
MyUserControls.GridControl grid = LoadControl("~/GridControl.ascx") as MyUserControls.GridControl;
grid.DataSource = someDataSet;
grid.DataBind();
pnlGrid.Controls.Add(grid);
}
So basically the code above loads a control, preps it by loading it with data and then adds it to a panel. Should work fine, right?
Well... it does. It works fine. The first time! What I found is that upon pushing the button and causing the reloading of data (which let's suppose is different each time), the control still displays the data it was loaded with on the first request.
Sounds like a ViewState-related problem to you? It is! I found that by setting EnableViewState="false" on the GridView, the control's data was loaded and displayed correctly on subsequent requests (button pushes). Still, this isn't the cause of the problem.
As you might know, when a control is dynamically added to a asp.net page, it plays the catch-up game . Basically what this means is that as soon as the control is added to the page it starts trying to "catch-up" to the current state of things, maybe by initializing, loading viewstate, prerendering etc. I remembered all of this vaguely, but I hadn't realized exactly the importance of the part "as soon as the control is added to the page" until I read the blogpost above. Also, I was a bit confused since as far as I knew, viewstate was loaded on a per-control-id basis, whereas in my case, even if I set a random id for my control on every request, things did not seem to work. So let's take them one at a time:
1. Again I have to mention that the catch-up game post is quite revealing. If you're still a bit foggy, maybe the following "code" will make it better:
// ControlCollection class
public virtual void Add(Control child)
{ // do the usual crap
int num1 = this._size;
this._controls[num1] = child;
this._size++;
this._owner.AddedControl(child, num1); // --> see below
}
// Control class
protected internal virtual void AddedControl(Control control, int index)
{ if (this._controlState >= ControlState.ChildrenInitialized)
{ control.InitRecursive(control1);
// …
if (this._controlState >= ControlState.ViewStateLoaded)
{ object obj1 = null;
if ((this._occasionalFields != null) && (this._occasionalFields.ControlsViewState != null))
{ obj1 = this._occasionalFields.ControlsViewState[index];
// …
}
control.LoadViewStateRecursive(obj1);
if (this._controlState >= ControlState.Loaded)
{ control.LoadRecursive();
if (this._controlState >= ControlState.PreRendered)
{ control.PreRenderRecursiveInternal();
}
}
}
}
}
So, in my case what happens is: 1) I set the control's DataSource then call DataBind(): the control is populated with actual values. 2) I add the control to the panel,
it starts playing catch-up, its ViewState is loaded with old values, actual values are lost. The fix is simple:
Either data bind the control after adding it
grid.DataSource = someDataSet;
pnlGrid.Controls.Add(grid);
grid.DataBind();
..Or data bind the whole container
grid.DataSource = someDataSet;
pnlGrid.Controls.Add(grid);
pnlGrid.DataBind();
My only remaining question was...
2. Why is the ViewState loaded even if I set a random ID for the control every time? Well, by looking at the code above we can see that the ViewState is
retrieved by index, not by ID. So basically whenever I'm adding a control to a container's control collection, and it has to go through LoadViewState,
it's ViewState will always be loaded, regardless of id or other things.
SO! To make a long story short: whenever you load and add controls dynamically to a page or container, keep in mind that EXACTLY WHEN THE CONTROL IS ADDED
to the container's control collection it will try to catch-up to the current parent control's state. This of course is fine, but if it will go through the LoadViewState phase be aware
that you don't have any meaningful stuff in it, because it will be overwritten. Basically just know the mantra "Add to page first, Fill with data later" and it should be ok.