Saturday, January 03, 2009

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:

  1. Bind<DataContext> 
  2.   .To(() => new DBDataContext()) 
  3.   .CacheBy(InstanceScope.Singleton); 
  4. Bind<IProductRepository> 
  5.   .To(() => new ProductRepository()) 
  6.   .CacheBy(InstanceScope.HttpContext); 

Here are the helper classes in all their glory:

  1. using System; 
  2. using System.Collections.Generic; 
  3. using System.Threading; 
  4. using System.Web; 
  5.  
  6. namespace Helper 
  7.   // usage: 
  8.   // Bind<ISomething> 
  9.   //   .To(() => new Something()) 
  10.   //   .CacheBy(InstanceScope.ThreadLocal); 
  11.  
  12.   /// <summary> 
  13.   /// Fluent interface simplifying the registration of implementations 
  14.   /// </summary> 
  15.   /// <typeparam name="T">Type to implement</typeparam> 
  16.   /// <example>Bind&lt;IRepository&gt;.To(() => new Repository());</example> 
  17.   public static class Bind<T> where T : class 
  18.   { 
  19.     /// <summary> 
  20.     /// Maps T to Func&lt;T&gt; 
  21.     /// </summary> 
  22.     /// <param name="func">Function returning an instance of T</param> 
  23.     public static BindHelper<T> To(Func<T> func) 
  24.     { 
  25.       ServiceLocator.Register(func); 
  26.  
  27.       return new BindHelper<T>(func); 
  28.     } 
  29.   } 
  30.  
  31.   public class BindHelper<T> where T : class 
  32.   { 
  33.     private readonly Func<T> func; 
  34.  
  35.     public BindHelper(Func<T> func) 
  36.     { 
  37.       this.func = func; 
  38.     } 
  39.  
  40.     public void CacheBy(InstanceScope scope) 
  41.     { 
  42.       ServiceLocator.Unregister<T>(); 
  43.       ServiceLocator.Register(Memoize(scope)); 
  44.     } 
  45.  
  46.     private static readonly Dictionary<string, object> cache = new Dictionary<string, object>(); 
  47.  
  48.     private Func<T> Memoize(InstanceScope scope) 
  49.     { 
  50.       Func<Type, string> GetTypeKey; 
  51.        
  52.       switch (scope) 
  53.       { 
  54.         case InstanceScope.NoCaching: 
  55.           return func; 
  56.  
  57.         case InstanceScope.Singleton: 
  58.           GetTypeKey = type => type.ToString(); 
  59.           break
  60.  
  61.         case InstanceScope.ThreadLocal: 
  62.           GetTypeKey = type => type + "_" + Thread.CurrentThread.ManagedThreadId; 
  63.           break
  64.  
  65.         case InstanceScope.HttpContext: 
  66.           GetTypeKey = type => type + "_" + HttpContext.Current; 
  67.           break
  68.  
  69.         case InstanceScope.Hybrid: 
  70.           GetTypeKey = type => type + "_" + HttpContext.Current ?? Thread.CurrentThread.ManagedThreadId.ToString(); 
  71.           break
  72.  
  73.         default
  74.           GetTypeKey = type => type.ToString(); 
  75.           break
  76.       } 
  77.  
  78.       return () => 
  79.                { 
  80.                  object result; 
  81.                  if (!cache.TryGetValue(GetTypeKey(typeof(T)), out result)) 
  82.                    cache[GetTypeKey(typeof(T))] = func(); 
  83.  
  84.                  return cache[GetTypeKey(typeof(T))] as T; 
  85.                }; 
  86.     } 
  87.   } 

By all means, if you can figure out a way to simplify this, let me know :)

Ah, I forgot the InstanceScope enum:

  1. namespace Helper 
  2.   public enum InstanceScope 
  3.   { 
  4.     HttpContext, 
  5.     Hybrid, 
  6.     ThreadLocal, 
  7.     Singleton, 
  8.     NoCaching 
  9.   } 

No comments: