Geeks With Blogs

@adrianhara
  • adrianhara @DellCares hey Dell, I bought a monitor from a reseller that went bankrupt and now the monitor died (still under warranty)...what can I do? about 506 days ago
  • adrianhara @nlaplante heh, i implemented something myself yesterday and submitted a pull request...looking forward to comparing the two about 512 days ago
  • adrianhara @nlaplante hey, is there a way to load "self-contained" info window content for the angularmap directive? (load template html+controller)? about 513 days ago
  • adrianhara well lol, apparently Microsoft customer satisfaction surveys expire after a week or so...does MS think customers forget that easily? :) about 574 days ago
  • adrianhara LucasArts is being shut down, but their amazing achievements will live forever...oh and frack Mickey Mouse! about 575 days ago
  • adrianhara argh! can't get anything to show up in @jira's "reviews" tab :( application links are set up, source tab is populated, reviews tab not #cry about 576 days ago

News

Locations of visitors to this page




Adrian Hara Working through the .NET maze

Today I experienced what I believe to be a classic asp.net dynamically loaded controls gotcha. Here's the scenario:

I have one page with a panel on it like so:

<asp:Panel ID="pnlGrid" runat="server"></asp:Panel>

...plus I have a user control, GridControl.ascx, like so:

<asp:GridView ID="gv" runat="server" AutoGenerateColumns="false" Width="100%">

      <Columns>

            <asp:TemplateField HeaderStyle-VerticalAlign="top">

                  <HeaderTemplate>Foobar</HeaderTemplate>

                  <ItemTemplate><%# Eval("Foo").ToString()%></ItemTemplate>

            </asp:TemplateField>

      </Columns>

</asp:GridView>

Since I want this control to be reusable, I've packaged it into a class library and exposed a property called DataSource and overriden the DataBind method to do something like: 

gv.DataSource = DataSource;

gv.DataBind();

 

Now suppose I had to load this control dynamically on the page with the panel, and add it as a child control of the panel's, probably in the event handler for a button (called "Show data") click. This is the code that did it:

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.
Posted on Monday, February 19, 2007 10:16 AM | Back to top


Comments on this post: Asp.net dynamic control catch-up gotcha

# re: Asp.net dynamic control catch-up gotcha
Requesting Gravatar...
This just saved me big time: Add to page first, fill with data later.

You are a gentleman and a scholar for posting this life saver.
Left by Don on Apr 21, 2009 7:55 AM

Your comment:
 (will show your gravatar)
 


Copyright © Adrian Hara | Powered by: GeeksWithBlogs.net | Join free