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

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. { 
  3.   public enum InstanceScope 
  4.   { 
  5.     HttpContext, 
  6.     Hybrid, 
  7.     ThreadLocal, 
  8.     Singleton, 
  9.     NoCaching 
  10.   } 
  11. } 

Comments

Popular posts from this blog

Posting dynamic Master / Detail forms with Knockout

Comparing Excel files, take two

Johnny Got His Gun