- If you’ve read my Article about How to wrap the ASP.net session state I want to show you how to make the wrapper testable using the Free .Net Ambient Context Pattern Implementation.
- If you only want a easy ASP.net session state wrapper snippet without any dependencies, try How to wrap the ASP.net session state
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: http://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); } }