When you think ASP, think...
Recent Articles
All Articles
ASP.NET Articles
ASPFAQs.com
Message Board
Related Web Technologies
User Tips!
Coding Tips
Search

Sections:
Book Reviews
Sample Chapters
Commonly Asked Message Board Questions
JavaScript Tutorials
MSDN Communities Hub
Official Docs
Security
Stump the SQL Guru!
Web Hosts
XML
Information:
Advertise
Feedback
Author an Article

ASP ASP.NET ASP FAQs Message Board Feedback
 
Print this Page!
Published: Wednesday, January 17, 2007

Persisting Page State in ASP.NET 2.0

By Scott Mitchell


Introduction


There are different types of state in an ASP.NET web application: page state, session state, and application state. Page state is state that is specific to a particular user's visit to a particular page and is commonly used to remember any programmatically changed state of the page across postbacks. Session state is state remembered for a particular user across all visits and all pages during their session. Application state is state that is shared across all users on all pages and all requests and is often used for caching data or information that is applicable to all users visiting the site.

Page state, commonly referred to as view state, is persisted in a hidden form field, by default. When a page is being rendered, any programmatic changes to a control's state is saved to the page's overall view state. During the rendering stage, this view state is serialized into a base-64 encoded hidden form field and sent down to the client's browser. On postback, the view state data is sent back to the web server, where it is deserialized and returned to the appropriate Web controls in control hierarchy so that they may re-establish their state as it was prior to the postback.

View state provides a slick way to remember state in a stateless client-server model and it happens underneath the covers without any extra effort from page developers. The downside of view state, however, is that in certain situations the view state can grow to be exceedingly large. A large view state requires a longer page download time since it bloats the total web page size and also affects the postback time, since the entire view state content must be posted back to the web server along with the other form fields.

It is possible, however, to persist view state to an alternate medium. Such customizations were possible in ASP.NET version 1.x by overriding a couple of methods in the Page class. ASP.NET 2.0 makes customizing page state persistence easier as this logic is handled through a separate class. In this article we'll explore the built-in page state persistence options in ASP.NET 2.0, which includes the ability to persist page state to session state rather than through a hidden form field. We'll also look at how to extend the functionality to provide a custom persistence scheme. Read on to learn more!

- continued -

A Quick Look at the Page Lifecycle


