Maintain State Control

Diversify the Role of the Viewstate with a Control-specific Cross-page State

CoreCoder

LANGUAGES: C#

ASP.NET VERSIONS: 1.0 | 1.1 | 2.0

 

Maintain State Control

Diversify the Role of the Viewstate with a Control-specific Cross-page State

 

By Dino Esposito

 

Like it or not, page size affects performance. The larger the response, the more time the page takes to download and display. To feel the difference, create any ASP.NET Web form page with a 10-row DataGrid and not many frills, such as tables, images, and stylesheets. Then, run the page and count the size of only one of the page elements the viewstate. Well, only the viewstate of such a page is likely be more than 2KB!

 

The theme of this article is not pure page performance, but rather the role of viewstate in the context of performance and reliability.

 

Often wrongly blamed for deficient page performance, the viewstate plays a key role in the ASP.NET programming model: It is a secure and compact vehicle for storing data that must survive across postbacks. The actual weight of viewstate, however, must be evaluated with respect to other server-side state management tools such as Session and Cache. Data stored in viewstate is stateful information that would otherwise stay on the server, thus taxing the Web server s memory. The key point about viewstate is this: Take advantage of it, but don t misuse it and never overindulge in its storage capabilities.

 

Who Uses Viewstate?

Viewstate can take at least 10 percent of the entire page size and for nothing, from the browser s perspective. Although most controls can work with or without viewstate, for some of them (the most complex) a disabled viewstate can be a serious issue up to the point of jeopardizing the overall behavior. The majority of properties that a server control provides are implemented through the ViewState hashtable:

 

public string Text

