Gain Control Of Your Control State

asp.net, code 0 comments suggest edit

Some people think the ViewState is the spawn of the devil. Not one to be afraid of being in bed with the devil, I feel a tad bit less negative towards it, as it can be very useful.

Still, it has its share of disadvantages. It sure can get bloated. Not only that, but disabling ViewState can wreack havock with the functionality of many controls.

This is why ASP.NET 2.0 introduces the control state. The basic idea is that there is some state that should be considered the data for the control, while other state is necessary for the control to function. For example, the contents of a GridView. The control doesn’t absolutely need this data persisted across postbacks to function properly. You could choose to reload it from the database, Cache, or Session.

In contrast is the state of the selected node in a TreeView. This is state that is necessary for the control to function properly across postbacks.

Unlike the ViewState, the control state isn’t implemented as a property bag. You have to do a little bit of extra work to make use of it. Namely, there are two methods you have to implement in your custom control.

  • LoadControlState – Restores the control state from a previous page request. ASP.NET calls this method passing in the control state as an object to this method.
  • SaveControlState – Saves any changes to control state since the last post back. You need to return the state of the control as the return value of this method. ASP.NET will store it.

Your custom control must also register the fact that it needs the control state by calling Page.RegisterRequireControlState.

A Demonstration That Makes This All Clear As Mud

I’ve put together a simple control to demonstrate the control state. Now before I go any further, I must warn you not to copy and paste this implementation. This implementation is designed to clarify how the control state works. I will present another implementation that describes a safer approach, which you can feel free to copy and paste. You’ll see what I mean.

public class ControlStateDemo : WebControl
{
  public int ViewPostCount
  {
    get { return (int)(ViewState["ViewProp"] ?? 0); }
    set { ViewState["ViewProp"] = value; }
  }

  public int ControlPostCount
  {
    get { return controlPostCount; }
    set { controlPostCount = value; }
  }
  
  private int controlPostCount;

  protected override void OnInit(EventArgs e)
  {
    //Let the page know this control needs the control state.
    Page.RegisterRequiresControlState(this);
    base.OnInit(e);
  }

  protected override void OnLoad(EventArgs e)
  {
    ViewPostCount++;
    ControlPostCount++;
    base.OnLoad(e);
  }

  protected override void Render(HtmlTextWriter writer)
  {
    writer.Write("<p>ViewState: " + this.ViewPostCount + "</p>");
    writer.Write("<p>ControlState:" + this.ControlPostCount + "</p>");
    base.Render(writer);
  }
  
  protected override void LoadControlState(object savedState)
  {
    int state = (int)(savedState ?? 0);
    this.controlPostCount = state;
  }

  protected override object SaveControlState()
  {
    return controlPostCount;
  }
}

This control has two properties. One backed by the ViewState and the other backed by a private member variable. Notice that we register this control with the Page in the OnInit method.

In the OnLoad method, we increment each property. For demonstration purposes, we need these properties to change on each postback, and this is as good a method as any.

In the Render method, we simply output the values of the two properties. So far so good, eh?

Now we get to the LoadControlState method. This method is called by ASP.NET early in the control lifecyle (after OnInit but before LoadViewState) in order to provide your control with the saved control state from the previous request.

In this case, we can cast this value to an int and set the control’s state (the value of controlPostCount) to this value.

The SaveControlState method provides ASP.NET the data to store in the control state as the return value. In this example, we return the value of controlPostCount. This is how we knew we could cast the value to an int in LoadControlState.

Now if I drop this control onto a page with a Button control, let’s see what happens after a few postbacks.

Image showing the ViewState and Control State counters with values of
4
each

As expected, both values increment, as they are persisted across postbacks. But what happens if we disable ViewState on the page and click the button a few more times.

Image showing the ViewState counter with a value of 1 and the Control
State counters with a value of
8

As you can see, we retain the control state, while the ViewState is disabled.

But What About Inherited Controls?

I am so glad you asked! In this example, I inherited from WebControl, but what if I inherited from TreeControl, or some other control that made use of the control state. My implementation of LoadControlState and SaveControlState pretty much obliterates the control state for the base class.

The class I wrote here is intentionally simple to show you no real magic is going on. Let’s demonstrate the proper way to save and load the control state by creating a class that inherits from this control.

public class SubControlStateDemo : ControlStateDemo
{
  public int AnotherCount
  {
    get { return this.anotherCount; }
    set { this.anotherCount = value; }
  }

  private int anotherCount;

  protected override void OnLoad(EventArgs e)
  {
    AnotherCount++;
    base.OnLoad(e);
  }

  protected override void Render(HtmlTextWriter writer)
  {
    base.Render(writer);
    writer.Write("<p>AnotherCount:" + this.AnotherCount + "</p>");
  }

