ViewState

The Good, the Bad, and the Ugly

asp:Feature

LANGUAGES: C#

ASP.NET VERSIONS: 2.0

 

ViewState

The Good, the Bad, and the Ugly

 

By Kamran Qamar

 

At the dawn of ASP.NET, the first thing that caught my eye was a small demonstration. The presenter dropped a Label and a Button control on a page, and wrote the following on the button click event handler:

 

label1.Text = (int.Parse(label1.Text)++).ToString;

 

Each time we clicked the button, we were presented with the click count.

 

The magic was because of a feature called ViewState. Some of us love this feature; some of us hate it. The ASP.NET team listened to the complaints and suggestions made by the community and updated the way ViewState works. In this article I will explain the good, the bad, and the ugly of ViewState, and what has changed in ASP.NET 2.0. I am assuming that readers already know what ViewState is and how it works. If you need to refresh your memory, I recommend these two excellent articles:

 

The Problem

The advantage of the Web statelessness is the ugly part for developers who have to maintain, manage, and track a user s Web session. ASP.NET provides a number of mechanisms to accomplish this, including Cache, Session, ViewState, and Profile. All of these methods have their own space in this problem domain. ViewState, as the name implies, manages the state of the server controls with which a user interacts on a Web Form.

 

The Good

Because the Web is stateless, once ASP.NET serves a page, it loses all information about that page and its child controls. When this page is posted back, the ASP.NET framework must recreate all the controls on the fly, and reset them to their original state. ViewState employs a straightforward mechanism of persisting a page s ViewState within a hidden HTML input field named __VIEWSTATE. When the page is posted back, this hidden field is pushed back to the server, where ASP.NET captures it, parses it, and sets the state of the page s controls.

 

The Bad

Because ViewState is enabled by default, you can imagine that a Web Form with dozens of server controls will result in a large amount of data stored in the __ViewState hidden field. This is especially true if you are using repeater data controls like DataGrid. With a modest number of columns, say five columns and 10 rows per page, you have 50 controls on your page and each control state is persisted in the hidden field. You might not consider it a disadvantage if you are an intranet developer or if you are positive that all of your users have high-speed Internet access. However, it can be a real problem for a public Web application developer, and it s something that should be taken into account as a page is being developed.

 

The Ugly

Because the ViewState is persisted by using a well-known format, it is not difficult to hijack. For instance, a hacker can manipulate your catalog page and set the prices of your items to, say, one cent, and then post it back to buy these items at this price. Therefore, the security of a Web site can be compromised if ViewState is not used properly.

 

Note: ASP.NET 1.x uses the System.Web.UI.LosFormatter class to serialize/deserialize the ViewState, which in turn is stored as Tuple, consisting of hierarchical collections of triplets, pairs, etc. Again, see Scott Mitchell s previously referenced article to learn how the ViewState is persisted and retrieved. The Microsoft Pattern and Practice group devotes an entire chapter to ways of securing the Web application and services. Consult Securing Your ASP.NET Application and Web Services (http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/THCMCh19.asp) to learn how to secure your ViewState by enabling the MAC.

 

ASP.NET 2.0 Improvements

The ASP.NET team has looked into these problems, and has updated the way ViewState s functionality works. The rest of this article will explore these changes.

 

Control State

Server-side control developers are accustomed to using ViewState to persist the state of their controls between post backs. We generally define our properties in a server control like this:

 

public string PaymentUrl

{

 get{return ViewState["PaymentUrl"];}

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

}

 

Here we piggyback the ViewState to save the state of our custom server control. However, this is not the best method. This technique requires enabling the Page-level ViewState; otherwise, the state will not be preserved and we will have unpredictable results. To clarify this point, create an aspx page, drop a label and button on it, and write the following line of code at the button click event.

 

void Button1_Click(object sender, EventArgs e)

{

 this.Label1.Text =

  (int.Parse(this.Label1.Text) + 1).ToString();

}

 

When you run this page and click the button, the label will be updated with the click count, as expected. Now add the EnableViewState attribute with the value set to false in the Page directive and re-run the page. This time the label will not update with each click because page ViewState has not been saved.

 

