How to make ASP.net session state testable

In the initial version, the wrapper simply throws an exception if HttpContext.Current or HttpContext.Current.Session is not available.

So in our Test-Methods we have to “mock” out the SessionState somehow. Basically we just need to override the Session State with a static dictionary within the computing Thread.

I would like to write my tests like this…

[Test]
public void TestOverride()
{
  using(SessionVariableStorage.Override(new Dictionary<string, object>()))
  {
    // normally a call to code that uses session variables internally
    var sessionString = new SessionVariable("sessionString"); 
    Assert.IsFalse(sessionString.HasValue);
    sessionString.Value = "Hello, world!";
    Assert.IsTrue(sessionString.HasValue);
    Assert.AreEqual("Hello, world!", sessionString.Value);
  }
}

To enable that, I need an extra static class “SessionVariableStorage” and some modifications in the SessionVariable<T>.

public static class SessionVariableStorage
{
  internal static readonly ThreadVariable<IDictionary<string, object>> Storage =
    new ThreadVariable<IDictionary<string, object>>();

  public static IDisposable Override(IDictionary<string, object> storage)
  {
    return Storage.Use(storage);
  }
}

Every time before I access the HttpSession I have to check wether it’s currently overridden or not. Here is the new version (requiring ThreadVariable<T>)

/// <summary>
/// Wrapper class for <see cref="HttpContext.Session"/>.
/// Author: Lars Corneliussen
/// Source: https://startbigthinksmall.wordpress.com/2008/05/14/how-to-wrap-the-aspnet-session-state/
/// </summary>
/// <typeparam key="T">
/// The type of the value to be stored.
/// </typeparam>
public class SessionVariable<T>
{
  private readonly string key;
  private readonly Func<T> initializer;

  /// <summary>
  /// Initializes a new session variable.
  /// </summary>
  /// <param name="key">
  /// The key to use for storing the value in the session.
  /// </param>
  public SessionVariable(string key)
  {
    if (string.IsNullOrEmpty(key))
      throw new ArgumentNullException("key");

    this.key = GetType() + key;
  }

  /// <summary>
  /// Initializes a new session variable with a initializer.
  /// </summary>
  /// <param name="key">
  /// The key to use for storing the value in the session.
  /// </param>
  /// <param name="initializer">
  /// A function that is called in order to create a 
  /// default value per session.
  /// </param>
  public SessionVariable(string key, Func<T> initializer) : this(key)
  {
    if (initializer == null) 
      throw new ArgumentNullException("initializer");

    this.initializer = initializer;
  }


  private object GetInternalValue(bool initializeIfNessesary)
  {
    if (SessionVariableStorage.Storage.HasCurrent)
    {
      var dictionary = SessionVariableStorage.Storage.Current;

      object value;

      if (!dictionary.TryGetValue(key, out value) && initializeIfNessesary
          && initializer != null)
        dictionary.Add(key, value = initializer());

      return value;
    }
    else
    {
      HttpSessionState session = CurrentSession;

      var value = session[key];

      if (value == null && initializeIfNessesary
          && initializer != null)
        session[key] = value = initializer();

      return value;
    }
  }

  private static HttpSessionState CurrentSession
  {
    get
    {
      var current = HttpContext.Current;

      if (current == null)
        throw new InvalidOperationException(
          "No HttpContext is not available.");

      var session = current.Session;
      if (session == null)
        throw new InvalidOperationException(
          "No Session available on current HttpContext.");
      return session;
    }
  }

  /// <summary>
  /// Indicates wether there is a value present or not.
  /// </summary>
  public bool HasValue
  {
    get { return GetInternalValue(false) != null; }
  }

  /// <summary>
  /// Sets or gets the value in the current session.
  /// </summary>
  /// <exception cref="InvalidOperationException">
  /// If you try to get a value while none is set. 
  /// Use <see cref="ValueOrDefault"/> for safe access.
  /// </exception>
  public T Value
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        throw new InvalidOperationException(
          "The session does not contain any value for '"
          + key + "'.");

      return (T) v;
    }
    set
    {
      if (SessionVariableStorage.Storage.HasCurrent)
        SessionVariableStorage.Storage.Current.Add(key, value);
      else
        CurrentSession[key] = value;
    }
  }

  /// <summary>
  /// Gets the value in the current session or if 
  /// none is available <c>default(T)</c>.
  /// </summary>
  public T ValueOrDefault
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        return default(T);

      return (T)v;
    }
  }

  /// <summary>
  /// Clears the value in the current session.
  /// </summary>
  public void Clear()
  {
    if (SessionVariableStorage.Storage.HasCurrent)
      SessionVariableStorage.Storage.Current.Remove(key);
    else
      CurrentSession.Remove(key);
  }
}

