Chris Stewart's ASP.NET Blog

My experiences with ASP.NET

Strongly-Typed Session in ASP.NET

Session state is a very useful tool for an ASP.NET developer.  I use the Session object in every project.  I use it to store information related to the current user that I need quick access to throughout their visit.  It's important not to overuse the Session object but rather to store quick bits of information that need to persist over a single, or multiple, post-backs.

The most painful part of using the Session object built-in to the ASP.NET framework is the error prone nature of getting and setting variables inside of the Session object.  The common usage of getting something out of Session looks like  this:

Product product = Session["MyProduct"] as Product;

Sure, it's simple enough and makes sense.  However, there is the potential to forget the name of the object you'd like to retrieve.  Wouldn't it be nice if the Session was a strongly-typed object that wouldn't allow for this mistake?

In various projects I've been using what I call the SessionWrapper.  I can't take credit for the name or the idea, as it was something a past co-worker of mine opened my eyes to.  However, his implementation was different than mine in that you simply stored the SessionWrapper object itself in Session and accessed it in your pages.  The usage looked like this:

SessionWrapper sessionWrapper = Session["SessionWrapper"] as SessionWrapper;
sessionWrapper.Product = new Product();

I wanted to get away from touching the Session object in my presentation code.  Why couldn't I simply say:

SessionWrapper.Product = new Product();

With this goal, my implementation of the SessionWrapper was created.  The key is using the HttpContext to interact with the Session object while not being required to inherit the Page class.  Doing this allows us to access the Session object in a static manner, creating the usage scenario we desire.  I've included a full Visual Studio 2008 solution at the bottom of this post.  Make sure you check that out.  Here's the code for the SessionWrapper class:

using System;
using System.Web;

namespace SessionWrapperExample
{
    public static class SessionWrapper
    {
        public static Product Product
        {
            get
            {
                if (null != HttpContext.Current.Session["SessionWrapperProduct"])
                    return HttpContext.Current.Session["SessionWrapperProduct"] as Product;
                else
                    return null;
            }
            set
            {
                HttpContext.Current.Session["SessionWrapperProduct"] = value;
            }
        }
    }
}

Download the Solution: SessionWrapperExample.zip

Comments

Strongly-Typed Session in ASP.NET - Chris Stewart's ASP.NET Blog said:

Pingback from  Strongly-Typed Session in ASP.NET - Chris Stewart's ASP.NET Blog

# January 9, 2008 11:33 AM

Chris said:

I have question what was your thinking when you wrote

"null != HttpContext.Current.Session["SessionWrapperProduct"]"?

Specifically why did you put null in front of "HttContext..." or was there not a reason behind it? This is a question of location not that you are checking for a null object.

I was thinking that you did it so that both parts of the if stayed on the screen whereas if you did HttpContext first the null may have gone off the screen and would need to scroll.

-Chris

# January 9, 2008 12:29 PM

CompiledMonkey said:

Chris,

No particular reason.  You can do it both ways and achieve the same result.  Good eye in catching that and asking me to clarify.  Thanks Chris.

# January 9, 2008 1:30 PM

Tyrone said:

My implementation is quite similar:

using System;

using System.Web;

[Serializable]

public sealed class SessionManager {

   private const string SESSION_MANAGER = "SESSION_MANAGER";

   private Product _product = null;

   private SessionManager( ) {

   }

   public static SessionManager Current {

       get {

           HttpContext context = HttpContext.Current;

           SessionManager manager =

               context.Session[ SESSION_MANAGER ] as SessionManager;

           if ( manager == null ) {

               manager = new SessionManager( );

               context.Session[ SESSION_MANAGER ] = manager;

           }

           return manager;

       }

   }

   public Product ActiveProduct {

       get {

           return this._product;

       }

       set {

           this._product = value;

       }

   }

}

And you would add a product to the session by doing this:

SessionManager.Current.ActiveProduct = new Product();

By doing this, I think you encapsulate the SessionWrapper/SessionManager class in a much cleaner fashion. It's also a Singleton object which is great.

# January 9, 2008 1:49 PM

Chris Stewart said:

Great class.  I really like how the HttpContext is encapsulated to a single properly.  So extending this class is as simple as creating a new property to represent one of your objects.  I trimmed your code slightly with respect to the ActiveProduct property.  Thanks for the comment Tyrone!

