More on ServiceLocator - caching
I got an idea while watching this webcast with Rob Conery and Jeremy Miller - Jeremy was talking about StructureMap and how you can set caching with it. It took me a few hours but I managed to duplicate that functionality. Of course, it doesn't work when you call Discover() - that one will still be the regular un-cached call (I think Jeremy calls it PerInstance).
This is an usage example:
- Bind<DataContext>
- .To(() => new DBDataContext())
- .CacheBy(InstanceScope.Singleton);
- Bind<IProductRepository>
- .To(() => new ProductRepository())
- .CacheBy(InstanceScope.HttpContext);
Here are the helper classes in all their glory:
- using System;
- using System.Collections.Generic;
- using System.Threading;
- using System.Web;
- namespace Helper
- {
- // usage:
- // Bind<ISomething>
- // .To(() => new Something())
- // .CacheBy(InstanceScope.ThreadLocal);
- /// <summary>
- /// Fluent interface simplifying the registration of implementations
- /// </summary>
- /// <typeparam name="T">Type to implement</typeparam>
- /// <example>Bind<IRepository>.To(() => new Repository());</example>
- public static class Bind<T> where T : class
- {
- /// <summary>
- /// Maps T to Func<T>
- /// </summary>
- /// <param name="func">Function returning an instance of T</param>
- public static BindHelper<T> To(Func<T> func)
- {
- ServiceLocator.Register(func);
- return new BindHelper<T>(func);
- }
- }
- public class BindHelper<T> where T : class
- {
- private readonly Func<T> func;
- public BindHelper(Func<T> func)
- {
- this.func = func;
- }
- public void CacheBy(InstanceScope scope)
- {
- ServiceLocator.Unregister<T>();
- ServiceLocator.Register(Memoize(scope));
- }
- private static readonly Dictionary<string, object> cache = new Dictionary<string, object>();
- private Func<T> Memoize(InstanceScope scope)
- {
- Func<Type, string> GetTypeKey;
- switch (scope)
- {
- case InstanceScope.NoCaching:
- return func;
- case InstanceScope.Singleton:
- GetTypeKey = type => type.ToString();
- break;
- case InstanceScope.ThreadLocal:
- GetTypeKey = type => type + "_" + Thread.CurrentThread.ManagedThreadId;
- break;
- case InstanceScope.HttpContext:
- GetTypeKey = type => type + "_" + HttpContext.Current;
- break;
- case InstanceScope.Hybrid:
- GetTypeKey = type => type + "_" + HttpContext.Current ?? Thread.CurrentThread.ManagedThreadId.ToString();
- break;
- default:
- GetTypeKey = type => type.ToString();
- break;
- }
- return () =>
- {
- object result;
- if (!cache.TryGetValue(GetTypeKey(typeof(T)), out result))
- cache[GetTypeKey(typeof(T))] = func();
- return cache[GetTypeKey(typeof(T))] as T;
- };
- }
- }
- }
By all means, if you can figure out a way to simplify this, let me know :)
Ah, I forgot the InstanceScope enum:
- namespace Helper
- {
- public enum InstanceScope
- {
- HttpContext,
- Hybrid,
- ThreadLocal,
- Singleton,
- NoCaching
- }
- }
Comments