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

35 Comments

  • 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

  • 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.

  • 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.

  • 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;
    }
    }

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

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

  • 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.

  • 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.

  • 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.

  • Hi,
    That is exactly what i use in all my project :)
    It is very esay and very powerful.

  • 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(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(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;
    }
    }

  • 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?

  • 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; }
    }

  • Nice addition, thanks!

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

  • 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...

  • 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://

  • 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.

  • "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(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.

  • "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;



  • 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.

  • 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. :)

  • 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. :)

  • "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

  • 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.

  • 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"?

  • 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; }
    }

  • 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!".

  • 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://

  • "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.

  • 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.

  • It's the same as doing this:

    System.Collections.Generic.List list = new System.Collections.Generic.List();
    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://

  • 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.

  • Any issues with thread safety with this pattern?

  • Hi Chris,
    Great blog, you saved a lot of my time. I converted my entire module into session property in no time.
    Thanks a million!

Comments have been disabled for this content.