Caching example with DynamicProxy
Here's a quick example of how to write a caching aspect with Castle DynamicProxy.
First, let's write an implementation and a service worth caching:
public interface IMyService | |
{ | |
string GetSomeLongRunningResult(string input); | |
} |
public class MyService : IMyService | |
{ | |
public string GetSomeLongRunningResult(string input) | |
{ | |
Thread.Sleep(5000); // simulate long running process | |
return string.Format("Result of '{0}' returned at {1}", input, DateTime.Now); | |
} | |
} |
Next, you need to decide where to cache the results. If you're using a web app, maybe try ASP.NET's Cache object. If you're writing an Azure app, try AppFabric caching. You can cache things in a database, a text file, whatever is appropriate for your application.
For this simple example, I'm going to cache everything in memory, in a static Dictionary object. Also, nothing that goes into my cache will ever expire or be invalidated. Once it's cached, it's cached forever. Also, it's not thread safe. Not a very useful cache in a real app, so let me just be clear: do not use this in a production application. It's only for demonstration: whatever you use for caching is up to you, I'm just demonstrating the aspect part.
// this is not a class you should actually use in production! | |
// this is just caching things in a static dictionary | |
// and individual items don't ever expire | |
// you should use a cache that suits your application, like ASP.NET's Cache class, AppFabric caching, etc. | |
public class StaticCachingInterceptor : IInterceptor | |
{ | |
static readonly IDictionary<string, object> _cache; | |
static StaticCachingInterceptor() | |
{ | |
_cache = new Dictionary<string, object>(); | |
} | |
public void Intercept(IInvocation invocation) | |
{ | |
var cacheKey = GenerateCacheKey(invocation.Method.Name, invocation.Arguments); | |
if (_cache.ContainsKey(cacheKey)) | |
{ | |
invocation.ReturnValue = _cache[cacheKey]; | |
return; | |
} | |
invocation.Proceed(); | |
_cache[cacheKey] = invocation.ReturnValue; | |
} | |
static string GenerateCacheKey(string name, object[] arguments) | |
{ | |
if (arguments == null || arguments.Length == 0) | |
return name; | |
return name + "--" + string.Join("--", arguments.Select(a => a.ToString()).ToArray()); | |
} | |
} |
I'm generating the cache key by appending the argument values to the method name. So if you call MyMethod("test") and MyMethod("test2"), that's two different keys that will cache two different results. This might work for you, or you might need a more complex GenerateCacheKey method (something that uniquely identifies objects, perhaps).
Next, I wire up my IoC container to return MyService when asked for an implementation of IMyService. But I also have to make sure that my IoC container (StructureMap in this case) applies the caching interceptor to it.
ObjectFactory.Configure(x => | |
{ | |
var proxyGenerator = new ProxyGenerator(); | |
x.For<IMyService>().Use<MyService>() | |
.EnrichWith(y => proxyGenerator.CreateInterfaceProxyWithTarget<IMyService>(y, new StaticCachingInterceptor())); | |
}); |
I put it all together in a simple console app:
var myService = ObjectFactory.GetInstance<IMyService>(); | |
Console.WriteLine("[{0}] Now getting the result with argument 'foo' for the first time...", DateTime.Now); | |
Console.WriteLine("[{0}] {1}", DateTime.Now, myService.GetSomeLongRunningResult("foo")); | |
Console.WriteLine("[{0}] Now getting the result with argument 'foo' for the second time...", DateTime.Now); | |
Console.WriteLine("[{0}] {1}", DateTime.Now, myService.GetSomeLongRunningResult("foo")); | |
Console.WriteLine("[{0}] Now getting the result with argument 'bar' for the first time...", DateTime.Now); | |
Console.WriteLine("[{0}] {1}", DateTime.Now, myService.GetSomeLongRunningResult("bar")); |
And here's the result: