Vision Quest

Understanding ViewState

From the Source

LANGUAGES: VB.NET

ASP.NET VERSIONS: ALL

 

Vision Quest

Understanding ViewState

 

By Dave Reed

 

Most ASP.NET developers are familiar with ViewState. It is the mechanism used by ASP.NET to persist changes to the controls on a page across postbacks. It allows Web forms to behave more like Windows forms, where modifications to the form remain active despite the stateless nature of Web requests. For the most part, developers take the mechanism for granted.

 

ViewState is a great tool but like most tools, it can be misused. It is important to have an understanding of precisely how it works when developing highly optimized sites and for avoiding issues you may encounter when working with dynamically created controls or other advanced operations. Without this understanding, you may be misusing ViewState, bloating it with unnecessary data.

 

The ViewState mechanism can be described as having the following four functions:

  • Storing data on a per-control basis by key name, like a Hashtable.
  • Tracking changes from the initial state of the control s data.
  • Serializing dirty data into a hidden form field on the client.
  • Automatically deserializing data on postbacks and restoring it to each control.

 

While ViewState does have one overall purpose in the ASP.NET Framework, these four main roles in the page lifecycle are quite distinct from each other. We can separate them and try to understand them individually.

 

Storing Data into ViewState

ViewState is a protected property defined on the base class for all controls, System.Web.UI.Control. The type of this property is System.Web.UI.StateBag. Each control has its own instance of a StateBag. Strictly speaking, the StateBag has little to do with ASP.NET. It happens to be defined in the System.Web assembly, but there s no reason why the StateBag class couldn t live alongside ArrayList in the System.Collections namespace. The StateBag has an indexer that accepts a string as the key and any object as the value:

 

ViewState("Key1") = 123.45; ' store a number

ViewState("Key2") = "abc"; ' store a string

ViewState("Key3") = DateTime.Now ' store a DateTime

 

In practice, controls use ViewState as the backing store for most, if not all their properties. This is true of almost all properties of all controls, built-in or otherwise. To use ViewState as the backing store for a property means the value of the property is stored in the StateBag rather than a field, as you typically would have (see Figure 1).

 

' using a private field

Private field As String = "default"

Public Property Text() As String

   Get

       Return field

   End Get

   Set(ByVal value As String)

       field = value

   End Set

End Property

' using viewstate

Public Property Text() As String

   Get

       Dim o As Object = ViewState("Text")

       If (o Is Nothing) Then

           Return "default"

       Else

           Return o

       End If

   End Get

   Set(ByVal value As String)

       ViewState("Text") = value

   End Set

End Property

Figure 1: Note that in both cases the default value is the same, because accessing a value in a StateBag with a non-existent key will return null.

 

Tracking Changes from the Initial State

The StateBag has a tracking ability. Tracking is either on, or off. Tracking can be turned on by calling TrackViewState on the StateBag, but once on, it cannot be turned off. When tracking is on, any writes to the StateBag will cause that item to be marked as dirty. The StateBag even has a method you can use to detect if an item is dirty, IsItemDirty (see Figure 2). You can also manually cause an item to be considered dirty by calling SetItemDirty.

 

' before tracking begins...

stateBag.IsItemDirty("key") 'false

stateBag("key") = "foo"

stateBag.IsItemDirty("key") 'false

' after tracking begins...

stateBag.IsItemDirty("key") 'false

stateBag("key") = "bar"

stateBag.IsItemDirty("key") 'true

stateBag.SetItemDirty("key", False)

stateBag.IsItemDirty("key") 'false

Figure 2: How items in a StateBag are marked.

 

Tracking allows the StateBag to keep track of what values have been changed since tracking started. Tracking begins after the internal method TrackViewState has been called. It is important to know that any assignment will mark the item as dirty even if the value given matches the value it already has. ASP.NET could simply serialize the entire collection of items, but it actually only serializes the items marked as dirty. This is why tracking is important. To appreciate the benefit this provides, you must understand a little bit about how ASP.NET parses markup:

 

<asp:Label id="Label1" runat="server" Text="Hello World" />

 

ASP.NET parses the above markup, generating code that creates an instance of the specified control, a Label. The Text attribute declared within the tag prompts ASP.NET to use reflection to detect whether the control has a property by that name. It does, so the generated code sets its value to the declared value.

 

Because most properties of controls use their StateBag as their backing store, the value of the Text property, Hello World , is saved as an entry in the StateBag. However, the StateBag during this stage in the page lifecycle is not yet tracking. ASP.NET recursively calls TrackViewState on every control s StateBag during the Init phase, and markup is processed before that.

 

This is what allows ASP.NET to detect the difference between a declaratively set value and a dynamically set value. Entries in the StateBag that correspond to declared attributes will not be marked as dirty.

 

The Init and Load events are interesting in that the order in which they occur is reversed. Init begins at the bottom of the tree and works its way up. Load begins at the top and works its way down. The consequence of this is that when the Init event occurs in a control or page, the Init phase has already occurred for all of its child controls. It also means while the control firing the event is still not tracking changes to its state, its child controls are! Keep that in mind when manipulating the state of child controls. Any properties you modify are going to be marked as dirty and will therefore be serialized into ViewState.

 

