This project is read-only.

Register a partially closed decorator

Nov 12, 2014 at 11:21 AM
Edited Nov 12, 2014 at 11:25 AM
I'm trying to register a partially closed decorator. I have figured out a method but wanted to check if there is a better approach.

I have a CacheQueryResultDecorator which decorates a simple query abstraction IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult> to cache the results of some WCF queries. Some of the results need to be stored per session while others are application wide.

As such I want some results cached in HttpContext.Current.Session and others in HttpRuntime.Cache

I have 2 CacheProvider's: HttpContextCacheProvider and HttpRuntimeCacheProvider

and the decorator:
public sealed class CacheQueryResultDecorator<TCacheProvider, TQuery, TResult> :
    IQueryHandler<TQuery, TResult> where TQuery : IQuery<TResult>
    where TCacheProvider : CacheProvider
{
    public CacheQueryResultDecorator(
        IQueryHandler<TQuery, TResult> decorated,
        TCacheProvider cache)
    {
        this.decorated = decorated;
        this.cache = cache;
    }

    //......
This registration syntax is invalid but shows what I am trying to do
var types = new[]
    {
        typeof(IQueryHandler<Query1, Result1>),
        typeof(IQueryHandler<Query2, Result2>)
    };

container.RegisterDecorator(
    typeof(IQueryHandler<,>),
    typeof(CacheQueryResultDecorator<HttpContextCacheProvider,,>),
    context => (types).Contains(context.ServiceType));
I also tried this syntax to create a partial type but this fails because I have not supplied all 3 generic arguments:
var decoratorType = typeof(CacheQueryResultDecorator<,,>)
    .MakeGenericType(typeof(HttpContextCacheProvider));
This is how I got it to work:
foreach (var type in types)
{
    var decoratorType = typeof(CacheQueryResultDecorator<,,>)
        .MakeGenericType(
        typeof(HttpContextCacheProvider),
        type.GetGenericArguments()[0],
        type.GetGenericArguments()[1]);

    container.RegisterDecorator(type, decoratorType);
}
Are there any other ways to register this decorator?
Nov 12, 2014 at 11:41 AM
C# doesn't allow you to define partial open generic types. You have to fall back to using the MakeGenericType method. When building a generic type, the MakeGenericType method however forces you to supply all generic types, even if you want to build a partial open generic type. Your last example is about as good as it gets when doing this. If you need this more often though, it might be valuable to create an extension method for this:
public static class TypeExtensions {
    public static Type MakePartialOpenGenericType(this Type type,
        Type first = null, Type second = null, Type third = null) {
        Type[] arguments = type.GetGenericArguments();

        if (first != null) arguments[0] = first;
        if (second != null) arguments[1] = second;
        if (third != null) arguments[2] = third;

        return type.MakeGenericType(arguments);
    }
}
With this extension method you can write your example as follows:
typeof(CacheQueryResultDecorator<,,>).MakePartialOpenGenericType(first = typeof(HttpContextCacheProvider));
Nov 12, 2014 at 11:58 AM
Edited Nov 12, 2014 at 11:59 AM
A completely different way to solve this is using context based injection:
public sealed class CacheQueryResultDecorator<TQuery, TResult> : IQueryHandler<TQuery, TResult>
    where TQuery : IQuery<TResult> {
    public CacheQueryResultDecorator(ICacheProvider c, IQueryHandler<TQuery, TResult> d) { ... }
    ...
}

container.RegisterDecorator(typeof(IQueryHandler<,>), typeof(CacheQueryResultDecorator<,,>));

var types = new[] {
    typeof(IQueryHandler<Query1, Result1>),
    typeof(IQueryHandler<Query2, Result2>)
};

container.RegisterWithContext<ICacheProvider>(context => {
    if (types.Contains(context.ServiceType)) 
        return container.GetInstance<HttpContextCacheProvider>()
    else 
        return container.GetInstance<HttpRuntimeCacheProvider>()
});