Each time an ASP.NET page is requested, it goes through a series of events that make up its lifecycle. The end goal of a page's lifecycle is to render its contents (typically HTML), which is then returned to the client that made the request (typically a browser). Rendering is the last stage of the page's lifecycle, and before rendering the page constructs its control hierarchy, reloads page state, raises any necessary Web control-related events (such as a Button's Click event), and saves the page state, among other things. During this lifecycle, any changes to the page's state must be remembered across postback else any such changes will be lost.

For example, imagine a Web page with a Label Web control whose BackColor property is not set. Also on this page is a Button Web control. Now imagine that this page's Page_Load event handler has code like the following:

Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
    If Not Page.IsPostBack Then
        LabelID.BackColor = Color.Red
    End If
End Sub

This code sets the Label control's BackColor property to Red on the first page visit. On subsequent postbacks, Page.IsPostBack will be True and therefore the code assigning the BackColor property will not execute.

By eyeballing this code it's clear that when first visiting the page, the Label will render with a red background. But what happens when the Button is clicked, causing a postback? The code that sets the Label's BackColor property doesn't run, but the rendered Label's background remains red. This is because this property assignment is remembered across postbacks in the page's state.

On each visit to the page, during the save page state stage of its lifecycle, the Page asks the controls on the page: "Excuse me, but do you have any changed state?"

The Label then raises his hand and sheepishly says, "Ahem, sir? Sir, over here. I have changed my BackColor property to Red." The Page jots this information down.

When the Page produces its final rendering, it takes those notes it jotted down - the changed state - and persists that information to some sort of backing store. By default, the notes are serialized into a base-64 encoded string and persisted to a hidden form field named __VIEWSTATE, but the serialization and backing store can be modified, as we'll see in this article.

For more information on the page lifecycle, see The ASP.NET Page Lifecycle and Dino Esposito's The ASP.NET Page Object Model.

Introducing the PageStatePersister Class


New to ASP.NET 2.0, the PageStatePersister class defines the base functionality needed to persist page state to some backing store. This class is associated with a particular Page instance and provides Load and Save methods (among other members). The PageStatePersister class is abstract, meaning it can't be used directly but rather must be extended. There are two classes that extend PageStatePersister in the .NET Framework 2.0:
  • HiddenFieldPageStatePersister - the default page state persisting logic. Uses a hidden form field to persist page state.
  • SessionPageStatePersister - stores page state in a session variable, which can be used to reduce the bloat associated with persisting page state as a form field.
You can also define your own page state persistence logic by extending PageStatePersister class, opting to persist page state to a database or to text files on the web server's file system or through some other means. Later on in this article we'll look at how to persist page state to the web server's file system later on in this article, but first let's take a closer look at SessionPageStatePersister.

Persisting Page State to Session


The .NET Framework 2.0 includes SessionPageStatePersister class, which persists page state to session. In particular, this page state persister, when used, tracks the page state for each page visit for a user in her session store using two session variables:
  • __VIEWSTATEQUEUE - a Queue object that maintains the names of the session variables that maintains the page state. This object just serves as bookkeeping - it just maintains the list of session state variable names. that store the actual page state. By default, only the last nine page state instances are saved in session, but you can configured your application, raising or lowering this value. Keep in mind that a request to any page (including postbacks) constitutes a "visit", so visiting a single page and posting back eight times would add nine entries to the session state queue and nine additional session state variables.
  • __SESSIONVIEWSTATETicksAsBase16Number - stores the serialized page state. TicksAsBase16Number takes the number of 100-nanosecond intervals that have elapsed since midnight on January 1, 0001 and the current date and time and converts it into a hexidecimal number. Again, by default only nine of these values are stored in session at a time. Upon adding the tenth item, the oldest one is removed from session state.
In its Save method, the SessionPageStatePersister class stores the value of TicksAsBase16Number in the __VIEWSTATE hidden form field. On postback, the Load method examines the passed-back TicksAsBase16Number value and retrieves the appropriate page state from the session variable __SESSIONVIEWSTATETicksAsBase16Number.

To change the default page persistence logic from the hidden form field approach to using the SessionPageStatePersister class, you can either override the Page class's PageStatePersister property or use an adapter. The download available at the end of this article shows how to override the PageStatePersister property using a base page class, with the germane code shown below. For information on using an adapter, see the example at the bottom of this page.

The following code (taken from this article's download) shows how to override the PageStatePersister property, configuring it to use the SessionPageStatePersister class instead of the default HiddenFieldPageStatePersister:

Private _persister As System.Web.UI.PageStatePersister

Protected Overrides ReadOnly Property PageStatePersister() As System.Web.UI.PageStatePersister
   Get
      If _persister Is Nothing Then
         _persister = New FileSystemPageStatePersister(Me)
      End If

      Return _persister
   End Get
End Property

A history of nine page state values are saved in session because of the browser's Back button. Imagine a user visits a page and does a number of postbacks. On each postback, a new TicksAsBase16Number value is calculated and a new session variable is created holding that particular postback instance's page state. Now, envision what would happen if the SessionPageStatePersister class did not store a history of states. Say that our user, after making a number of postbacks, hits the Back button a few times. Their browser now displays an earlier page from the browser cache with an earlier TicksAsBase16Number value!. After hitting Back a few times, the user does a postback, sending an old TicksAsBase16Number value back to the server. Since no history of page state was kept, the page state cannot be found in session, and page state is lost.

To customize the size of the page state maintained in history, which dictates how many times the user can go Back with things still working as expected, add the <sessionPageState> element to Web.config, specifying a value for the historySize attribute like so:

<configuration>
   <system.web>
      <sessionPageState historySize="number" />
   </system.web>
</configuration>

Another downside of using session state to persist page state is that session state is volatile. That is, it automatically is abandoned after a user's session "expires," which occurs, by default, 20 minutes after a user's last activity. So if a user visits a page, does some postbacks, then goes to lunch and comes back 30 minutes later, on the following postback the page state will be lost since the session will have been abandoned ten minutes ago. Also, some users may have their browsers configured to not accept session-level cookies, which means the default session implementation will not work for them, meaning that they will not be able to persist page state across postbacks.

To see the SessionPageStatePersister class in action, its effect on shrinking the hidden __VIEWSTATE form field size, and the contents of session state when this method is employed, download the support material available at the end of this article.

Extending PageStatePersister: Persisting Page State to the Web Server's File System


The download at the end of this article includes a class in the App_Code folder named FileSystemPageStatePersister that persists the page state to a file on the web server's file system. In particular, these page state files are stored in the ~/StateFiles/ folder with the file name TicksAsBase16Number-SessionID.vs and contain the serialized page state. The SessionID is included in the file name since these files are generated for all users visiting the site.

The FileSystemPageStatePersister class stores the page's state to a new file with each "visit" to the page. The file name is remembered across postbacks through a hidden form field, __SKM_VIEWSTATEID, much like how the SessionPageStatePersister stores the TicksAsBase16Number in the __VIEWSTATE hidden form field.

The code for FileSystemPageStatePersister is fairly straightforward. Let's look at Save first:

Imports System.IO
Imports Microsoft.VisualBasic
Imports System.Web.UI

Public Class FileSystemPageStatePersister
   Inherits PageStatePersister

   Private Const ViewStateFormFieldID As String = "__SKM_VIEWSTATEID"
   Private Const StateFileFolderPath As String = "~/StateFiles/"

   ... code omitted for brevity ...

   Public Overrides Sub Save()
      If MyBase.Page.Form IsNot Nothing AndAlso (MyBase.ControlState IsNot Nothing OrElse MyBase.ViewState IsNot Nothing) Then
         'Save state to file
         Dim fileName As String = String.Concat(Convert.ToString(DateTime.Now.Ticks, 16), "-", HttpContext.Current.Session.SessionID, ".vs")
         Dim filePath As String = HttpContext.Current.Server.MapPath(StateFileFolderPath & fileName)
         Dim p As New Pair(MyBase.ViewState, MyBase.ControlState)
         File.WriteAllText(filePath, MyBase.StateFormatter.Serialize(p)
)
         Dim stateField As String = String.Format("{0}<input type=""hidden"" name=""{1}"" value=""{2}"" />{0}{0}", System.Environment.NewLine, ViewStateFormFieldID, fileName)
         MyBase.Page.Form.Controls.AddAt(0, New LiteralControl(stateField))
      End If
   End Sub
End Class

The method starts by determining the file name to save the page state to and then determines the full physical file path using Server.MapPath. Next, the page state, which is represented via the ViewState and ControlState objects, is serialized to the specified file path using the configured StateFormatter. The StateFormatter determines how the page state is serialized and the default StateFormatter serializes page state into a base-64 encoded string.

Next, a hidden form field named __SKM_VIEWSTATEID is added. Its value is the file name that contains the page state. This form field markup is injected into the rendered markup by adding a LiteralControl to the Page's Web Form.

The Load method grabs the file name from the hidden __SKM_VIEWSTATEID form field and then deserializes the page state, assigning it back to the ViewState and ControlState objects.

Public Class FileSystemPageStatePersister
   Inherits PageStatePersister

   Private Const ViewStateFormFieldID As String = "__SKM_VIEWSTATEID"
   Private Const StateFileFolderPath As String = "~/StateFiles/"

   Public Overrides Sub Load()
      'Determine if we have a __SKM_VIEWSTATEID
      Dim stateIdentifierValue As String = HttpContext.Current.Request.Form(ViewStateFormFieldID)
      If stateIdentifierValue.Length > 0 Then
         Dim fileName As String = stateIdentifierValue
         Dim filePath = HttpContext.Current.Server.MapPath(StateFileFolderPath & fileName)
         Dim contents As String = File.ReadAllText(filePath)

         Dim state As Pair = TryCast(MyBase.StateFormatter.Deserialize(contents), Pair)
         If state IsNot Nothing Then
            MyBase.ViewState = state.First
            MyBase.ControlState = state.Second
         End If
      End If
   End Sub

   ... code omitted for brevity ...
End Class

The complete code plus a demo of this custom page state persister is available in the download at the end of this article.

View State Persistence in ASP.NET 1.x


The PageStatePersister class is new to ASP.NET version 2.0. In ASP.NET version 1.x, view state persistence could be customized by extending the Page class and overriding its SavePageStateToPersistenceMedium and LoadPageStateFromPersistenceMedium methods. As these methods' names imply, they are responsible for saving page state to some backing store and loading it back, and are synonymous to the PageStatePersister class's Save and Load methods, respectively. For a more in-depth look at view state as well as ASP.NET 1.x implementation specifics, refer to my article Understanding ASP.NET View State.

Conclusion


In ASP.NET 1.x, page state was stored to a hidden form field by default, although this behavior could be modified by overriding a couple methods in the Page class. With ASP.NET 2.0, page state persistence can be handled by any class that extends the PageStatePersister class. The .NET Framework 2.0 ships with two built-in persisters: HiddenFieldPageStatePersister, which persists page state to a hidden form field (the default); and SessionPageStatePersister, which persists state in session. As we saw in this article, it's possible to create custom page state persisters and plug them into an ASP.NET 2.0 application.

Happy Programming!

  • By Scott Mitchell


    Attachments


  • Download the code used in this article
  • Further Readings


  • Overview of State Persisters
  • Save Some Space, Compress that ViewState
  • The ASP.NET ViewState (ASP.NET 1.x-specific content)


  • ASP.NET [1.x] [2.0] | ASPMessageboard.com | ASPFAQs.com | Advertise | Feedback | Author an Article