Control Building and ViewState Lesson for the Day

Published 06 October 04 03:17 PM | Scott Mitchell

Whenever I am working on a problem and hit a perplexing roadblock that impedes my process, my initial emotional state is calm. “I'll figure out this problem soon,” I tell myself, as I explore workarounds or examine the code base to try to understand why I can't do what I know I should be able to do. If this search goes on for five or more minutes, I start to get a little flustered. Off to Google!” I declare, hoping there has been some other unfortunate soul who has experienced the same problem and has shared his solution. I usually hit the Google Web search first and, if I have no luck there, I delve into the USENET postings on Google Groups. If 15 minutes have passed with no hits from Google, I start to get angry at myself. “I SHOULD KNOW HOW TO FIX THIS,” I say, as I pour back over the material I examined in the first five minutes, knowing there must be somethinig I overlooked. What's interesting is that if this search continues into, say, the half hour mark, my emotions start to sway from frustration and despair to glee. “Wait until I figure out what went wrong,” I reason, “and I can share it on my blog, and it will help others down the road.” And that's what keeps me going.

Today's lesson - which will hopefully save you the 40 minutes it cost me - centers around composite controls, the DropDownList Web control, and ViewState. Let's say you want to create a composite control that contains a DropDownList that already is bound to database data (namely, it does not allow the control developer to specify the DropDownList's DataSource as would normally be the case: for a discussion on the pros and cons of this approach check out Composite Controls That Do Their Own DataBinding by Andy Smith). If you are at all like me, you're composite control's code would look like this (WARNING: this is wrong):

public class MyControl : WebControl, INamingContainer
{
...

protected override void CreateChildControls()
{
DropDownList ddl = new DropDownList();
if (!Page.IsPostBack)
// do data binding to some database...

Controls.Add(ddl);
}
}

This, as aforementioned is the WRONG WAY to do it. Why? Well, give the control a whirl by creating an ASP.NET Web page with this control and a Button Web control. When you visit the page the first time, the DropDownList is displayed, populated with the database data. However, if you click the Button and the page posts back, the items in the DropDownList disappear - the DropDownList is not saving its view state.

If you're like me, the first thing you do is crack open Developing Microsoft ASP.NET Server Controls and Components, thumb through to Chapter 12: Composite Controls, and read the section titled “State and Child Controls.” The gist of the section says that composite controls don't need to employ extra effort to save the view state of their children since it's saved automatically when the page recursively walks the control tree to generate the entire view state. Opening Reflector, I poured through the Page and Control classes, and saw that, yes, the control hierarchy should be walked and the view state saved, so when my composite control's SaveViewState() method was called, it should save not only its own view state, but the view state of its child(ren): namely, the DropDownList. But, evidently it was not.

This got me to scratching my head. Why isn't the DropDownList's view state being saved? EnableViewState for both the Web page, composite control, and DropDownList were all set to True (the default). I looked at the SaveViewState() method for the DropDownList and confirmed that it should be saving its view state. So why wouldn't the DropDownList be saving its view state?

And then it occurred to me - check to make sure that the DropDownList's IsTrackingViewState property is True - if it isn't, then the DropDownList won't be recording changes to its state, and therefore won't produce a ViewState. A control begins tracking its view state when its TrackViewState() method is called, and this is handled after the Initialization stage of the page lifecycle. So, if the DropDownList is being added after that stage, then it wouldn't be tracking its view state, and therefore its state would be lost on postbacks.

But wait a minute, if you add a child control via the Controls.Add() method, the added child control's TrackViewState() method is automatically called. But wait! Only those items added to the view state after the view state has started being tracked will be recorded. So binding the items to the DropDownList prior to adding the DropDownList to the Controls collection causes those additions to not be saved in the view state for the DropDownList. Rather, we need to first add the DropDownList to the Controls collection and then bind the database data to the list. The order matters because we don't want to bind the data to the DropDownList until after we've started tracking view state.

To summarize, the correct code should look like:

public class MyControl : WebControl, INamingContainer
{
...

protected override void CreateChildControls()
{
DropDownList ddl = new DropDownList();
Controls.Add(ddl); // Add the control FIRST

if (!Page.IsPostBack) // Bind data AFTER
// do data binding to some database...
}
}

Partial credit goes to (IIRC) Alex Lowe. If I'm remembering correctly, way back in (yikes!) 2001 he tech reviewed ASP.NET: Tips, Tutorials, and Code for which I authored some chapters, and on one of the chapters I had an example of adding dynamic controls to a page and - again, if my memory doesn't fail me - he noted that you wanted to first add the control to the page prior to setting properties that needed to be maintained in the view state.

Comments

No Comments

Archives

My Books

  • Teach Yourself ASP.NET 4 in 24 Hours
  • Teach Yourself ASP.NET 3.5 in 24 Hours
  • Teach Yourself ASP.NET 2.0 in 24 Hours
  • ASP.NET Data Web Controls Kick Start
  • ASP.NET: Tips, Tutorials, and Code
  • Designing Active Server Pages
  • Teach Yourself Active Server Pages 3.0 in 21 Days

I am a Microsoft MVP for ASP.NET.

I am an ASPInsider.