The same thing will happen with our server controls if they use ViewState to save the state of the control. This is frustrating for page and control developers, who are ever mindful of their overall page size.

 

The ASP.NET team has introduced another level of state, called ControlState. ControlState lives within the ViewState (keeping ControlState within the ViewState saves the extra cost of serialization and deserialization). However, it is separated from the older ViewState in two ways:

1)     Enabling, disabling of ViewState at the page level does not affect ControlState. The framework will always try to save the control s state.

2)     The control state serialization/deserialization is delegated to the control developer.

 

Let s build a custom Label control to gain better understanding of ControlState. Our Label control will preserve its state across postback, even when the Page s or Control s EnableViewState property is set to false. Figure 1 shows the complete listing of the PersistentLabel control.

 

namespace cpSphere.Articles

{

 ///

 /// Sample control that uses ControlState to

 /// persist it state accross the postback.

 ///

 public class PersistentLabel :

  System.Web.UI.WebControls.WebControl

 {

   private string _text;

   public string Text

   {

       get { return _text; }

       set { _text = value; }

   }

   protected override void OnInit(EventArgs e)

   {

       // A control need to register itself with Page

       // as ControlState participent.

       Page.RegisterRequiresControlState(this);

       base.OnInit(e);

   }

   ///

   /// Framework, leave it for developer to define

   /// or structure the control state for saving.

   /// We should use one of the supported objects

   /// to persist state.

   ///

   protected override object SaveControlState()

   {

       return _text;

   }

   ///

   /// Since it is we who are defining persisting

   /// standard we also have to deal with loading

   /// it back.

   ///

   ///

   protected override void LoadControlState(

    object savedState)

   {

       _text = (string)savedState;

   }

   protected override void Render(HtmlTextWriter writer)

   {

       writer.Write(_text);

       base.Render(writer);

   }

 }

}

Figure 1: The PersistentLabel control.

 

The ASP.NET framework only checks for the ViewState status at the parent level. If the parent s ViewState is set to false, it ignores all child controls. A control s state should be persisted in the ControlState, even when the ViewState is off at the parent level. One way to accomplish this is to call the SaveControlState method of all the controls in the page. However, it is not necessary for each control to participate in the control state. Therefore, at the page level, ASP.NET 2.0 maintains an internal list of controls that require saving their state in ControlState. ASP.NET then saves the state of these controls, regardless of the ViewState status. Any control that wishes to use ControlState has to register itself on this list. This is done by calling the RegisterRequiresControlState method of the Page class. The most appropriate place to call this method is in the OnInit method of the control:

 

protected override void OnInit(EventArgs e)

{

 Page.RegisterRequiresControlState(this);

 base.OnInit(e);

}

 

Registering our control as a ControlState participant only means that the framework will call our control s SaveControlState and LoadControlState methods. It s up to the developer of the control to pass the state of the control to the page to save in ControlState, and repopulate the control with the original state at postback. Therefore, we must override these two methods.

 

Saving and Retrieving the Control State

We can pass any object as control state to the Page for saving. However, the State serialization mechanism implicitly supports only basic value types: String, DateTime, Color, Pair, Triplet, IndexedString, arrays, ArrayLists, Hashtables, and HybridDictionaries. If we pass an object of any other type, the serializer will look for a TypeConvertor for the object that can be converted to or from the string. While doing so, it not only stores the fully converted string, but also the full type name of the object. This information is used during deserialization to recreate the object. Therefore, it is strongly recommended that you avoid passing objects of any other type as state objects.

 

I recommend using ArrayList or HybridDictionary objects for complex controls; and make sure that their elements are one of those listed above. If you have complex objects whose state you want to save, decompose the state using Pair, Triplet, etc. Do not try to convert Value-Type data type (e.g., int, bool, double, etc.) to a string while populating the state object, as they are treated differently by the serializer. The motto here is: a little prior work goes a long way.

 

In our control, we are only interested in saving the label s text value, which is a string; we will simply return it like this:

 

protected override object SaveControlState()

{

 return _text;

}

 

At postback, the Page calls the LoadControlState method of all the controls in its list, passing each control its state object. It is again left for the control developer to populate the control with this state object. In our sample control, our state object is a simple string; we can simply cast it back to a string and set the Text property:

 

