Click to search the site Click to log in
Online articles
Download free tools
Support pages, per product
Services
Frequently asked questions, per product
Building a type safe ASP.Net session
Author: George Mihaescu
Published: November 10, 2006
Category: Best practices / Design pattern / ASP.Net
Notes:
Description: This article makes the case for having a type-safe implementation of a session object in your ASP.Net application and describes two very simple design patterns to achieve this (and replace the untyped collection offered by ASP.Net)
View count: 2,232
Comments: 3 Read comments or post your own

  Print viewOpens in new window
 Building a type safe session

Building a type-safe ASP.Net session

 

By George Mihaescu

 

Summary: this article makes the case for using a type safe session object instead of the standard ASP.Net Session and presents two solutions for this, one based on the Adapter design pattern, the other based on the Façade design pattern.

A similar case can be made for the ASP.Net cache – that is presented in another article.

The problem

ASP.Net offers the Session object for application that need to store user session data (i.e. data across requests made by the same web client). However, the provided Session has a number of shortcomings:

  • It is not type-safe: the type of an item stored in the session is Object; you always need to cast it to its actual type when you retrieve it. This cast is always based on the developer knowing the actual type of the object stored for that key. If there is a design change and the type of that item stored has changed, you MUST make sure that all casts when retrieving the item are updated to the correct type, otherwise you get runt-time cast exceptions.
  • It is open to key name collisions: developers can use by mistake the same key for two different purposes.
  • It is open to key name mistypes: developers can by mistake mistype the key and then the expected object will not be found in the session.

By the way, none of these shortcomings are flaws in the ASP.Net Session design: the Session is a generic "bag" of any data types you may need to store. ASP.Net designers cannot know what each application will need to store in the session – however, you do, because it's your application, and below you'll find what you can do to build a better Session for you particular application needs.

The solution

The goal is to find a design that would resolve the shortcomings listed above. I have found two such designs that only have minor differences:

  • The design based on the Adapter pattern is less convenient: you always have to pass in the ASP.Net Session (HttpSessionState) to get your type-safe SessionAdapter. But is safer to use because by having you pass the Session in, the call is guaranteed to be in the context of a web request.
  • The design based on the Façade pattern is more convenient: you don't have to pass anything to get your type-safe SessionFacade (it is just a static class implementation). But it assumes that it's being called in the context of a web request – if not, the current HttpContext will be null and you'll get run-time exceptions.

 

The Adapter pattern implementation

 

using System;

using System.Web;

using System.Web.SessionState;

 

/// <summary>

/// SessionAdapter implements an Adapter pattern over the ASP.Net

/// HttpSessionState in order to achieve type-safety and consistency.

/// </summary>

public class SessionAdapter

{

    // Keys used for values in the session

    private const string USER_LOGON_NAME = "USER_LOGON_NAME";

    private const string USER_LOGON_TIME = "USER_LOGON_TIME";

 

    private readonly HttpSessionState m_session;

 

    private SessionAdapter(HttpSessionState session)

    {

        m_session = session;

    }

 

    /// <summary>

    /// Factory, gets a session adapter

    /// </summary>

    public static SessionAdapter GetSessionAdapter(HttpSessionState session)

    {

        return new SessionAdapter(session);

    }

 

    /// <summary>

    /// Sets / gets the current user's logon name.

    /// May return null if value not in session.

    /// Pass in null to remove the object from session.

    /// </summary>

    public string UserLogonName

    {

        get { return m_session[USER_LOGON_NAME] as string; }

        set

        {

            if (value == null)

            {

                m_session.Remove(USER_LOGON_NAME);

            }

            else

            {

                m_session[USER_LOGON_NAME] = value;

            }

        }

    }

 

    /// <summary>

    /// Sets / gets the current user's logon date and time

    /// May return null if value not in session.

    /// Pass in null to remove the object from session.

    /// </summary>

    public Nullable<DateTime> UserLogonDateTime

    {

        get

        {

            //retrieve the object from the session

            object val = m_session[USER_LOGON_TIME];

            if (val == null)

            {

                return null;

            }

            else

            {

                return (DateTime)val;

            }

        }

        set

        {

            //if passed value is null, remove object from session

            if (value == null)

            {

                m_session.Remove(USER_LOGON_TIME);

            }

            else

            {

                m_session[USER_LOGON_TIME] = value;

            }

        }

    }

}

Example of use:

 

{

    //get the session adapter

    SessionAdapter s = SessionAdapter.GetSessionAdapter(Session);

 

    //get the values

    string user = s.UserLogonName;

    Nullable<DateTime> logon_timestamp = s.UserLogonDateTime;

 

    //set the values

    s.UserLogonName = "George";

    s.UserLogonDateTime = DateTime.Now;

}

 

 

The Façade pattern implementation

 

using System;

using System.Web;

using System.Web.SessionState;

 

/// <summary>

/// SessionFacade implements a Facade pattern over the ASP.Net

