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);
}
}