protected override void LoadControlState(object savedState)

{

  _text = (string)savedState;

}

 

A point of caution is in order here: Remember, the control s state is still saved within the ViewState by default; hence, it contributes to the overall size of the page as it did before. The Page developer cannot turn it on or off. Therefore, a control developer should be very careful while using this feature. He should save the absolute minimum set of information that is required for the proper behavior of the control.

 

Serialization of ViewState

As stated above, the ControlState is saved within the ViewState. Therefore, the serialization mechanism of the ViewState must be updated. In ASP.NET 1.x the ViewState is serialized using the LosFormatter class:

 

Object-Type;

where Object-Types are:

 t = triplet,

 p = pair,

 l = ArrayList etc.

 

You can verify it by decoding any __VIEWSTATE field value from Base64 to ASCII; the following line of code will work:

 

String decodedState =

 System.Text.Encoding.Default.GetString(

 Convert.FromBase64String(ENCODED_VIEWSTATE));

 

Figure 2 shows the result of decoding the ViewState. As you can see, there are a number of < and > characters. These characters are used to separate each property type and its value. They contribute significantly to the size of the ViewState.

 


Figure 2: ViewState decoded.

 

ASP.NET 2.0 has taken the first step in reducing the size of the ViewState hidden field by changing the serialization format. Now ASP.NET 2.0 uses the ObjectStateFormatter class to serialize/deserialize the ViewState. The ObjectStateFormatter writes data to a binary formatter and employs non-printable characters to mark the type of an object and beginning and ending of its values. This formatter also uses special tokens/codes for compact storage (e.g., all the integers are stored as a 7-bit encoded number, hence integers between 0-255 could be stored as 7-bit as compared to 4 byte). Similarly, Booleans are stored as a simple flag, 1-bit, as compared to the true, false string in LosFormatter.

 

Note: If you are interested in the full list of the object types that are supported and the tokens/codes used by ObjectStateFormatter, I recommend you read a great blog entry by Victor Garcia (http://weblogs.asp.net/vga/archive/2004/05/26/WhidbeyWillBringsUsAShorterViewstateGuaranteed.aspx).

 

The greatest improvement in serialization came in the form of IndexedString. To understand the benefit, suppose you have 10 TextBoxes on the page and each has a Text property that needs to be saved. The LosFormatter class saves this as key-value; hence, we have 10 Text keys. The ObjectStateFormatter maintains a separate list of strings and uses an index instead of the text; hence, in ObjectStateFormatter, there will be only one Text string value, and an index value will be used for all other appearances of Text string. Because few numbers of characters are needed to encode, this not only decreases the size of the ViewState, it also improves the efficiency of the lexical analysis while parsing the ViewState.

 

In the code download, I have included a small utility to decode the ViewState. It uses the ObjectStateFormatter class to deserialize the ViewState. Figure 3 shows how it looks. The code behind its implementation is beyond the scope of this article; I leave it as an exercise for the reader.

 


Figure 3: Use the ObjectStateFormatter class to deserialize the ViewState.

 

ViewState Persistent Store

By default, the ViewState is saved within the Page HTML using a __VIEWSTATE hidden field. In ASP.NET 1.x, one can override the Page class two methods, namely SaveViewStateToPersistentMedium and LoadViewStateFromPersistentMedium, to provide a custom store.

 

Although this is a workable solution (and is still supported), it requires we either override these two methods in all the aspx pages of a solution or use a different base class. The first option allows us to selectively use different persistent media for a page, but it requires a lot of work. The second option has a lot of potential; a custom base class derived from the Page class allows us to plug in a number of useful goodies, including ViewState management.

 

ASP.NET 2.0 provides a much more elegant solution in the form of adaptive rendering. Before we jump into this solution, let s step back and see how ViewState persistence works in the default mode.

 

ASP.NET uses PageStatePersister and IStateFormatter objects to save and load the ViewState across the postbacks. Figure 4 shows the relationship of these classes. As you can see, the Page class works as Factory to create the concrete PageStatePersister class to use as a state-saving algorithm.

 


Figure 4: PageStatePersister and IStateFormatter save and load ViewState across postbacks.

 

PageStatePersister is an abstract class. The ASP.NET framework provides two concrete implementations, namely:

1)     HiddenFieldPageStatePersister, which, as its name implies, persists the state in the page s HTML as a hidden field. This is the default persister that is used by the framework.

