CodeBetter.Com
CodeBetter.Com
RSS 2.0 via Feedburner
           Do you Twitter? Follow us @CodeBetter

Brendan Tompkins [MVP]

Blog First. Ask Questions Later.

A Better ASP.NET State Pattern

Creating a better way to manage ASP.NET state has been talked about quite a bit.  I’ve posted about it a few times, as have others.  A couple of weeks ago Raymond brought the topic up again, and it got me thinking that the way I've been doing it could probably be improved upon.  Today I needed to look at it again for our public web site, so I thought that it was a good time for a rewrite. 

If you have a look at my StateClassBase Redux  there’s a number of problems with the implementation. For one, it’s a poor use of OO.  Yes, there is a base class, but it just contains a bunch of overloaded methods.  In fact, having a bunch of overloaded methods is usually a good indication that your design isn’t all that great.  For another, it was cumbersome to use, and required that it be initialized in your code before you use it.  Initializing the class in your OnInit worked okay sometimes, but could also be buggy in other situations, like when your control was dynamically loaded.  The new version lazy loads itself, via a singleton-ish Instance property, which helps sort out these issues.

It also wasn’t easy to re-use. If I wanted to add another back-end session storage mechanism, it involved modifying the base class itself.  What was I thinking?

Recent Updates (2/24/05):

  • Added  [Serializable] to the class and [XmlIgnore] to the Instance
  • Made the SessionStateObject class Abstract

So, here’s my new and improved StateBase class:

  [Serializable]public abstract class StateBase

  {

    protected string stateKey;

    private object instance;

 

    /// <summary>

    /// Gets the instance.

    /// </summary>

    /// <value></value>

    [XmlIgnore]public virtual Object Instance

    {

      get

      {

        // Lazy Load

        if(instance == null)

        {

          // Try to restore

          Object o = Restore();

          if(o != null)
          {

             instance = o;
             return o;

          }

          // Create a new instance of the derived type

          Type type = this.GetType();

          instance = Activator.CreateInstance(type);

        }

 

        return instance;

      }

    }

 

    /// <summary>

    /// Gets or sets the state key.

    /// </summary>

    /// <value></value>

    public string StateKey

    {

      get { return stateKey; }

      set { stateKey = value; }

    }

 

    /// <summary>

    /// Creates a new <see cref="StateBase"/> instance.

    /// </summary>

    /// <param name="stateKey">State key.</param>

    public StateBase(string stateKey)

    {

      this.stateKey = stateKey;

    }

 

    /// <summary>

    /// Stores this instance.

    /// </summary>

    /// <returns></returns>

    public abstract void Store();

 

    /// <summary>

    /// Removes this instance.

    /// </summary>

    /// <returns></returns>

    public abstract void Remove();

 

    /// <summary>

    /// Restores this instance.

    /// </summary>

    /// <returns></returns>

    public abstract Object Restore();

 

  }

And here’s a derived class that sores the state in Session. Note that you can create one that stores state in ViewState, or Application or Cache or in the Database, or on your cell phone. The point is, you just derive from StateBase, and implement the abstract methods.

public abstract class SessionStateObject : StateBase

  {

    /// <summary>

    /// Creates a new <see cref="SessionStateObject"/> instance.

    /// </summary>

    /// <param name="stateKey">State key.</param>

    public SessionStateObject(string stateKey) : base(stateKey)

    {}

 

    /// <summary>

    /// Stores this instance.

    /// </summary>

    /// <returns></returns>

    public override void Store()

    {

      System.Web.HttpContext.Current.Session[stateKey] = this;

    }

 

    /// <summary>

    /// Removes this instance.

    /// </summary>

    /// <returns></returns>

    public override void Remove()

    {

      System.Web.HttpContext.Current.Session.Remove(stateKey);

    }

 

    /// <summary>

    /// Restores this instance.

    /// </summary>

    /// <returns></returns>

    public override Object Restore()

    {

        return System.Web.HttpContext.Current.Session[stateKey];

    }

  }

And here’s how to use it in your code, simply derive from the SessionStateObject, adding any members you want to keep track of your state.

    /// <summary>

    /// Object for maintaining state

    /// </summary>

    public class SomeControlsState : SessionStateObject

    {

      public SomeControlsState() : base("SOMECONTROL_STATE")

      {}

 

      public new SomeControlsState Instance

      {

        get

        {

          return (SomeControlsState)  base.Instance;

        } 

      }

 

      private ShippingContainerCollection containers;

 

      public ShippingContainerCollection Containers

      {

        get { return containers; }

        set { containers = value; }

      }

    }

Now, assuming that you’ve created an instance of state (you can now do this in-line in your class) to save your state, just call Store();