{

 get {return Convert.ToString(ViewState["Text"]);

 set {ViewState["Text"] = value;}

}

 

If the viewstate for a given control is disabled, the property is assigned a default value on postback. The assigned value can be the value assigned through the ASPX markup, if any. If not, it will be the value assigned in the control s constructor or in the get accessor of the property. No value programmatically assigned to the property is retained across postbacks. Let s consider a TextBox control.

 

Most of the time you simply use the Text property of the TextBox control. Interestingly enough, this property seems to work regardless of the viewstate settings. When disabled, the viewstate for the TextBox is neither restored nor saved, as expected. However, the methods of the IPostBackDataHandler interface regularly process the TextBox-posted data the HTML value attribute of the <input> tag. In doing so, the LoadPostData method copies the value to the Text property of the server-side TextBox control.

 

It is important to note that this happens only for the Text property. All the other properties are lost, including ReadOnly, MaxLength, colors, and borders. Drilling down a bit, though, you easily see that a disabled viewstate alters the behavior of the control, even for what relates to the Text property. The TextChanged event will repeatedly fire even when no real change occurred over the postback (see the code in Figure 1). What s up?

 

bool LoadPostData(string postDataKey,

                 NameValueCollection postCollection)

{

 string restoredText;

 string postedText;

 // Always the empty string (or what in the ASPX) with

 // the viewstate disabled

 restoredText = this.Text;

 postedText = postCollection.get_Item(postDataKey);

 // Whenever the input differs from the empty string

 // (or what in the ASPX), the event fires. Repeatedly.

 if (restoredText != postedText) {

    this.Text = postedText;

    return true;  // Fires TextChanged

 }

 return false;

}

Figure 1: Disable the viewstate and the TextChanged event will fire too often.

 

When the TextBox is instantiated to serve a new request for the page (i.e., a postback), properties get default values set in the constructor or values assigned through the ASPX markup. With a disabled viewstate, this is what happens to the restoredText variable shown in Figure 1. The first time a different value is posted, the TextChanged event fires as expected. From then on, though, the TextChanged event fires at each postback because the strings compared in Figure 1 are always different. A restored viewstate would guarantee a constant and consistent update of the restoredText variable on each postback.

 

All standard 1.x ASP.NET controls are designed to minimize the impact of a disabled viewstate. In general, though, disabling the viewstate to get pages to download faster may introduce harder to solve problems than just slow rendering. Disabling the viewstate can break the functionality of certain components, especially custom and third-party controls that make undocumented use of the viewstate. More often than not, controls make a private use of the viewstate, meaning that they store in the viewstate private or protected properties completely invisible and not programmable from within the page level. If this is the case, there s no way out other than re-enabling the viewstate. Be aware of this problem you leave to your customers when you design and code custom controls for ASP.NET 1.x.

 

In ASP.NET 2.0, this problem finds a definitive solution with the introduction of the control state. In ASP.NET 1.x you can simulate the version 2.0 control state through a custom hidden field.

 

Introducing Control State

How does control state differ from viewstate? In ASP.NET 2.0, the control state is a subset of the viewstate. You should use control state only for small amounts of critical data that is essential for the functioning of a control across postbacks. In no way should the control state be used as an alternative to viewstate. Control state and viewstate are stored in the same hidden field (__VIEWSTATE) and are managed through pairs of overridable functions. However, unlike the viewstate, the control state can t be disabled. This guarantees that no pages risk being broken by a disabled viewstate. The control state is completely handled by the proprietary control and, in a certain way, the control state is the control s private viewstate.

 

If your custom control has private or protected properties stored in the viewstate, you should move all of them to the control state in ASP.NET 2.0. Anything you store in the control state remains there until it is explicitly removed. The more data you pack into it, the more data is moved back and forth between the browser and the Web server. But in doing so, you gain total control over the working of the component in any host page and you can deliver to your customers a solid component that is not a time bomb or a Trojan horse.

 

Control State in ASP.NET 2.0

In ASP.NET 1.x, the viewstate comes with a minimal, but effective, programming interface. All controls come with a ViewState property, a customized hashtable (StateBag class) where you explicitly store information. The content of the collection is serialized and secured when the page gets to generate the browser s response. The viewstate originates a Base64-encoded hidden field. Upon page loading, the hidden field is decoded, deserialized, and remapped to control properties. You can control the serialization and deserialization process through a pair of overridable methods: LoadViewState and SaveViewState.

 

The implementation of control state develops along the same lines, with one notable difference: There s no global container for control state data like the StateBag class. In other words, you can t rely on, say, a ControlState collection object; you can use any variable type and name you like to store data during the page request. Two overridable methods offer a chance to deserialize and serialize data from and to an output stream. The methods are LoadControlState and SaveControlState (see Figure 2).

 

public class MyButton : Button

 {

 private int m_counter;

 public int Counter

 {

   get {return m_counter;}

   set {m_counter = value;}

 }

 protected override void OnInit(EventArgs e)

{

   base.OnInit(e);

   Page.RegisterRequiresControlState(this);

 }

 protected override void LoadControlState(object state)

 {

   if (state != null)

 {

    Pair p = state as Pair;

    if (p != null)

    {

      base.LoadControlState(p.First);

     _m_counter = (int) p.Second;

    }

    else

    {

      if (state is int)

        _ m_counter = (int) state;

      else

          base.LoadControlState(state);

    }

  }

}

 protected override object SaveControlState()

 {

   object obj = base.SaveControlState();

   if (m_counter != 0)

   {

     if (obj != null)

       return new Pair(obj, _index);

     else

       return (m_counter);

   }

   else

     return obj;

  }

}

Figure 2: A custom control that implements control state.

 

The MyButton control illustrated in Figure 2 has a public property named Counter designed to bufferize the number of times the control is clicked. If you store this value in the viewstate, you re lost as soon as the page developer turns viewstate support off. Before you implement the control state, you must first require ad hoc support from the page infrastructure:

 

protected override void OnInit(EventArgs e)

{

 base.OnInit(e);

 Page.RegisterRequiresControlState(this);

}

 

The RegisterRequiresControlState method adds the control to the list of controls in the page that implements control state. The list is scanned when the page is going to load and save state from the body of the HTTP request. If the control is in the list, the LoadControlState method is invoked. As demonstrated in Figure 2, the method receives a Pair object where the first element is the rest of the state, and the second element is what the control itself stored in SaveControlState. If your control needs to persist more than one piece of information you can group them in an array and store the array. The control state is serialized as a special section within the __VIEWSTATE hidden field.

 

To fully understand control state, and the risk run by 1.x pages, consider that in ASP.NET 2.0 the DataList control stores SelectedIndex and EditItemIndex in the control state.

 

Control State in ASP.NET 1.x

With ASP.NET 2.0 Beta 1 available, most developers have already begun to seriously approach the new platform, which is now only a few months away from release. With 2.0 providing an off-the-shelf solution, I m not sure if I would spend any time fixing the issue for 1.x controls. However, the work is not hard and can be outlined in two steps.

 

First, take your few critical properties out of the viewstate and implement them through protected or private properties. Next, write a couple of methods that serialize and deserialize those properties into a string. You can use any string format you like. These two methods, say LoadMyControlState and SaveMyControlState, will plug into the control infrastructure and work by streamlining the content to and from a custom hidden field. You can call LoadMyControlState from within the OnLoad event and SaveMyControlState from within OnPreRender. Figure 3 shows the source code of a TextBox that retains ReadOnly and BackColor, even with the viewstate disabled. By using the LosFormatter ASP.NET class you can encode your data like the viewstate.

 

public class TextBox : System.Web.UI.WebControls.TextBox

{

 const string CONTROLSTATE = "__CONTROLSTATE";

 protected virtual void LoadMyControlState()

 {

   string ctlState;

   ctlState = Page.Request.Form[CONTROLSTATE];

   if (controlState == null)

      return;

   LosFormatter f = new LosFormatter();

   object state = f.Deserialize(controlState);

   object[] arrValues = (object[]) state;

   ReadOnly = (bool) arrValues[0];

   BackColor = (Color) arrValues[1];

 }

 protected virtual void SaveMyControlState()

 {

   object[] state = new object[2];

   state[0] = this.ReadOnly;

   state[1] = this.BackColor;

   LosFormatter f = new LosFormatter();

   StringWriter writer = new StringWriter();

   f.Serialize(writer, state);

   Page.RegisterHiddenField(CONTROLSTATE,

      writer.ToString());

 }

 protected override void OnLoad(EventArgs e)

 {

   base.OnLoad (e);

   LoadMyControlState();

 }

 

 protected override void OnPreRender(EventArgs e)

{

   base.OnPreRender (e);

   SaveMyControlState();

 }

}

Figure 3: A TextBox that implements control state.

 

Conclusion

Don t blindly kill the viewstate; and especially don t do it if you re using third-party controls including your own team s controls. Aim at controlling the overall page size and define a target size that is both reasonable and in line with your application s expectations. A page size ranging from 20K to 30K is more than acceptable; however, you can even go a little over if the application is particularly complex.

 

Be aware that viewstate is a key part of the ASP.NET infrastructure; you can limit its size and even kill it, but never blindly. Introduced in ASP.NET 2.0, the control state gives you more flexibility you can serialize it at will and keeps your controls out of the page developer s decisions about the overall page viewstate.

 

Dino Esposito is a Wintellect trainer and consultant who specializes in ASP.NET and ADO.NET. Author of Programming Microsoft ASP.NET and Introducing ASP.NET 2.0, both from Microsoft Press, Dino has also helped several companies architect and build effective products for ASP.NET developers. Dino is the cofounder of http://www.VB2TheMax.com, a popular portal for VB and .NET programmers. Write to him at mailto:[email protected] or join the blog at http://weblogs.asp.net/despos.

 

 

 

Hide comments

Comments

  • Allowed HTML tags: <em> <strong> <blockquote> <br> <p>

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.
Publish