Generics and the Session State

A minor buzzfest about type safe HttpSessionState handling going on now in the upcoming story queue at DNK.  There are a couple of decent patterns being shared, so I figured I'd add in my own favorite type-safe session state handler.

The pattern uses generics (I loev generics) and works very well for data objects specifically designed to be stored in the session. 

The pattern goes like this:  A static SessionHelper class exposes a static, generic method for retrieving objects from the session state.  The method has one type parameter for the type of object that the caller wants.  The caller must supply a key under which the object is stored.  Simple, no?

Yes.  Too simple.  But here is where the magic happens.  Callers must also pass in a delegate that creates the default instance of the object.  Essentially, this delegate allows the helper to "set" the value if not found.  And the delegate uses generics, so the set logic is type safe as well.

Here's the implementation of the SessionHelper method

public class SessionHelper
{
    /// <summary>
    /// Retrieves an object from the session state or, if not found, creates it
    /// </summary>
    /// <remarks>
    /// A valid default value for a reference type is null.  It is up to the caller
    /// to determine the default value for value types.
    /// </remarks>
    /// <example>
    /// // Used to keep track of links a user clicks on
    /// public class UriHistory
    /// {
    ///    // Constructor is protected so that it can't be
    ///    // accessed by outside callers
    ///    protected UriHistory() { }
    ///    // lots of implementation here
    ///    // left out for brevity
    ///    // Retrieves Uri history for the current user
    ///    public static UriHistory CurrentUriHistory
    ///    {
    ///        get
    ///        {
    ///            SessionHelper session = new SessionHelper();
    ///            return session.Get<UriHistory>("UriHistory",
    ///                delegate
    ///                {
    ///                    return new UriHistory();
    ///                });
    ///        }
    ///    }
    /// }
    /// </example>
    /// <typeparam name="T">The type of object to retrieve</typeparam>
    /// <param name="key">A string key for the item</param>
    /// <param name="createDefault">A <seealso cref="CreateDefault"/> delegate used to
    /// create the default value for an item if it does not exist in the session</param>
    /// <returns>The object if found, the </returns>
    public T Get<T>(string key, CreateDefault<T> createDefault)
    {
        T retval;
        // if all is well, grab from the session
        // otherwise use the createDefault delegate
        if (SessionHelper.Session != null
            && SessionHelper.Session[key] != null
            && SessionHelper.Session[key].GetType() == typeof(T))
        {
            retval = (T)SessionHelper.Session[key];
        }
        else
        {
            retval = createDefault();
            SessionHelper.Session[key] = retval;
        }
 
        return retval;
    }
 
    /// <summary>
    /// The current session
    /// </summary>
    private static HttpSessionState Session
    {
        get
        {
            // HttpContext can be null when the website is shutting down
            if (HttpContext.Current != null)
                return HttpContext.Current.Session;
            return null;
        }
    }
}

And here is the delegate:


/// <summary>
/// Used by <seealso cref="SessionHelper.Get{T}"/> in order to create
/// the default value for a session item
/// </summary>
/// <remarks>This method may return null for reference types.</remarks>
/// <typeparam name="T">The type of item to be created</typeparam>
/// <returns>A default value</returns>
public delegate T CreateDefault<T>();

Notice the example in the XML comments.  It shows how the second part of this pattern can be used to create a pseudo-singleton pattern using the session helper, rather than the CLR, to make sure only one instance for a particular object is available within the current context.  With a singleton, a singleton object only has one instance per AppDomain.  With this pattern, only a single instance of each object is allowed per Session. 

Here's a nicer formatted version of the example class for easier viewing:

// Used to keep track of links a user clicks on
public class UriHistory
{
    // Constructor is protected so that it can't be
    // accessed by outside callers
    protected UriHistory() { }
    // lots of implementation here
    // left out for brevity
    // Retrieves Uri history for the current user
    public static UriHistory CurrentUriHistory
    {
        get
        {
            SessionHelper session = new SessionHelper();
            return session.Get<UriHistory>("UriHistory",
                delegate
                {
                    return new UriHistory();
                });
        }
    }
}

The nicest thing about this pattern is that it prevents session state management concerns from becoming a dependency nightmare.  The logic for managing session state is kept within the SessionHelper.  The logic for creating default implementations of your objects rests within the objects themselves and not some third object that stands between the session and your business objects.  Plus, its type safe and uses generics, which I love.

The only sore point I have with this code is the manner in which the current HttpSessionState is retrieved.  I know that there is a better way to do this that works even when the website is shutting down and that facilitates unit testing, but I can't remember the pattern nor can I find it anywhere.  If you happen to know, please put it in the comments.  Thanks!

3 Responses to Generics and the Session State

  1. Troy Goode says:

    Nice work! If you are using .Net 3.5 you could use yummy lamdas to simply the get request to:

    [b]session.Get<UriHistory>( "UriHistory", () => new UriHistory() );[/b]

  2. Manu Temmerman-Uyttenbroeck says:

    I added this method, because in a lot of cases I retrieve strings from session and return null as default.
    /// <summary>
    /// Gets the specified key from session and returns null as default if it didn’t find it.
    /// </summary>
    private static string Get(string key)
    {
    return Get<string>(key, delegate { return null; });
    }

    I’m to lazy to write the delegate {return null; } all the time ;)

  3. Will says:

    Manu,

    That works fine, but you could improve it a touch this way:

    public static T Get<T>(string key)
    {
    return Get<T>(key, delegate{ return default(T); } );
    }

    This will give you a null for reference types, and the default value for value types.

Comments are closed.