state.Instance.Containers = someService.Instance.GetShippingContainerCollection();

state.Instance.Store();

Using in on subsequent postbacks is even easier:

ShippingContainer sc = state.Instance.Containers.Find(Convert.ToInt32(e.CommandArgument));

No more worrying about calling Restore() unless you specifically need to.  Hope you find this useful.  I really would like some feedback to this.  If this can be done better, I want make this thing sing!

-Brendan



Comments

Raymond Lewallen said:

I like this model much better, but what do you do if you have a userId that you need to store in session as an Int32? Are you creating a class for that too? Are you creating a class for each and every object that you want to maintain state for? Each control on a page for viewstate? Its just seems so heavy, yet very useful the way its designed. What does your PreInit or Page_Load code look like for creating and dealing with all of these classes when you have a control-heavy page for say 20 controls? Does it get hard for you to maintain at that point?
# February 24, 2005 6:10 AM

Brendan Tompkins said:

Raymond,

You can use one State class for the entire application, or one per control (which is my preference).

If I needed to keep track of a userId, I'd probably create a stateclass, with the userId member, then use it. My reasoning? Because someday, I'm probably going to have to keep track of something else on that control, and now I have a place to put it.

There's no intialization code for this, since it's lazy loaded. All you have to do is declare a class-level variable of your state type, and then always access it through the "Instance" member.
# February 24, 2005 6:24 AM

Raymond Lewallen said:

You declare a class-level variable for each control you are going to maintain state for, I understand that, but they way I understand it is you don't have a singleton "instance" for managing state, you have an "instance" for managing state per object you want to manage. And if you did have a singleton instance for the entire state for the entire application, you run into what I deal with in my pattern, which is a sometimes heavy state instance with many members that I don't use all the time and doesn't scale easily enough. I certainly understand the reasoning for doing it this way you show here, and like the approach, but don't you worry about the maintenance of it all? Don't think I don't like what you have, because I do. I like it better than my approach. I just play a great devil's advocate. :) I have to update my presentation material to include your new state class pattern to present on March 7th. I'm excited to present this stuff and get some feedback for us both.
# February 24, 2005 7:43 AM

Brendan Tompkins said:

"but don't you worry about the maintenance of it all?"

Yes! I worry a lot about maintenance, which is exactly why I think it's better to encapsulate a control's state within (or close to) the control itself.

When each control is responsible for it's own state, then it gets de-coupled from the entire application, which then makes it more portable and maintainable, IMO.

Now, some state management makes sense at the macro level, so I put this stuff in a state class that lives in my PageBase class. For everything else, I try te encapsulate it as much as possible in the control.
# February 24, 2005 7:49 AM

TrackBack said:

# February 27, 2005 6:27 PM

TrackBack said:

# February 27, 2005 6:54 PM

TrackBack said:

# March 4, 2005 1:33 PM

Charles Fisher said:

hey hey...

Pretty good stuff Brendan! I hope there is an additional class (utility or functionality included in the .aspx) that cleans up the state behind you unless you're doing your own housekeeping.

Using the example Raymond and yourself discussed previously, there are 20 controls with this state management. When they move on to another webform or process in which the information is not necessary, the state will remain bloated until it's manually cleaned up or the session ends.

Hmm... maybe I need to revisit the original blog first before I put my foot in my mouth. :)

Really great stuff... just wish I was working on a project that gave me time to develop more.. elegant solutions to some of the problems we face.
# March 8, 2005 1:42 PM

Brendan Tompkins said:

Yep, you do have to watch out for anything you put into Session. I've coded implementation of a strongly-typed ViewState, and I have to wrap a blog post around it some day. This has advantages/disadvantages, but it does take care of the memory concerns.
# March 8, 2005 1:50 PM

TrackBack said:

# March 14, 2005 1:26 PM

TrackBack said:

# March 15, 2005 8:09 AM

TrackBack said:

# March 15, 2005 8:25 AM

Leave a Comment

(required)  
(optional)
(required)  

Enter the numbers above:
Add

About Brendan Tompkins

Brendan has been programming with .NET since the first public beta and is owner and operator of Port Technology Services, a consultancy company providing .NET application development services to the Maritime industry. In July, 2007, he was awarded the Microsoft MVP award for ASP.NET. He's also a proud co-founder of failed .COM startup Intrinsigo, and has had a hand in the failure of numerous other businesses. He currently runs CodeBetter.Com and Devlicio.us, and lives in Norfolk, Virgina with his wife Tiara and son Ian.

View Brendan's profile on LinkedIn

Check out Devlicio.us!

Our Sponsors

Free Tech Publications