ASP.NET 2.0 Page State Persister

 

Matt Gibbs
Development Lead, Web Platform and Tools

December 2005

Applies to:
   ASP.NET 2.0
   Visual Studio 2005

Summary: ASP.NET 2.0 adds support for altering the location where ViewState data is stored for your pages. This article looks at how a new class can be created and used to store ViewState information in Session rather than the default hidden field. (5 printed pages)

ASP.NET control developers utilize ViewState and control-state to persist state information between requests from the browser. Typically, this information is carried down to the client as a hidden field in the HTML markup rendered by the page. This page state is then submitted as part of the next form submission back to the server and restored to the control or page. This allows control developers to easily provide a richer application experience by leveraging the ability to store state information temporarily even though the browser is using the HTTP protocol, which is, by definition, stateless.

ASP.NET 2.0 allows you to modify where and how the page state is temporarily persisted. In some cases, it may be preferable to avoid roundtripping the data between client and server. The two page state persisters provided by ASP.NET 2.0 are the HiddenFieldPageState persister we already mentioned and the SessionPageStatePersister. The SessionPageStatePersister utilizes server sessions associated with the browser session to store the data. There are pros and cons to using the SessionPageStatePersister. Using session instead of a hidden field avoids an increase in the page size being sent to and from the browser. In many situations, the page state is a significant part of the overall markup. However, storing this data in session consumes valuable server resources. In addition, hidden fields do not have an associated timeout the way sessions do. You could configure an application to persist sessions to a backend database and avoid putting the load directly onto the web server. This would also scale out to web farm scenarios.

In order to use a persister other than the default, you override the PageStatePersister property of the page and return an instance of another persister. First, here is a simple page that just populates an ArrayList with a bunch of numbers and then binds it to a GridView control.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<script runat="server">
    protected override PageStatePersister PageStatePersister {
        get {
            return new SessionPageStatePersister(this);
        }
    }
    protected override void OnLoad(EventArgs e) {
        base.OnLoad(e);
        if (!IsPostBack) {
            ArrayList list = new ArrayList();
            for (int i = 0; i < 1000; i++)
            {
                list.Add(Convert.ToString(i));
            }
            GridView1.DataSource = list;
            GridView1.DataBind();      
        }
    }
</script>
<html xmlns="http://www.w3.org/1999/xhtml" >
<head id="Head1" runat="server">
    <title>Untitled Page</title>
</head>
<body>
    <form id="form1" runat="server">
    <div>
        <asp:GridView ID="GridView1" runat="server" />        
        <asp:Button ID="Button1" runat="server" Text="Submit" /></div>
    </form>
</body>
</html>

When you look at the rendered HTML for the page, you see a large hidden field for carrying the ViewState.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" >
<head><title>
   Untitled Page
</title></head>
<body>
    <form name="form1" method="post" action="default2.aspx" id="form1">
<div>
<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
value="/wEPDwUKMTQ0MDQzNjk2Ng9kFgICBA9kFgICAQ88KwANAgAPFgYeC18hRGF0YUJv
dW5kZx4JUGFnZUNvdW50AgEeC18hSXRlbUNvdW50AhRkDBQrAAEWBh4EVHlwZRkrAh4ETmF
tZQUESXRlbR4JRGF0YUZpZWxkBQEhFgJmD2QWKgIBD2QWAmYPDxYCHgRUZXh0BQEwZGQCAg
9kFgJmDw8WAh8GBQExZGQCAw9kFgJmDw8WAh8GBQEyZGQCBA9kFgJmDw8WAh8GBQEzZGQCB
Q9kFgJmDw8WAh8GBQE0ZGQCBg9kFgJmDw8WAh8GBQE1ZGQCBw9kFgJmDw8WAh8GBQE2ZGQC
CA9kFgJmDw8WAh8GBQE3ZGQCCQ9kFgJmDw8WAh8GBQE4ZGQCCg9kFgJmDw8WAh8GBQE5ZGQ
CCw9kFgJmDw8WAh8GBQIxMGRkAgwPZBYCZg8PFgIfBgUCMTFkZAIND2QWAmYPDxYCHwYFAj
EyZGQCDg9kFgJmDw8WAh8GBQIxM2RkAg8PZBYCZg8PFgIfBgUCMTRkZAIQD2QWAmYPDxYCH
wYFAjE1ZGQCEQ9kFgJmDw8WAh8GBQIxNmRkAhIPZBYCZg8PFgIfBgUCMTdkZAITD2QWAmYP
DxYCHwYFAjE4ZGQCFA9kFgJmDw8WAh8GBQIxOWRkAhUPDxYCHgdWaXNpYmxlaGRkGAEFCUd
yaWRWaWV3MQ9nZMhHZ3iQZp62S8IR8fTJ5ZL42ira" />
</div>
...

When we add an override the PageStatePersister property and use the built-in SessionPageStatePersister, the behavior of the page remains the same, but the storage used for the bulk of the state data is shifted from the hidden field to session state.

    protected override PageStatePersister PageStatePersister
    {
        get
        {
            return new SessionPageStatePersister(this);
        }
    }

Notice in the source of the page that the hidden field value is much smaller, but not gone entirely. ASP.NET will still carry some minimal set of data in the page output.

<input type="hidden" name="__VIEWSTATE" id="__VIEWSTATE" 
value="/wEPaA8FDzhjNzkyNTMzNjE1YWEyNxgBBQlHcmlkVmlldzEPZ2QZw
44JLJFcglwRl9TiNliE82yAuQ==" />

In some scenarios, you may want to just add code like this to a small set of pages, so adding a simple override like this may acceptable. In the case where you want this behavior for an entire application or for a larger set of pages, you will want a more centralized way of controlling it. There are several ways to accomplish this. We can move the code that creates the persister into class that inherits from page:

using System;
using System.Data;
using System.Configuration;
using System.Web;
using System.Web.Security;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using System.Web.UI.HtmlControls;
public class PagePersisterBasePage : Page
{
    public PagePersisterBasePage()   {
    }
    protected override PageStatePersister PageStatePersister {
        get {
            return new SessionPageStatePersister(this);
        }
    }
} 

ASP.NET 2.0 allows you to specify a base type for a page by using the "Inherits" page directive. The code generated by ASP.NET for that page then inherits from that base page and removes the need to duplicate the code in every page.

<%@ Page Language="C#"  Inherits="PagePersisterBasePage" %>

Furthermore, configuration options allow us to set a location of pages to all use a single base page type. In this web.config page, we set the pageBaseType and don't need to add the Inherits attribute to any pages to get the custom PageStatePersister behavior.

<?xml version="1.0"?>
<configuration>
   <system.web>
    <pages pageBaseType="PagePersisterBasePage" />
   </system.web>
</configuration>

Changing the PageStatePersister is not something that should be done lightly. Consider carefully your application and deployment. There is overhead associated with roundtripping the ViewState in a hidden field, but there is direct consumption of server resources to keep the state there. You can also see from the previous example, that you could plug in a custom persister to store the state elsewhere like in a backend database or web farm shared state service. And, as we demonstrated, you can control the behavior for the application centrally or on a page by page basis.

 

About the author

Matt Gibbs is a lead software design engineer on the ASP.NET team at Microsoft, where he has worked on Web development technologies since 1997. He has co-authored several books on ASP and ASP.NET.

© Microsoft Corporation. All rights reserved.