Serializing Dirty Data into a Hidden Form Field on the Client

If you ve ever viewed the HTML source of an ASP.NET page, you ve likely noticed the hidden form field named __VIEWSTATE. That data is the result of base-64 encoding the serialized StateBag for all the controls on the page. How this process occurs is interesting.

 

Controls on a page are structured as a tree, where the Page is the root of that tree. Each control has child controls, which form branches of the tree. Recall that each control in this tree has its own instance of a StateBag. During the SaveViewState phase of the page lifecycle, which is after PreRender, ASP.NET recursively calls the SaveViewState method on every control s StateBag in the control tree. What results from this process is a tree that is structured not unlike the control tree itself, except instead of a tree of controls, it is a tree of data.

 

The data at this point is not yet serialized into the string you see in the hidden form field. The key here is in understanding how it is determined which data winds up in the tree and which data doesn t. When the StateBag is asked to save its state, it only saves items in it that are marked as dirty. That means the serialized state is a representation of only the data on the page, which is different than how it was declared.

 

Take, for example, a Label control. Whether you declare the Text of the label to be abc or the full text of War and Peace, the size of the serialized ViewState is going to be the same because it won t contain the declared value at all. Only if the Label s text is changed from the declared value dynamically will the value be serialized into ViewState.

 

Only serializing dirty data makes sense. It would only be a waste of resources to serialize the Label s Text: put it in the hidden form field, allow the client to post it back to the server, then deserialize it. The value is declared in the markup, so it s going to be repopulated whether it exists in ViewState or not!

 

Automatically Restoring State on Postbacks

When a postback occurs, the serialized, base-64 encoded, and optionally encrypted string is sent to the server, along with the rest of the form. ASP.NET retrieves the value during the LoadViewState phase in the page lifecycle, which is after Init and before Load. It s also before postback data is loaded for any controls that are postback data handlers. The string is unencoded, unencrypted, and deserialized back into the original tree of data from which it was serialized.

 

The StateBag has a method, LoadViewState, which simply iterates over the items in the given state object and adds them back into its collection. Remember that each control has its own instance of the StateBag, so this process is recursive.

 

ASP.NET begins tracking during the Init phase by recursively calling TrackViewState on each StateBag in the control tree. So all StateBags are tracking changes when ViewState is deserialized. When the page first begins to load during a postback, all properties are set to their natural declared values, prior to when tracking begins. ASP.NET then enables tracking during the Init phase. The LoadViewState phase then reassigns any values from the deserialized data tree, which are only those values that were marked dirty from the previous request. Because tracking is enabled, this causes those items to be marked as dirty once again. The fact they are marked as dirty means they ll be persisted in the next postback once again, even if the value isn t changed again during this request.

 

Misusing ViewState

Without understanding the tracking mechanism of ViewState, it is easy to accidentally cause data to become serialized that doesn t need to be. Figure 3 illustrates a common example of misusing ViewState. It is a custom control that specifies a default value for its Text property by setting it from the Load phase. Because ViewState begins tracking in the Init phase, this is going to have the unfortunate side effect of marking the Text entry in the StateBag as dirty. Hello World will be serialized into ViewState, despite the fact it is the control s default value and does not need to be serialized. In fact, doing nothing more than dropping this control on a page will increase the size of the page s ViewState (unless the page developer disables ViewState for the control).

 

Public Class MyControl

   Inherits Control

   Public Property Text() As String

       Get

           Return ViewState("Text")

       End Get

       Set(ByVal value As String)

           ViewState("Text") = value

       End Set

   End Property

   Protected Overrides Sub OnLoad(ByVal args As EventArgs)

       Me.Text = "Hello World"

   End Sub

End Class

Figure 3: A common example of misusing ViewState.

 

The correct way to specify a default value is as described in Figure 1, by returning the default in the entry as null. This avoids setting the value into the StateBag while it is tracking. Interestingly, the correct way is also more compact than the incorrect way. It is not necessary to override OnLoad.

 

You might also be tempted to do the assignment from the Init phase, as ViewState isn t yet being tracked at that point. That is true, but recall child controls are tracking state by that time. So if you create a composite control, even the Init phase is too late to modify the properties of child controls with default values without unnecessarily bloating ViewState! Instead, specify defaults in child controls as they are created, before they are added to the control tree.

 

Be ViewState-friendly

ViewState optimization is easy when you understand what s going on. Now that you have a complete understanding of how ViewState works, and how it interacts with the page lifecycle, it should be easy to be ViewState-friendly! Besides, ViewState-friendly solutions often are simpler and more compact.

 

Dave Reed is a member of the ASP.NET team at Microsoft. He has a degree in Computer Science from California State University, Northridge in southern California. Dave maintains a blog on .NET and ASP.NET topics at http://weblogs.asp.net/infinitiesloop.

 

The ASP.NET dev team welcomes suggestions for topics you d like to see covered in this column. You can reached Matt Gibbs, Development Manager for the ASP.NET team at Microsoft, at mailto:[email protected].

 

 

 

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