How to wrap the ASP.net session state

Update:

After reading Why Should You Wrap Your ASP.NET Session Object by Shahar A I thought the Community is ready for another step of encapsulation – and this is what I want to share.

We all know about the problems storing data in untyped dictionaries using some key, so I spare you.

This is how I want to store and retrieve session variables:

public class MySessionVariables
{
  // just an example, pls use Asp.Net Membership instead…
  public static readonly SessionVariable<string&#91;&#93;> UserGroups
    = new SessionVariable<string&#91;&#93;>(“UserGroups”);
}

// Fill the UserGroups after Login, for example

public void DeleteDatabase()
{
  if (MySessionVariables.UserGroups.HasValue &&
      MySessionVariables.UserGroups.Value.Contains(“Admin”))
    MyApplication.DeleteDatabase();
}

I would really like to not have a key to store the value, but I couldn’t avoid. I thought about creating a new Guid per class instance. But this won’t work when you run multiple Applications using some network session state. If someone has an idea on this, please share!

Initializer

Sometimes I need a dictionary or a List<T> to cache some values. So in order to avoid checking wether my SessionVariable has any value before accessing it I would like it to be initialized automatically on first use.

public class MySessionVariables
{
  public static readonly SessionVariable<List<string>> MarkedArticles
    = new SessionVariable<List<string>>(“MarkedArticles”, () => new List<string>());
}

If you’re not using C# 3 yet, you could easily add a delegate instead of using Func<T>.

   1: public delegate T CreateInitialValueHandler();
 
Additionally I added a property called ValueOrDefault that gives back default(T) if no value is assigned yet, while Value throws an exception if neither a value nor an initializer is available.

Free Source Code

/// <summary>
/// Wrapper class for <see cref=”HttpContext.Session”/>.
/// Author: Lars Corneliussen
/// Source: https://startbigthinksmall.wordpress.com/2008/05/14/how-to-wrap-the-aspnet-session-state/
/// </summary>
/// <typeparam key=”T”>
/// The type of the value to be stored.
/// </typeparam>
public class SessionVariable<T>
{
  private readonly string key;
  private readonly Func<T> initializer;

  /// <summary>
  /// Initializes a new session variable.
  /// </summary>
  /// <param name=”key”>
  /// The key to use for storing the value in the session.
  /// </param>
  public SessionVariable(string key)
  {
    if (string.IsNullOrEmpty(key))
      throw new ArgumentNullException("key");

    this.key = GetType() + key;
  }

  /// <summary>
  /// Initializes a new session variable with a initializer.
  /// </summary>
  /// <param name=”key”>
  /// The key to use for storing the value in the session.
  /// </param>
  /// <param name=”initializer”>
  /// A function that is called in order to create a
  /// default value per session.
  /// </param>
  public SessionVariable(string key, Func<T> initializer) : this(key)
  {
    if (initializer == null)
      throw new ArgumentNullException("initializer");

    this.initializer = initializer;
  }


  private object GetInternalValue(bool initializeIfNessesary)
  {
    HttpSessionState session = CurrentSession;

    var value = session[key];

    if (value == null && initializeIfNessesary
      && initializer != null)
      session[key] = value = initializer();

    return value;
  }

  private static HttpSessionState CurrentSession
  {
    get
    {
      var current = HttpContext.Current;

      if (current == null)
        throw new InvalidOperationException(
          "No HttpContext is not available.");

      var session = current.Session;
      if (session == null)
        throw new InvalidOperationException(
          "No Session available on current HttpContext.");
      return session;
    }
  }

  /// <summary>
  /// Indicates wether there is a value present or not.
  /// </summary>
  public bool HasValue
  {
    get { return GetInternalValue(false) != null; }
  }

  /// <summary>
  /// Sets or gets the value in the current session.
  /// </summary>
  /// <exception cref=”InvalidOperationException”>
  /// If you try to get a value while none is set.
  /// Use <see cref=”ValueOrDefault”/> for safe access.
  /// </exception>
  public T Value
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        throw new InvalidOperationException(
          "The session does not contain any value for ‘"
          + key + "‘.");

      return (T) v;
    }
    set { CurrentSession[key] = value; }
  }

  /// <summary>
  /// Gets the value in the current session or if
  /// none is available <c>default(T)</c>.
  /// </summary>
  public T ValueOrDefault
  {
    get
    {
      object v = GetInternalValue(true);

      if (v == null)
        return default(T);

      return (T)v;
    }
  }

  /// <summary>
  /// Clears the value in the current session.
  /// </summary>
  public void Clear()
  {
    CurrentSession.Remove(key);
  }
}

kick it on DotNetKicks.com