2)     SessionPageStatePersister, which stores the ViewState within the user session object. When a page request is made by one of the mobile devices, the framework uses this class to maintain the ViewState.

 

To better understand the whole process, look at the sequence diagram in Figure 5. When the SavePageStateToPersistenceMedium method is called, it first gets the correct PageStatePersistent object by checking PageAdapter. If there is no PageAdapter, it returns a new instance of HiddenFieldPageStatePersister by default. The framework then passes the state to this persister to save. The same thing happens when LoadPageStateFromPersistenceMedium is called.

 


Figure 5: This sequence diagram explains the SavePageStateToPersistenceMedium method.

 

The SaveViewStateToPersistentMedium method uses the Page s Persister property to obtain concrete implementation of the PageStatePersister class, and uses it to save the ViewState. The simplest method of changing the default behavior is to override the Persister property to return whatever PageStatePersister you want to use. However, there is one caveat: the Persister property is protected virtually (i.e., you have to sub class from the Page class to end up with the same situation as stated before). The recommended method of creating a custom ViewState persistent medium is to use control adapters. In the next section, we ll create a custom persistent store.

 

Custom Persistent Stores

First, a warning: The custom adapter I am creating is simply to show how to implement your own custom ViewState persistent provider. I have not tested the performance impact and do not recommend using it in production without proper stress analysis. See Peter Bromberg s article at http://www.eggheadcafe.com/articles/20040613.asp for more on stress test results for ASP.NET 1.x.

 

Why implement a custom persistent store in the first place? Our goal is to reduce the page size and save the bandwidth, as well as to encrypt it for improved security. We can save the ViewState on the server side using any of the following mechanisms:

  • User Session,
  • Application Cache,
  • File,
  • Database, etc.

 

While choosing any one of these, we should ask:

  • How much bandwidth are we saving?
  • How much stress are we putting on the server?
  • What is the impact on the server s resources, especially memory?
  • In case of a page with databound objects, is it better simply to re-bind the controls with fresh data, then save their state?

 

There is no standard answer for these questions, because it varies from situation to situation. Therefore, we need to know how to implement an elegant solution for persisting ViewState. For demonstration purposes, we ll use Application Cache as our ViewState persistent store. Start Visual Studio.NET and create a new assembly project. Create a class, name it CachePagePersister, and have it inherit from the PageStatePersister abstract class. The abstract class has two abstract methods, named Load and Save, which we must implement. The following code shows the Save method:

 

public override void Save()

{

  if (ViewState != null || ControlState != null)

 {

   Pair p = new Pair(ViewState, ControlState);

     string state = StateFormatter.Serialize(p);

     HttpContext.Current.Cache.Insert(Key, state, null,

      DateTime.Now.AddMinutes(

      HttpContext.Current.Session.Timeout),

      TimeSpan.Zero);

     }

 }

}

 

ViewState and ControlState are properties of the PageStatePersister class, and provide direct access to these two state bags. Before saving, we check to see if we have to save values in any of them. We then create a Pair object that combines them. Next, we serialize it using StateFormatter, which returns the IStateFormatter (ObjectStateFormatter) object and inserts it into the cache.

 

Note: It is important to use the StateFormatter property to attain a formatter instead of creating our own instance. This will scale the solution to take into account any Formatter changes at Page level.

 

When we decide to store the ViewState at the server, we must deal with two more issues: associating a ViewState to the user, page, and session; and, we also have to remove the saved ViewState at some time. The simplest solution is to use the raw URL, along with the session ID, as the key identifying the ViewState and Cache s built-in invalidation mechanism to remove it. Notice that ViewState will be flushed out after the session timeout passes. The key is generated using Page URL and Session ID, as shown here:

 

private string Key

{

 get

 {

   return Page.Request.RawUrl + "_" + Page.Session.LCID;

 }

}

 

