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!
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.
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:
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:
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.