« Home | Handy Web Reference Links » | css Zen Garden: The Beauty in CSS Design » | Onbeforeunload throwing errors in IE - coding arou... » | Ajax Safari Update » | An AJAX Safari... » 

Saturday, November 05, 2005 

Tricky Dynamic Controls in ASP.NET

A few months ago I ran into a problem at work that forced me to really read up on dynamic controls and VIEWSTATE. Creating Dynamic controls requires a good understanding of the sequence of events when a page is generated in ASP.NET. If you don't understand what is happening things can get real messy, real fast!

I've created an ultra simplified version of the UI I was attempting to create. Pretty simple, I've got a DropDownList inside a table, that is populated with a DataBind when the page is first loaded. When the user selects and option from the DropDownList, and clicks a button, insert a column into the table. The dropdown always remains in the last column. I followed the guidelines from the codeproject article: Dynamically Created Controls in ASP.NET. Some important lessons to be learned from the article:

  • dynamically created controls don't survive a postback
  • so we have to 'remember' the controls, and recreate them after a postback

The first time I clicked the button everything worked as expected. Second attempt: not so happy! My DropDownList has lost its contents! View an example of broken dynamic contols: DynamicTable1.aspx (source). My first thought was to remove the test for !IsPostback in the Page_Load, so the DropDownList is populated everytime. This causes the DropDownList to 'forget' its selected position with each postback, and SelectedIndexChanged doesn't fire anymore. Presumably because I'm resetting it in Page_Load, ASP.NET doesn't consider it changed? Things are starting to get ugly.

The first clue as to what was going wrong came when I moved the DropDownList out of the table. Everything works! View an example of the working dynamic contols: DynamicTable2.aspx (source).

Some more research brings me to a really good article on the page events: Page Events: Order and PostBack by Paul Wilson. Important to note here is the order in which ASP.NET 'remembers' the contents on the DropDownList, and repopulates it.

Here is the order in which things are happening. First time we load the page:

  1. OnLoad: DropDownList is data-bound.
  2. SaveViewState: ASP.NET parses through the control tree, and saves the contents of each control. This is where the contents of the DropDownList are 'remembered'.
An option is selected from the DropDownList, and the button is clicked:
  1. LoadViewState: Just as Paul Wilson says: "After ViewState is retrieved, the next method, LoadViewState, restores it to the page, and then recursively to each control and their children". This is where the DropDownList is being repopulated.
  2. OnLoad: Page_Load calls RecreatePersistedControls, but at this stage there is nothing to recreate.
  3. RaiseChangedEvents: This calls DropDownList1_SelectedIndexChanged, and the dynamically created table cell is added.
  4. SaveViewState: This time around the contents of the DropDownList are 'remembered'. But the DropDownList is now in a different place in the control tree this time, because there is now a new TableCell.
At this stage everything has populated fine. For the second time an option is selected from the DropDownList, and the button is clicked:
  1. LoadViewState: ASP.NET attempts to populate the page controls with the ViewState that was saved last time around. The dynamically created TableCell hasn't been created yet - so the control trees don't line up. The DropDownList doesn't get populated!
  2. OnLoad: Page_Load calls RecreatePersistedControls, and a TableCell is created. Our TableCell has arrived a little late!

The mismatch of control trees can be demonstrated by switching ASP.NET Trace on.

The solution was to move the RecreatePersistedControls to a place earlier in the process. As Paul Wilson says: "The best use of this method (LoadViewState) is to restore any dynamic controls you created in events, based on values you must manually save in ViewState, which is now available to use.". Perfect - moved RecreatePersistedControls into an overriden LoadViewState like this:

protected override void LoadViewState(object savedState)
{
    base.LoadViewState (savedState);
    _persisted = (ArrayList)ViewState["persisted"];
    RecreatePersistedControls();
}

View the completed demo: DynamicTable3.aspx (source)

Labels: ,

Excellent article Russ. I know this article is definitely going to save me a lot of headache one day.

Post a Comment