Scenario
ok, so everyone caches crap in one way or another, right (in fact, how many times have you written one of these little cheeseball cachehelpers in your career by now?). Well, this is probably my hundredth, but maybe someone will find this one helpful. This simple cachehelper is designed to encapsulate cached global items as well as session-based items (encapsulating, thus allowing any technique to be used internally). In this implementation, global caching uses simple static-caching techniques, while the session makes use of…well…the session. Easy to change later as scenarios permit.
The API is meant to be simple, and to minimize forcing the developer to work with thread-locking crap too. So, in the case of global cache items, the developer, checks to see if an item is cached, while providing a Func<T> setter to call if the item is *not* cached, thus removing the need for revealing locking and such. The session based one just requires an object of type HttpContextBase.
Here it is:
{
T EnsureGlobalItem<T>(Type key, Func<object> setter);
T EnsureSessionItem<T>(HttpContextBase context,
Type key, Func<object> setter);
void InvalidateSessionItem(HttpContextBase context, Type key);
}
In our case, we inject this into our consumers, which tend to be asp.net MVC controller
public class StandardCacheHelper : ICacheHelper
{
private static IDictionary<Type, object> _cache;
private static object _lock = new object();
static StandardCacheHelper()
{
_cache = new Dictionary<Type, object>();
}
public T EnsureGlobalItem<T>(Type key, Func<object> setter)
{
lock (_lock)
{
if (_cache.ContainsKey(key) == false)
{
_cache.Add(key, setter());
}
return (T)_cache[key];
}
}
public T EnsureSessionItem<T>(HttpContextBase context,
Type key, Func<object> setter)
{
VerifyParam(context, "context");
VerifyParam(key, "key");
lock (context.Session.SyncRoot)
{
if (context.Session[key.FullName] == null)
{
context.Session.Add(key.FullName, setter());
}
return (T)context.Session[key.FullName];
}
}
public void InvalidateSessionItem(HttpContextBase context, Type key)
{
VerifyParam(context, "context");
VerifyParam(key, "key");
context.Session.Remove(key.FullName);
}
private void VerifyParam(object param, string paramName)
{
if (param == null)
{
throw new ArgumentException(string.Format("{0} must not be null.", paramName));
}
}
}
s via Ninject:
public class UtilityModule : NinjectModule
{
public override void Load()
{
this.Bind<ICacheHelper>().To<StandardCacheHelper>().InSingletonScope();
}
}
The base controller exposes the helper as such:
public class BaseController:Controller
{
private ICacheHelper _cacheHelper = null;
public BaseController(ICacheHelper cacheHelper)
{
this._cacheHelper = cacheHelper;
}
public ICacheHelper CacheHelper
{
get{return this._cacheHelper;}
}
}
And then finally, the consumption is clean and simple (and testable of course!):
public class Foo{
public string Name{get;set;}
}
private IEnumerable<Foo> GetFoos()
{
Func<object> setter = () =>
{
return this.myService.GetFoos();
};
return this.CacheHelper.EnsureSessionItem<IEnumerable<Foo>>(
this.HttpContext, typeof(IEnumerable<Foo>), setter)
}
Enjoy. I’m coding my face off these days (love it!), so I’ll be posting lots more. I’ve got a bunch of handy custom knockout bindings, some nice clean javascript modules to use (using the javascript module pattern), extensions to Javascript and many more helpful things.