  protected override object SaveControlState()
  {
    //grab the state for the base control.
    object baseState = base.SaveControlState();

    //create an array to hold the base control’s state 
    //and this control’s state.
    object thisState = new object[] {baseState, this.anotherCount};
    return thisState;
  }

  protected override void LoadControlState(object savedState)
  {
    object[] stateLastRequest = (object[]) savedState;
    
    //Grab the state for the base class 
    //and give it to it.
    object baseState = stateLastRequest[0];
    base.LoadControlState(baseState);

    //Now load this control’s state.
    this.anotherCount = (int) stateLastRequest[1];
  }
}

In this control, we inherit from the ControlStateDemo control I wrote earlier and added a new property called AnotherCount. The main thing to focus on here is our new implementation of SaveControlState and LoadControlState. We now take great pains to make sure that the base control gets the value it is expecting.

In SaveControlState, the first thing we do is grab the control state from the base control by calling base.SaveControlState. As you recall, this holds the value for the private member controlPostCount.

Since we want to add our own private member, anotherCount to the control state, we create an array to store both values and then return this array to the caller.

Within the LoadControlState method, we know we’re going to be passed in an object array and that the first element of the array is the control state for our base class. So in that method, we grab the first element and pass it to the method call base.LoadControlState, thus giving the base class what it expects to receive for its control state.

We then grab the second element, which is our control state, and set anotherCount to this value.

Let’s look at a screenshot of the result in action. Looks like everything is humming along nicely.

Screen showing our new property also being saved and restored
properly.

I would recommend using this approach anytime you implement control state in a custom control because you never know when you might override the control state for a base class.

Found a typo or error? Suggest an edit! If accepted, your contribution is listed automatically here.

Comments

avatar

5 responses

  1. Avatar for Steven Harman
    Steven Harman March 15th, 2007

    Good tutorial Phil.
    I actually dealt with this not too long when I tried to build an AJAXified Tabstrip control by mashing-up the ASP.NET Menu control with an unordered list. Of course, the next day the AJAX Control Toolkit team released the Tabs control and all of my work went right out the window.
    One thing that may not be obvious to those new to this type of state maintenance code is that you can store the state of multiple private members in the Control State array.
    For example, if your SubControlStateDemo class had a second property, YetAnotherCount that was backed by the local member int yetAnotherCount your SaveControlState method would change like so:
    object thisState = new object[] {baseState, this.anotherCount, this.yetAnotherCount};
    And the LoadControlState method would need to pull both local members back out:
    this.anotherCount = (int) stateLastRequest[1];
    this.yetAnotherCount = (int) stateLastRequest[2];
    The base control's ControlState object is always the first object in the array (position 0) and your control's state data will fill the rest of the array. This is guaranteed as long as each each level down the inheritance chain behaves nicely and pushes their stuff onto a growing stack of state data. You gotta love the stack!

  2. Avatar for DotNetKicks.com
    DotNetKicks.com March 15th, 2007

    You've been kicked (a good thing) - Trackback from DotNetKicks.com

  3. Avatar for Rex
    Rex June 14th, 2007

    Hi All over there,
    Its really cool. I have started working with the control state. As you said i have followed all the steps. I have overridden those LoadControlState and SaveControlState methods to store the selected index of a dropdownlist.
    Reason:
    I dont want the viewstate to be "ON".
    Then it wont fire the SelectedIndexChange event.
    So i used control state instead.
    It Works fine.
    Consider this scenario, where during every postback the selected index will be loaded and the same is retreived back. But when i change the selected index at the client side it will not be saved at the server.
    Now this will not work right. Please tell me whether am i missing something.
    Scenario explained
    Let the selected index during post back is 4.
    selected index in the control state = 4.
    Change it in the JavaScript as 2.
    Now change the selected index of the drop down back to 4 .. it will not fire the Selected Index change event.
    Any suggestion would be graetly appreciated.



  4. Avatar for Nariman Haghighi
    Nariman Haghighi July 29th, 2008

    I'm finding it difficult to see the rational behind not allowing control state to be selectively disabled on the ListControl suite. Take a CheckBoxList that has a 100+ elements.
    Why should the control state be pushed to the client if you're not interested in eventing? With the composite naming convention, we’re looking at 40K per trip for no reason…
    The control should be able to re-apply post data without issue so long as we're binding it before LoadPostData is invoked?
    More curiously, I'm finding that neither LoadControlState or SaveControlState are even called on a CheckBoxList override that includes no events! What a waste!!

  5. Avatar for Yogesh Jindal
    Yogesh Jindal May 11th, 2012

    Very good explanation of control state. Thanks.