I’ve always been impressed with Ayende’s LocalData class. I like Ritesh Rao’s vision of local data even more. I loved how he gives the option of application, session, and thread storage. But, I feel like those implementations leave something to be desired from a consumers perspective. So, I’ve come up with a base interface that is inspired by both of the above projects. It also defines an abstract class so I can create new implementations whenever I need to.
The ILocalData interface:
public interface ILocalData
{
object this[object key] { get; set; }
T Get(object key);
bool TryGet(object key, out T value);
T Set(object key, T value);
void Add(object key, object value);
void Remove(object key);
void Remove(params object[] keys);
bool Contains(object key);
void Clear();
}
The IDataBucket interface:
public interface IDataBucket : IEnumerable
{
object this[object key] { get; set; }
bool ContainsKey(object key);
void Remove(object key);
void Clear();
void Add(object key, object value);
bool UseSynchLock { get; set; }
}
The entry point and abstract class:
public class LocalData : BucketData
{
public LocalData(IDataBucket bucket)
: base(bucket)
{}
public LocalData(Func<IDataBucket> bucketProvider)
: base(bucketProvider)
{}
}
public abstract class BucketData : ILocalData, IEnumerable
{
private static readonly object lockInstance = new object();
private readonly Func<IDataBucket> bucketProvider;
protected BucketData(IDataBucket bucket)
: this(() => bucket)
{}
protected BucketData(Func<IDataBucket> bucketProvider)
{
this.bucketProvider = bucketProvider;
}
public IDataBucket Bucket
{
get { return bucketProvider(); }
}
public object this[object key]
{
get { return Do(() => Bucket[key.ToString()]); }
set { Do(() => Bucket[key.ToString()] = value); }
}
public IEnumerator GetEnumerator()
{
return Bucket.GetEnumerator();
}
public T Get<T>(object key)
{
return Do(() =>
{
T value;
TryGet(key, out value);
return value;
});
}
public bool TryGet<T>(object key, out T value)
{
value = default(T);
object data = Bucket[key.ToString()];
if (data == null)
return false;
if (data is T)
{
value = (T) data;
return true;
}
return false;
}
public T Set<T>(object key, T value)
{
return Do(() =>
{
Bucket[key.ToString()] = value;
return value;
});
}
public void Add(object key, object value)
{
Set(key, value);
}
public void Remove(object key)
{
if (Contains(key))
Do(() => Bucket.Remove(key.ToString()));
}
public void Remove(params object[] keys)
{
foreach (object key in keys)
{
Remove(key);
}
}
public bool Contains(object key)
{
return Do(() => Bucket[key.ToString()] != null);
}
public void Clear()
{
Do(() => Bucket.Clear());
}
private V Do<V>(Func<V> function)
{
if (Bucket.UseSynchLock)
{
lock (lockInstance)
{
return function();
}
}
return function();
}
private void Do(Action action)
{
if (Bucket.UseSynchLock)
{
lock (lockInstance)
{
action();
}
}
action();
}
}
With the following, I can finally say goodbye to buggy HttpSessionState code in legacy systems and even support session isolation.
public class HttpSessionDataBucket : IDataBucket
{
private readonly string bucketName;
public HttpSessionDataBucket() : this(typeof (IDataBucket))
{}
public HttpSessionDataBucket(Type forType) : this(forType.FullName)
{}
public HttpSessionDataBucket(string bucketName)
{
this.bucketName = bucketName;
}
private Hashtable Bucket
{
get
{
HttpSessionState session = HttpContext.Current.Session;
object ht = session[bucketName];
if (ht is HashtableDataBucket)
return (Hashtable) ht;
session[bucketName] = ht = new Hashtable();
return ht as Hashtable;
}
}
public object this[object key]
{
get { return Bucket[key.ToString()]; }
set { Bucket[key.ToString()] = value; }
}
public bool ContainsKey(object key)
{
return Bucket[key.ToString()] != null;
}
...
}
ILocalData localData = new LocalData(new HttpSessionDataBucket(typeof(IMenuItem)));
This is obviously still evolving but, for now, I love being able to have an ILocalData instance in my domain objects (most likely not session buckets though) and being able to call “localData.Clear();” without messing up the rest of the system.
It’s amazing how far we’ll go sometimes. Schwew!
-=Code Well


