Click here to Skip to main content
15,880,392 members
Articles / Web Development / ASP.NET
Article

Maintaining State Per Page in ASP.NET Web Applications

Rate me:
Please Sign up or sign in to vote.
4.64/5 (14 votes)
17 Apr 2007CPOL5 min read 74.3K   467   38   16
This article describes an approach to build ASP.NET Pages that maintain their state when the user navigates between them.

class diagram

Introduction

This article discusses an approach to build ASP.NET pages that maintain their state between requests.

Let me give an example: A user, Hugo, selects a year in the year-selection DropDownList on a page. Unfortunately, he decides to navigate to another page of my web application before continuing to enter data on my page. Later on, Hugo returns to my page and wants to continue entering data. Normally, he would have to start from scratch, but because my page has stored its state, Hugo can continue as he has expected.

What's so special about my approach?

Actually, nothing. But I like designs that help me build programs faster and better. As you will see further on, I use the Session hashtable of the HttpContext as normal, but I have built some wrappers and helpers that simplify my programming task.

Using the code

The StatePage class is a base class for all my pages that want to make use of their own state. It is derived from System.Web.UI.Page. Through the properties of the StatePage, all derived pages have access to two state objects:

  1. PageState: Contains the state of a single page.
  2. InterPageState: Contains the data that is shared by multiple pages.

Both state objects are read from the Session when the page is initialized, and stored back to the Session when the page is rendered.

The InterPageState is used to pass values from one page to another. A common scenario is that a user can select something on a page that is also present on several other pages (for example, a year selection that is present on multiple reporting pages). After selecting it on one page, it would be great if the selection would be the same for all pages all together. Therefore, I called this class InterPageState - the state shared between pages.

Actually, this is how you would use the Session normally, but I prefer using the InterPageState to access the Session directly, because I don't have to play around with key constants and casting operations. This is the same "pattern" as The Essential Geek describes in his article "Wrap Those Session Variables!". Furthermore, I can distinguish between state that is relevant to controls on a page and state that is "real" Session state like information about the user etc.

The PageState objects are handy because they can be used in the context of a single page. I use PageStates to restore the state of a page to the state before the user left the page the last time. PageStates lead to better readability and less complexity compared to one single wrapper for the Session bag because they contain only the state data used for one single page.

Interesting code parts

Loading and saving of the page specific states is provided by the PageState base class, as shown below:

C#
/// <summary>

/// Defines the state of a <see cref="StatePage"/>.

/// </summary>

/// <remarks>

/// This class is used to store information about the state of a page in 

/// order to restore it when the user navigates back to this page.

/// 

/// The <see cref="PageState"/> objects are stored in the 

/// <see cref="System.Web.SessionState.HttpSessionState"/> bag.

/// 

/// Classes can be direved form this class to hold specific data for a 

/// <see cref="StatePage"/>.

/// </remarks>

[Serializable]
public class PageState
{
    /// <summary>

    /// Retrieves the <see cref="PageState"/> object for the 

    /// specified <see cref="StatePage"/>

    /// </summary>

    /// <param name="page">

    /// The page for which the state is retrieved.

    /// </param>

    /// <returns>

    /// State information stored earlier in the current session.

    /// </returns>

    public static PageState GetPageState(StatePage page)
    {
        return 
           (PageState)System.Web.HttpContext.Current.Session
               [page.GetType().FullName];
    }

    /// <summary>

    /// Stores information about the state of a page into the 

    /// <see cref="System.Web.SessionState.HttpSessionState"/> bag.

    /// </summary>

    /// <param name="page">StatePage</param>

    /// <param name="pageState">State object</param>

    public static void SetPageState(StatePage page, PageState pageState)
    {
        System.Web.HttpContext.Current.Session[page.GetType().FullName] = 
            pageState;
    }
}

The key for the Session entry is the fully qualified type name of the Page. This allows that several pages can share the same StatePage class (class, not instance!)

Loading and saving of the state of a page is handled by the StatePage base class. It uses the methods provided by the PageState class (shown above) to access the correct state from the Session.

C#
public class StatePage : Page
{
    /// <summary>

    /// Resets the cache statistics.

    /// Retrieves the current <see cref="PageState"/> for this 

    /// <see cref="StatePage"/>.

    /// </summary>

    /// <param name="e"></param>

    protected override void OnInit(EventArgs e)
    {
        base.OnInit(e);

        if (System.Web.HttpContext.Current != null)
        {
            // get page state for this page

            this.pageState = PageState.GetPageState(this);
            if (this.pageState == null)
                this.pageState = CreatePageState();

            // don't get confused by the next couple of lines of code. 

            // this is not the way I do this in may applications. I'm using

            // an object manager but this is part of an other article ;-)

            // so just get it dirty from the Session bag

            this.interPageState = (InterPageState)Session["InterPageState"];
            if (this.interPageState == null)
            {
                this.interPageState = new InterPageState();
                Session.Add("InterPageState", this.interPageState);
            }
        }
    }

    /// <summary>

    /// Extracts the <see cref="PageState"/> from the page by calling 

    /// <see cref="PullState"/> and stores it in the session.

    /// Calls <see cref="Control.OnPreRender"/>.

    /// </summary>

    /// <param name="e"></param>

    protected override void OnPreRender(EventArgs e)
    {
        if (System.Web.HttpContext.Current != null)
        {
            PullState();
            PageState.SetPageState(this, this.PageState);
        }

        base.OnPreRender(e);
    }

    ... other code
}

If no PageState is present in the Session, then a new one is instantiated. The default implementation of the virtual CreatePageState method returns null. To define a page specific PageState, the derived page has to override the CreatePageState method - this is shown in the sample page further below.

