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

Advertisements

26 thoughts on “How to wrap the ASP.net session state

  1. This is pretty damn sweet. I’ll definitely be using this. Tho, I’ll probably change a couple things.

    Throwing when no valid HttpContext is available is not the best thing IMHO. It makes testing more difficult, and can result in users seeing error pages when the server is recycling.

    I’d suggest returning a dummy session (I usually do this for the cache; you have to use Activator.CreateInstance since the cache doesn’t have a public constructor), as this is acceptable 99.999% of the time, and avoids having to deal with handling that very rare exception wherever you access a session variable.

  2. Thanks! 🙂

    Idea: ValueOrDefault, could also return default(T) or new T() when session is not available.. and Value could call the init-method everytime, if session is not available, but I would’nt like it that much.

    I, myself wrap HttpContext.Current.Application, HttpContext.Current.Items and HttpContext.Current.Session in a IContextStorage with three IDictionaries…

    This is then overidable with “dummy”-implementations using the ThreadVariable. So in a test, i simply use:

    using(ContextStorage.Override(new StaticStorage())
    {
    // anny code using ContextStorage.Session
    // or SessionVariable
    // or ApplicationVariable
    // or RequestVariable
    }

    But anyhow, I rather like exceptions then unexpected undefined behaviour.

  3. Pingback: How to make ASP.net session state testable « .Net Braindrops

  4. This looks good.

    A few questions/comments though:

    In your example, you first call MySessionVariables.UserGroups.HasValue and then MySessionVariables.UserGroups.Value.Contains(“Admin”), both calls result in call to GetInternalValue. Why not have a local variable (private T m_value) to store the retrived value in, then you would only have to do that once every http request.

    Also, there seems to me be a threading issue. If the SessionVariable value is null, and somebody tries to read it (GetInternalValue(true)), while another thread is trying to set a new value. Then the new value could be replaced by default(T) value.

    I dont know if it make sense, or is even an issue.

  5. Hi Egil,

    1) “The first answer i gave here was wront”
    But still, Value throws an exception if nothing is defined. So you had to use ValueOrDefault and then check against the default value wich is null for reference types and a specific one per struct or value type.
    2) Normally this should be locked, but IMHO it is not necessary in this case. All HttpHandlers that implement IRequiresSessionState are locked to one request per session. They enter the lock in SessionStateModule.BeginAcquireState() and leave it in SessionStateModule.OnReleaseState().

    You can avoid this by implementing IReadOnlySessionState in your IHttpHandler or Page (wich implicitly implements IHttpHandler while the PageBuilder usually compiles IRequiresSessionState into *.aspx files).

    If I’m wrong, please correct me.

  6. Admittedly, I am not that familiar with session management in asp.net, so my concern with threading issues might not be valid.

    Could you elaborate a bit on your StaticStorage() class. Is it a common place where you have Session, Application and Current.Items available?

  7. Yes, but then i had to post a solution with 6 or 7 classes plus unit tests. People rather copy paste then download solutions.

    I just removed the dependency to HttpContext by wrapping it. Using my ThreadVariable (older post about ambient context pattern) i can then override the Dictionaries for Session, Applications and Items.

    public interface IContextStorage
    {
    IDictionary Items { get; }
    IDictionary Session { get; }
    IDictionary Application { get; }
    }

    For Session and Application I made a wrapper to HttpSessionState and HttpApplicationState that implements IDictionary.

    So my current implementations are HttpContextStorage as default, StaticContextStorage for Tests and ServiceSessionContextStorage that delegates to specific Dictionaries for Session and Application in WCF Services.

    Interested in the code? Someone else, too?

  8. Indeed, I am looking very much forward to working with MVC on my next pet project.

    Lars, I wonder what you think of this (http://pastebin.com/f66bcfb3c) approach, using Extension methods to give you a strongly typed experience with the SessionState.

    I put that code together a few weeks ago.

  9. Well, I would like to avoid strings as keys. While this is not possible i prefer to only specify the key once and then forget about it whereever i can. Putting it in a constant and then using the Extension Methods could be OK, but IMHO not that nice.

    I also don’t like to access HttpContext.Current.Session in order to be able to call the ExtensionMethod, because then I have to handle the possible null references every time I use a value. The initializer and testability (https://startbigthinksmall.wordpress.com/2008/05/19/how-make-code-that-uses-my-sessionvariable-testable) are to other arguments for SessionVariable 🙂

  10. Hi,

    thanks for sharing your idea.

    We implemented it in our project, but when we published our web site on iis 7.0 and two or more users enter concurrently last user’s data overwrite the others users data, mainly when the session variable was use inside of objectdatasource.

    could you please help me?,

    thank’s

    Jaimir G.

  11. Hi,

    I’m sorry you encounter problems with the implementation. Shared session state is the worst bug I can imagine.

    It seems to be a caching problem. I don’t know the objectdatasource well, but could it be, that you have to add SessionParameters to it?

    <asp:ObjectDataSource ID="ods" runat="server" OldValuesParameterFormatString="original_{0}"
    SelectMethod="GetStuff" TypeName="RM.Stuff">
    <SelectParameters>
        <asp:SessionParameter  SessionField='SESSION_PAREMETER_NAME' Name="somename" Type="Int32" />
    </SelectParameters>
    

    Could you post your code or drop me a mail? (lars(ät)corneliussen.de)

  12. Hello Lars,

    We’ve already found our problem. A load analysis reported that the method “CurrentSession” executed thousands of times. To reduce the impact, we declared the variable “session” as static at class level, and that’s the reason that data of different users were mixed. So, we decided to move to your original previous version of “CurrentSession”

    Thanks for your answer and collaboration.

    Jaimir G.

  13. Hi Lars, I know time has passed (now is ’13 and you posted it on ’08). But I gotta say this class is awesome!! I have one comment/question about it through, If I have to check the session state constantly (on every page the user goes), does it really mean I have to create an object to check the value of the session?

      • Hi Lars, let’s see if this makes sense…

        test1.aspx.cs
        SessionVariable test1 = new SessionVariable(“SiteSession”);
        test1.Value = “0000”;

        test2.aspx.cs
        SessionVariable test2 = new SessionVariable(“SiteSession”);
        if (test2.HasValue)
        {
        //Specific logic goes here…
        }
        else
        {
        //Generic logic goes here…
        }

        but then, if I have to keep going back and forth with test2 page, I would have to keep creating a new SessionVariable class to retrieve the “SiteSession” session variable since SessionVariable clas is not a static class.

      • Declare and initialize the session variable in a static field or property in a third (static) class – you could call it “SharedState”, for example.

        Then access the very same instance from the two codebehind pages.

  14. Excellent and neat solution. I hope you’d appreciate my modest addition for really seamless usage – its this line of code:

    public static implicit operator T(SessionVariable var)
    {
    if (var == null)
    throw new InvalidOperationEcxeption(“Session variable must not be null”);
    return var.Value;
    }

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s