This project is read-only.

RegisterPerWebRequest with open generic types

Nov 1, 2012 at 4:01 AM
Edited Nov 1, 2012 at 4:02 AM

To start, I'm loving what I see so far.  Having said that, I'm having a little trouble porting over some of my existing Castle Windsor registration code and was hoping to get some guidance.

I have a situation where I am doing the following with Castle Windsor:

Component.For( typeof ( IRepository<> ) )
		.ImplementedBy( typeof ( Repository<> ) )
		.LifestylePerWebRequest() );

Note the LifestylePerWebRequest.

I can easily do the following in Simple Injector:

container.RegisterOpenGeneric( typeof( IRepository<> ), typeof( Repository<> ) );

I have also read up on container.RegisterPerWebRequest...

My problem is combining the two in order to achieve what I was doing with Castle.  The RegisterPerWebRequest extension wants a generic type parameter, which I don't have....I have the open generic type.

Is there a way to accomplish this or am I looking at writing my own extension method to achieve this functionality?

Thanks in advance!

Nov 1, 2012 at 10:38 AM
Edited Nov 3, 2012 at 9:58 AM

This is an interesting and valid scenario. There is absolutely a way to accomplish this, although the current version doesn't make it really easy to do so. Although the extensibility mechanisms of the Simple Injector will allow you to implement almost any scenario, not every scenario is trivial to implement (of course every DI container has its quirks) and implementing special lifetime for things like open generic mappings and generic decorators are a few of them. Doing this with open generic batch registration (using RegisterManyForOpenGeneric) on the other hand, is simple.

When I think about implementing repositories, there are ways to design around this, for instance by wrapping all repositories inside a Unit of Work, and letting the unit of work function as a factory for repositories. An example of such design can be found in this blog post.

Another option is to move the parts of your open generic repository implementation that need to be per web request to a (non-generic) dependency. This allows you to register that dependency with the Per Web Request lifestyle. This is a trick that is useful in other scenarios when using generic types as well, such as when registering open generic decorators.

These solutions are of course workarounds and might not be applicable to your situation. So if that's the case, this is how to register your open generic repository type with a Per Web Request lifestyle:

 

container.RegisterOpenGeneric(typeof(IRepository<>), typeof(FakeRepository<>));

container.ExpressionBuilt += (sender, e) =>
{
    var type = e.RegisteredServiceType;

    if (type.IsGenericType &&
        type.GetGenericTypeDefinition() == typeof(IRepository<>))
    {
        e.Expression = typeof(PerWebRequestInstanceCreator<>)
            .MakeGenericType(type)
            .GetMethod("BuildPerWebRequestExpression")
            .Invoke(null, new[] { e }) as Expression;
    }
};

 

After your normal RegisterOpenGeneric registration , there is a second registration that hooks onto the ExpressionBuilt event. This event allows you to rewrite the built Expression objects. The trick is to rewrite the expression that returns a transient to an expression that caches that instance and returns a cached instance. This code depends on the PerWebRequestInstanceCreator<T> class. This class however is an internal type, so you can't use it directly. You need a copy of it (and I added the BuildPerWebRequestExpression method to it):

 

internal sealed class PerWebRequestInstanceCreator<T> where T : class
{
    private readonly Func<T> instanceCreator;
    private readonly bool disposeWhenRequestEnds;

    internal PerWebRequestInstanceCreator(Func<T> instanceCreator,
        bool disposeWhenRequestEnds)
    {
        this.instanceCreator = instanceCreator;
        this.disposeWhenRequestEnds = disposeWhenRequestEnds;
    }

    public static Expression BuildPerWebRequestExpression(
        ExpressionBuiltEventArgs e)
    {
        const bool DisposeWhenRequestEnds = true;

        // Extract a Func<T> delegate for creating the transient TConcrete.
        var transientInstanceCreator = Expression.Lambda<Func<T>>(
            e.Expression, new ParameterExpression[0]).Compile();

        var instanceCreator = new PerWebRequestInstanceCreator<T>(
            transientInstanceCreator, DisposeWhenRequestEnds);

        return Expression.Call(Expression.Constant(instanceCreator),
            instanceCreator.GetType().GetMethod("GetInstance"));
    }

    public T GetInstance()
    {
        var context = HttpContext.Current;

        if (context == null)
        {
            // No HttpContext: Let's create a transient object.
            return this.instanceCreator();
        }

        T instance = (T)context.Items[this.GetType()];

        if (instance == null)
        {
            instance = this.CreateInstance(context);
        }

        return instance;
    }

    private T CreateInstance(HttpContext context)
    {
        T instance = this.instanceCreator();

        context.Items[this.GetType()] = instance;

        if (this.disposeWhenRequestEnds)
        {
            var disposable = instance as IDisposable;

            if (disposable != null)
            {
                SimpleInjectorWebExtensions.RegisterForDisposal(disposable);
            }
        }

        return instance;
    }
}

 

I know, it's a lot of code. I'll try to make this less painful in a future version.

Feb 25, 2013 at 2:21 PM
Simple Injector 2.0 has just been released. This version makes it much easier to register custom lifestyles for open generics, batch registrations and decorators. With Simple Injector 2 you can simply make your registration as follows:
container.RegisterOpenGenerc(
    typeof(IRepository<>), 
    typeof(Repository<>), 
    new WebRequestLifestyle());
Please note that the PerWebRequestInstanceCreator<T> that was added to version 1.6 had been removed from 2.0.
Marked as answer by dot_NET_Junkie on 11/5/2013 at 7:36 AM