Now that saving is done, we must implement the Load method. We must check for any ViewState data in cache, retrieve it if it exists, and use it to fill the ViewState and ControlState. The following code demonstrates this:

 

public override void Load()

{

 if (Page.IsPostBack)

 {

    string state = (string)HttpContext.Current.Cache[Key];

    if (state != null)

    {

       Pair p = StateFormatter.Deserialize(state) as Pair;

       ViewState = p.First;

       ControlState = p.Second;

    }

 }

}

 

That s all we have to do to implement the custom persister. However, we cannot use it as-is. To use Adaptive rendering, we must create a Page adapter. For this, we ll create another class that inherits from the PageAdapter class. We need only to override the GetStatePersister method. The code below demonstrates this:

 

public class ViewStatePageAdapter : PageAdapter

{

 public override System.Web.UI.PageStatePersister

  GetStatePersister()

 {

   return new CachePageStatePersister(this.Page);

 }

}

 

Note: Adaptive Page rendering is beyond the scope of this article, but you can read more about it in the article Authoring ASP.NET Server Control Adapters - An Introduction by Vandana Datye (http://msdn.microsoft.com/asp.net/whidbey/default.aspx?pull=/library/en-us/dnvs05/html/ASPNET-AuthorSrvrCntrlAdapters.asp).

 

The last piece of the puzzle is to create a browser definition file and place it in the Browser folder under the application folder. The browser file, along with other things, specifies the mappings between controls and adapters, and is simply an xml file with a .browser extension. For our purposes, we need a simple file, as shown here:

 

 

 

 

    adapterType="cpSphere.Articles.ViewStatePageAdapter" />

 

 

 

The point of interest here is:

 

 

which tells the framework that this adapter should apply to the default browser, and:

 

 

which is the actual line that maps the control and the adapter. If you d like to see the other available mappings, go to the sub folder Browser under your Framework/Config folder.

 

Well, that s it. We re done. When you run your application, the ViewState will be saved in the cache. Figure 6 shows the results of our hard work. Notice there is no value for the hidden field named __VIEWSTATE.

 


Figure 6: Run your application and the ViewState will be saved in the cache.

 

ViewState Hidden Field Size

ASP.NET 2.0 introduces the MaxPageStateFieldLength property. As the name implies, you can set the maximum size of the __VIEWSTATE value; e.g., we can set the maximum size to 100, the framework will break the actual ViewState string into a substring of 100 characters. Then these strings will pass around using a number of __VIEWSTATE hidden fields. To combine them, another hidden field named __VIEWSTATEFIELDCOUNT is used to keep track of the number of __VIEWSTATE fields. Figure 7 shows the result of setting MaxPageStateFieldLength to 40 on DefaultViewState.aspx page. I wonder what will happen if we change the count and post back the page!

 


Figure 7: Set MaxPageStateFieldLength to 40 on DefaultViewState.aspx page.

 

Conclusion

We have learned a lot from ASP.NET 1.x. Based on these experiences and suggestions, ASP.NET 2.0 brings a number of improvements to all facets of Web development. The new version not only improves the size of the ViewState, it also provides an elegant way of defining our own persistent stores. The most useful feature of ViewState is the introduction of ControlState. Control developers should learn when and how to use ControlState as opposed to ViewState.

 

The sample code in this article is available for download.

 

Kamran Qamar works as an independent consultant. He has more than eight years of system architect and design experience. He loves movies and enjoys good company. He is always looking for new challenges. He can be reached at mailto:[email protected].

 

Tips and Tricks

  • For databound controls, always weigh the pros and cons of ViewState against the database call. You can consider caching the database query results. ASP.NET 2.0 provides enhanced caching functionalities.
  • ViewState only tracks the changes made in state after the initialization. Therefore, for dynamically added control, first create the control, set the basic properties, then call the EnsureChildControl method. This way, initial control values will not be included in the ViewState until they are changed. This will decrease the ViewState size.
  • The __VIEWSTATE hidden field is usually at the top of the page. One improvement could be to move this field to the end of the page. The advantage is that the browser will be able to process the page and display it to the user while ViewState is still downloading.

 

 

 

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