/// HttpSessionState in order to achieve type-safety and consistency.

/// </summary>

public static class SessionFacade

{

    //Key names used in the session

    private const string USER_LOGON_NAME = "USER_LOGON_NAME";

    private const string USER_LOGON_TIME = "USER_LOGON_TIME";

 

    /// <summary>

    /// Sets / gets the current user's logon name.

    /// May return null if value not in session.

    /// Pass in null to remove the object from session.

    /// </summary>

    public static string UserLogonName

    {

        get

        {

            //retrieve the session from the context

            HttpSessionState s = HttpContext.Current.Session;

 

            //retrieve the object from the session

            return s[USER_LOGON_NAME] as string;

        }

        set

        {

            //retrieve the session from the context

            HttpSessionState s = HttpContext.Current.Session;

 

            //if passed value is null, remove object from session

            if (value == null)

            {

                s.Remove(USER_LOGON_NAME);

            }

            else

            {

                s[USER_LOGON_NAME] = value;

            }

        }

    }

 

    /// <summary>

    /// Sets / gets the current user's logon date and time

    /// May return null if value not in session.

    /// Pass in null to remove the object from session.

    /// </summary>

    public static Nullable<DateTime> UserLogonDateTime

    {

        get

        {

            //retrieve the session from the context

            HttpSessionState s = HttpContext.Current.Session;

 

            //retrieve the object from the session

            object val = s[USER_LOGON_TIME];

            if (val == null)

            {

                return null;

            }

            else

            {

                return (DateTime)val;

            }

        }

        set

        {

            //retrieve the session from the context

            HttpSessionState s = HttpContext.Current.Session;

 

            //if passed value is null, remove object from session

            if (value == null)

            {

                s.Remove(USER_LOGON_TIME);

            }

            else

            {

                s[USER_LOGON_TIME] = value;

            }

        }

    }

}

 

Example of use:

 

{

    //get the values

    string user = SessionFacade.UserLogonName;

    Nullable<DateTime> logon_timestamp = SessionFacade.UserLogonDateTime;

 

    //set the values

    SessionFacade.UserLogonName = "George";

    SessionFacade.UserLogonDateTime = DateTime.Now;

}

 

 

Conclusion:

The above implementations are equally good; the chances that you'll be mistakenly using the SessionFacade outside the context of a web request (e.g. worker thread, unit test, or certain functions in Global.asax) are pretty slim, and if you do, you'll find out during development very quickly – therefore I would be inclined to use the implementation based on the Façade pattern (for its convenience).

 

Note that in addition to meeting the goals I've outlined at the beginning of the article, those implementations also offer additional benefits:

  • The usual benefits of having setters (e.g. validate what's getting into the session; log the value if in DEBUG mode, etc).
  • The usual benefits of having getters (e.g. initialize value to a default if not present in the session, etc).
  • They hide the internal implementation of the session workings, therefore reducing the chances of errors in the application and decoupling the application code from changes in the session's internal workings.
  • Allow for retrieval of value-types (such as the DateTime in the examples above) or null if the value is not present through the use of the generic Nullable<T> (available in .Net 2.0 only).

 

 

One minor objection to the above implementations can be that they don't prevent a sloppy / unaware developer in the team from circumventing them and going directly to the ASP.Net Session, bringing back in the issues listed at the top of the article.

If you want to prevent this, one possible solution could be to add code in the above implementations that checks the state of the underlying ASP.Net Session every time an item is set / retrieved in the Session (i.e. at the top of each get / set property implementation).

For instance, traverse all the keys and if you find one that's not a known key, throw an exception – that way you'll know immediately if someone has bypassed your type-safe implementation. But probably that's too heavy-handed for most cases.

 


Reader comments:
Name: (optional)
Verification text:    
(type as in image next to it)
Comment: max 2,000 characters; for security reasons no active content / no HTML formatting is supported.
Please stick to the subject of the article; comments are reviewed and unrelated / inappropriate ones will be deleted.

On Dec 16, 2006 at 17:35 EST George said:

Mark, indeed it is an extra effort that you need to go through; I personally believe that it's worth it because it reduces the chances for errors and in fact reduces the maintenance effort quite substantially. As I've stated in the article, without this it is quite likely that changing the type of a value stored in the session at some point in the application lifetime will break things in many places, and tracking those down is not pleasant. With a type-safe implementation, the compiler does the tracking down automatically for you.

On Dec 15, 2006 at 20:32 EST Mark T. said:

I find this overkill; every time you need to put something in the session you need to add another property / method in type-safe session class. This becomes cumbersome and requires a lot of maintenance.

On Dec 10, 2006 at 18:30 EST Scott said:

Very good explanation; there's also an article on http://www.codeproject.com but it's less detailed and does not offer the adapter pattern (it only explains the facade one). Anyway, thank you for another useful article.
Copyright 2308 registered users, 24 users online now