Accordingly, the InterPageState is accessed from the session. The InterPageState is included in this sample just to emphasize the difference between the state per page and the state that is beyond the scope of a page. Hopefully, I'll find some time to write an article about my instance managing pattern that is used to access the InterPageState and other singleton instances. The above sample is a quick and dirty implementation in order to have a running sample application.

Before the page is rendered, there is the last change to writes values to the PageState. This is accomplished in a derived class by overriding the PullState method (an example is shown below). After the state is defined, it is stored to the Session by the PageState class.

The following diagram summarizes the steps for loading, accessing, updating, and saving a PageState.

life time of PageState instances

Example stateful page

The following example shows a page that is derived from my StatePage and uses its functionality to store a year value and a text value:

C#
public partial class Page2 : StatePage
{
    /// <summary>Just a small helper</summary>.

    /// <remarks>

    /// I normally use Dependency Injection for intitalizing member objects 

    /// but that's part of another article ;-)

    /// </remarks>

    private DropDownListHelper dropDownListHelper = 
        new DropDownListHelper();

    /// <summary>

    /// Load data.

    /// </summary>

    /// <remarks>

    /// I override the event invoker method instead of registering the Load

    /// event because I don't like objects registering events of themselves.

    /// </remarks>

    /// <param name="e"></param>

    protected override void OnLoad(EventArgs e)
    {
        base.OnLoad(e);

        if (!IsPostBack)
        {
            // try to select the value from the InterPageState

            this.dropDownListHelper.SetSelectedValue(
                this.ddlYear, 
                this.InterPageState.Year);

            // restore the text of the TextBox

            this.txtText.Text = this.PageState.Text;
        }
    }

    /// <summary>

    /// Create the PageState for this page.

    /// </summary>

    /// <returns></returns>

    protected override PageState CreatePageState()
    {
        return new Page2PageState();
    }

    /// <summary>

    /// Provide typed access to the PageState of this page.

    /// </summary>

    private new Page2PageState PageState
    {
        get { return (Page2PageState)base.PageState; }
    }

    /// <summary>

    /// Save the state of the page. This is the second way to accomplish 

    /// that. See Page1 for the other implementation.

    /// </summary>

    protected override void PullState()
    {
        base.PullState();

        this.InterPageState.Year = this.ddlYear.SelectedValue;
        this.PageState.Text = this.txtText.Text;
    }
}

When the page is loaded, I restore the saved values from the last visit on this page. If this is the first visit, then the controls are set to the default values defined in the corresponding PageState.

This page overrides the CreatePageState method to instantiate an instance of the class Page2PageState that will hold the state information for this class (the text value). Additionally, the PullState method is overridden in order to save the values from the controls to the state: the year value is stored in the InterPageState so other pages that have a year selection, too, can initialize them with this value (the user has to specify a value once in the application and not on every page again and again). The text value is stored in the state of the page so it can be restored when the user returns to this page.

Conclusion

Okay, you may say to yourself that this article describes nothing that is new to you. Maybe because you are using a pattern similar to mine, or you do the same directly with the Session bag.

You may be right with that, but in a Web Application with some hundreds of thousand of lines of code and more than thirty (non-static) pages, I'm very happy to have a wrapped access to session data, especially the context based approach of the PageState classes.

Finally, please post your thoughts in the message area :-)

Source of the code

The sample code is extracted from a web application that I wrote for bbv Software Services AG.

bbv logo

Thanks for letting me write about it.

History

  • 2007-04-17 - Initial version.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect bbv Software Services AG
Switzerland Switzerland
Urs Enzler is working for bbv Software Services in Switzerland as a Software Architect.

Blogger at planetgeek.ch

Comments and Discussions

 
Questionwhu u need to use [Serializable] in your coding Pin
koolll13-May-12 17:15
koolll13-May-12 17:15 
GeneralExcellent Pin
Member 284702422-Jul-09 1:11
Member 284702422-Jul-09 1:11 
GeneralGood Article - My Scenario Pin
engineerachu5-Mar-09 22:26
engineerachu5-Mar-09 22:26 
GeneralRe: Good Article - My Scenario Pin
Urs Enzler13-Mar-09 22:08
Urs Enzler13-Mar-09 22:08 
GeneralPerformance Pin
nksaroj4-Jun-08 13:10
nksaroj4-Jun-08 13:10 
GeneralRe: Performance Pin
Urs Enzler20-Jun-08 4:49
Urs Enzler20-Jun-08 4:49 
GeneralVery good article Pin
ReginaJensen8-Apr-08 3:58
ReginaJensen8-Apr-08 3:58 
GeneralRe: Very good article Pin
Urs Enzler20-Jun-08 4:39
Urs Enzler20-Jun-08 4:39 
GeneralVery Interesting Pin
Normand3728-Apr-07 2:31
Normand3728-Apr-07 2:31 
GeneralA great article Pin
Henry Nguyen19-Apr-07 4:48
professionalHenry Nguyen19-Apr-07 4:48 
GeneralVery Nice Pin
JohnDeHope318-Apr-07 7:55
JohnDeHope318-Apr-07 7:55 
GeneralRe: Very Nice Pin
Urs Enzler18-Apr-07 19:54
Urs Enzler18-Apr-07 19:54 
QuestionWindows Workflow ? Pin
TonyS18-Apr-07 0:09
TonyS18-Apr-07 0:09 
AnswerRe: Windows Workflow ? Pin
Urs Enzler18-Apr-07 0:21
Urs Enzler18-Apr-07 0:21 
QuestionSession Bag? Pin
Dewey17-Apr-07 21:10
Dewey17-Apr-07 21:10 
AnswerRe: Session Bag? Pin
Urs Enzler17-Apr-07 21:24
Urs Enzler17-Apr-07 21:24 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.