Okay, this is a fairly wacky scenario, I admit.
I am using IRepository<T> for my data access, and I wanted to be able to specify attributes on the T (that is, on the entity, not the repository) and have the behavior of the class change. For instance, if I have IRepository<OrderStatus>, that is something that changes very rarely, so I can safely cache that. But I want to put [Cacheable] on OrderStatus, not on OrderStatusRepository. To make matter worse, I never explicitly register IRepository<OrderStatus>, I only register IRepository<T> and let Windsor figure the rest of it out.
I thought that it would be a major hurdle, but it turn out to be fairly easy.
Windsor has the concept of Component Model, basically everything that Windsor knows about a component, and you can plug your own contributers, which can add additional knowledge to Windsor.
/// <summary>
/// Inspect the model and add a caching interceptor if appropriate
/// </summary>
public class CachingInterceptorContributer : IContributeComponentModelConstruction
{
/// <summary>
/// Inspect the model and add a caching interceptor if appropriate
/// </summary>
public void ProcessModel(IKernel kernel, ComponentModel model)
{
bool isRepository = model.Service.IsGenericType &&
model.Service.GetGenericTypeDefinition() == typeof(IRepository<>);
if (isRepository == false)
return;
Type entityType = model.Service.GetGenericArguments()[0];
bool cacheable = entityType
.GetCustomAttributes(typeof(CacheableAttribute),true).Length != 0;
if(cacheable==false)
return;
model.Interceptors.Add(new InterceptorReference(typeof(CachingInterceptor)));
}
}
This checks that the component is an IRepository<T>, and that the T has [Cacheable] attribute. The real nice thing here is that it will be called when Windsor decides that it needs to create an IRepository<OrderStatus>, thereby giving me the chance to add the interceptor to this (and only this) repository.
The interceptor is very simple, and builds on already existing code:
/// <summary>
/// Add query caching capabilities
/// </summary>
public class CachingInterceptor : IInterceptor, IOnBehalfAware
{
private string typeName;
/// <summary>
/// Intercepts the specified invocation and adds query caching capabilities
/// </summary>
/// <param name="invocation">The invocation.</param>
public void Intercept(IInvocation invocation)
{
using(With.QueryCache(typeName))
{
invocation.Proceed();
}
}
/// <summary>
/// Sets the intercepted component model.
/// </summary>
/// <param name="target">The target.</param>
public void SetInterceptedComponentModel(ComponentModel target)
{
typeName = target.Service.GetGenericArguments()[0].FullName;
}
}
I am using this just to tell NHibernate that it should cache the query, since NHibernate would already be caching the entities in its second level cache (I might show later how I handled that). Now to register the contributer:
/// <summary>
/// Registers various add-ons to the container
/// </summary>
public class AdditionalFunctionalityFacility : AbstractFacility
{
/// <summary>
/// The actual addition of resolvers
/// </summary>
protected override void Init()
{
Kernel.AddComponent("caching.interceptor", typeof(CachingInterceptor));
Kernel.ComponentModelBuilder.AddContributor(new CachingInterceptorContributer());
}
}
And that is it. I register a facility, which takes care of configuring the interceptor and the contributer, and I am set. I can now simply put [Cacheable] on an entity, and it will cache that in all levels.
This technique is a powerful one, and I do believe that I will use it more in the future.