[Serializable]

public sealed class SessionManager

{

   private const string SESSION_MANAGER = "SESSION_MANAGER";

   private SessionManager() { }

   public static SessionManager Current

   {

       get

       {

           HttpContext context = HttpContext.Current;

           SessionManager manager = context.Session[SESSION_MANAGER] as SessionManager;

           if (manager == null)

           {

               manager = new SessionManager();

               context.Session[SESSION_MANAGER] = manager;

           }

           return manager;

       }

   }

   public Product Product

   {

       get;

       set;

   }

}

# January 9, 2008 2:04 PM

Max said:

What timing on reading this, I was thinking about this exact same thing today but for cookies - thanks for the insight.

# January 9, 2008 4:18 PM

Tyrone said:

Happy to help Chris.  Nice use of automatic properties. I have to remember to use them. :-)

# January 9, 2008 10:51 PM

Damien Guard said:

I have a much simpler solution that avoids the need to write code for every property and box/unbox value types:

damieng.com/.../typed-session-data-made-easier-still

[)amien

# January 10, 2008 7:35 AM

CompiledMonkey said:

I'm not seeing how it's much different than Tyrone's sample, except for the fact you're writing declarations instead of properties.  Based on some of the comments made in your blog entry, I can see why my initial implementation might be useful in certain situations.  As Philippe brought up, all of the variables in your class are stored in session regardless of whether they're used or not.  If you have a very large class, it may make sense to use my implementation seeing as only the necessary fields are stored in the session.  We're certainly splitting hairs at this point as all of the samples provided thus far are of use in certain situations.  Thanks for posting Damien.

# January 10, 2008 8:41 AM

Will Sullivan said:

I've got a much different pattern I just posted on my blog.

A static session helper class exposes a generic method for retrieving strongly typed data from the session.  Callers must also pass in a delegate to create the default value if not found in the session.  This default value is saved in the session and returned to callers.

What makes this pattern powerful is that objects can be designed to be stored in the session by marking their constructors as protected/private and providing a static method, "GetCurrent", that uses the session helper to retrieve/create the object.

# January 10, 2008 9:45 AM

CompiledMonkey said:

Will, can you post a link to your blog entry?  I'd love to check out what you're doing in more detail.  I visited State Street Gang but could not find the entry you're talking about.

# January 10, 2008 10:02 AM

Alexandre Marlot said:

Hi,

That is exactly what i use in all my project :)

It is very esay and very powerful.

# January 10, 2008 11:59 AM

Brian Lowry said:

You could probably integrate my generics way of doing things to clean up your code a bit more... that could be a ton of static methods depending on what you are doing which is not easily testable:

public static class SessionRepository: ISessionRepository

   {

       public static T Get<T>(string key)

       {

           HttpContext current = HttpContext.Current;

           if (current == null)

           {

               return default(T);

           }

           if (current.Session.Mode == SessionStateMode.Off)

           {

               throw new Exception("Session elements cannot be added when session is disabled.");

           }

           object value = current.Session[key];

           if (value != null)

           {

               return (T)value;

           }

           return default(T);

       }

       public static bool Add<T>(string key, T value)

       {

           HttpContext current = HttpContext.Current;

           if (current == null)

           {

               return false;

           }

           if (current.Session.Mode == SessionStateMode.Off)

           {

               throw new Exception("Session elements cannot be added when session is disabled.");

           }

           current.Session.Add(key, value);

           return true;

       }

       public static bool Remove(string key)

       {

           HttpContext current = HttpContext.Current;

           if (current == null)

           {

               return false;

           }

           if (current.Session.Mode == SessionStateMode.Off)

           {

               throw new Exception("Session elements cannot be added when session is disabled.");

           }

           current.Session.Remove(key);

           return true;

       }

   }

# January 10, 2008 2:49 PM

CompiledMonkey said:

I'm not sure how that method is much different than simply using session directly.  The major benefit I'm after in this implementation is having a strongly-typed way of interacting with my objects in session.  Your sample continues to use a string based key.  Can you help me better understand the strength of your sample?

# January 10, 2008 4:30 PM

Will Asrari said:

Very cool! Thanks.

Also, syntactical sugar for those that care:

public static Product Product

{

           get{ return HttpContext.Current.Session["SessionWrapperProduct"] ?? null; }

           set { HttpContext.Current.Session["SessionWrapperProduct"] = value; }

}

# January 10, 2008 9:05 PM

CompiledMonkey said:

Nice addition, thanks!

# January 10, 2008 10:04 PM

Tyrone said:

I think using generics makes things look alot more complicated and I'm not sure if it need to be that way.

# January 11, 2008 12:47 AM

Jamie Pinkham said:

Brian Lowry said...

if (value != null)

{

  return (T)value;

}

return default(T);

you'll probably have a lot of boxing and unboxing going on with this implementation...

i like to check the type of T and branch my execution path based on typof(T).IsValueType...

# January 11, 2008 9:18 AM

Wayne said:

I'm not sold on this, it's confusing to the end developer.

What happens when I want to do this?:

SessionWrapper.Product.ProductName = "Some new name";

Response.Write(SessionWrapper.Product.ProductName);

Nothing stopping me from trying. Of course it wont work because the first part(SessionWrapper.Product) gave you the product object - the second part (.ProductName = "Some new name";) wrote to your Product objects productName your new value. But nothing was done after that, it was never persisted back to session. And there's nothing telling you that's the case.

And I can't think of a work around. :(

It would be great if it weren't for that.

I'm thinking some kind of IoC to say that whenever one of these properties states change - persist back. But that might be a bit much...

w://

# January 11, 2008 12:23 PM

CompiledMonkey said:

Wayne,

In my example, the "SessionWrapper.Product.ProductName" code would not compile since there is no ProductName property in the Product object.  What you're describing is exactly why you'd want to go with a strongly-typed session object.  It prevents the end developer from making simple mistakes as you've described.

# January 11, 2008 12:52 PM

Brian Lowry said:

"I'm not sure how that method is much different than simply using session directly.  The major benefit I'm after in this implementation is having a strongly-typed way of interacting with my objects in session.  Your sample continues to use a string based key.  Can you help me better understand the strength of your sample?"

The strength of my sample is that it while I still have to use string-based keys, it returns a strongly typed value.

int blogId = SessionRepository.Get<int>(BlogIdKey);

Additionally, i don't like using strings with teeth either, so I could define constants for each of the possible keys.

I threw this code sample out there and its not truly complete, but in my apps, I wouldn't have static methods, but rather use IoC to instantiate the SessionRepository. This will allow for easy interface-based programming and the ability to test and mock my ISessionRepository class.

# January 11, 2008 1:16 PM

Brian Lowry said:

"you'll probably have a lot of boxing and unboxing going on with this implementation...

i like to check the type of T and branch my execution path based on typof(T).IsValueType..."

I'm not a big fan of huge if-then or switch statements...

Isn't this casting doing the same boxing/unboxing?

return HttpContext.Current.Session["SessionWrapperProduct"] as Product;

# January 11, 2008 1:20 PM

CompiledMonkey said:

Gotcha.  IoC really is all the rage these days I guess.  I've yet to find it useful outside of testing purposes, although I can't say I've done enough research to understand its usefulness.  That said, given the new testability of the ASP.NET MVC framework, I can see more reasons to follow TDD and IoC.  Being as I'm still in Web Forms land, I'm just not completely on board yet.

# January 11, 2008 1:23 PM

Brian Lowry said:

Yeah, to be honest... I picked up MVC when it came out, and I'm just starting to fully understand the IoC structure and how useful it is. It is like the Provider model(configurable, etc.), except better for testing and overall code structure.

Once you begin playing with it, you won't look back. :)

# January 11, 2008 1:30 PM

Brian Lowry said:

If you used IoC though, then you wouldn't need my example. You could just create an ISettings interface and use that instead of SessionWrapper. Yours is better.  :)

# January 11, 2008 1:40 PM

Wayne said:

"In my example, the "SessionWrapper.Product.ProductName" code would not compile since there is no ProductName property in the Product object.  What you're describing is exactly why you'd want to go with a strongly-typed session object."

Sorry - that was just an example - it doesn't detract from my issue - I'm not knocking the idea, it's _really_ good. I just think it would be even better if we could solve this issue

# January 11, 2008 1:55 PM

CompiledMonkey said:

I fell in love with MVC when I was working with Ruby on Rails.  I'm very excited about this new model for ASP.NET.  I'm creating the specs for a new project now and wish the framework was further along in it's development.  For now, I'll have to stick with Web Forms for "real" development.

# January 11, 2008 1:58 PM

CompiledMonkey said:

Wayne,

I'm not sure I understand the issue you're describing.  Are you thinking the new property value doesn't get stored in session if it's set by: SessionWrapper.Product.Name = "something_new"?

# January 11, 2008 2:11 PM

Will Asrari said:

Sorry, forgot to add 'as Product'. It should look like:

public static Product Product

{

   get{ return HttpContext.Current.Session["SessionWrapperProduct"] as Product ?? null; }

   set { HttpContext.Current.Session["SessionWrapperProduct"] = value; }

}

# January 11, 2008 2:20 PM

Wayne said:

Hey

I'm saying that by implication one would expect to be able to access the properties of the object by chaining the statement and that those properties would be persisted. As you would in any other circumstance. It would not be unreasonable to expect to be able to do something like:

<psudo code>

[SessionManager].[SessionKeyName].[PropertyName] = [Value];

//And somewhere later in the code use that in another statement.

[LocalPropertyName] = [SessionManager].[SessionKeyName].[PropertyName];

//As it stands you have to do this:

[Object] [VarName] = [SessionManager].[SessionKeyName];

[VarName].[PropertyName] = [Value];

//And then return it back to the persistance store:

[SessionManager].[SessionKeyName] = [VarName];

</psudo code>

It's OK as it stands, just not ideal. It allows for things that don't work, and doesn't have any way of notifying any different.

w://

# January 12, 2008 11:05 AM

CompiledMonkey said:

Wayne,

I see what you're saying now.  And at first glance, that seems to make sense as a potential problem.  However, it actually does work.  You can say:

SessionWrapper.Product.Name = "Jim Jones!";

And that value will be persisted to the Session and can be retrieved anywhere else in the application.  Download the same code I provided and add that line to Default.aspx.cs after line 23.  You'll see instead of "Product A" being printed on the Result.aspx page, you get "Jim Jones!".

# January 12, 2008 11:17 PM

Wayne said:

Hey

I'll take a look tomorrow, I'm thinking it wasn't working when I tried but I had altered the code significantly to use generics so there's a chance that tweakage borffed it.

w://

# January 13, 2008 1:00 PM

Jamie Pinkham said:

"Isn't this casting doing the same boxing/unboxing?

return HttpContext.Current.Session["SessionWrapperProduct"] as Product;"

No, because you're not converting from a value type to a reference type, or vice versa.

"I'm not a big fan of huge if-then or switch statements..."

I don't think one if condition is huge.  You do however, run into some issues with struct types like GUID, DateTime.

# January 14, 2008 10:03 AM

Wayne said:

Nah - doesn't work:

protected void Page_Load(object sender, EventArgs e)

       {

           //Product p = new Product();

           //p.Name = "Product A";

           SessionWrapper.Product.Name = "Product A";

       }

       protected void Button1_Click(object sender, EventArgs e)

       {

           Response.Redirect("~/Result.aspx");

       }

Doesn't persist.

# January 16, 2008 9:21 AM

Wayne said:

It's the same as doing this:

System.Collections.Generic.List<string> list = new System.Collections.Generic.List<string>();

       list.Add("");//[0]

       list.Add("");//[1]

       list.Add("");//[2]

       list.Add("");//[3]

       list[2] = "helo";

       Response.Write(list[2]);

Which does work.

i would looove to figure this one out, it's such a good idea...

w://

# January 16, 2008 9:27 AM

CompiledMonkey said:

That throws an exception because the Product property is null, which is expected.  It's not a silent error or something you can easily shoot yourself in the foot over.  It's like saying "p.Name = "Product A";" and expecting it to work without ever declaring p as an instance of a Product object.  I can kind of see your point but it's definitely not enough to be considered an issue from my point of view.

# January 16, 2008 9:31 AM

Will Sullivan said:

Permalink to generic/session-singleton pattern:

statestreetgang.net/post.aspx

# January 18, 2008 11:01 AM

lwh said:

Any issues with thread safety with this pattern?

# February 4, 2008 2:10 PM
Leave a Comment

(required) 

(required) 

(optional)

(required)