Register type by the dependency on whether it is inserted into a controller from a certain MVC Area

Aug 24, 2012 at 10:15 PM

I have used the following code with Ninject to provide these types only when injected into controllers that come from the Admin MVC area within my MVC 3 application:

 #region Admin bindings use noncaching repositories

// custom when condition
Func<IRequest, bool> adminAreaRequest = new Func<IRequest, bool>(r => r.Target.Member.ReflectedType.FullName.Contains("Areas.Admin"));

kernel.Bind<ICategorizationRepository<DirectoryCategory>>().To<XmlCategorizationProvider<DirectoryCategory>>().When(adminAreaRequest).InRequestScope();

kernel.Bind<ICategorizationRepository<PostCategory>>().To<XmlCategorizationProvider<PostCategory>>().When(adminAreaRequest).InRequestScope();

#endregion

I know controllers in Admin area will always be have a Areas.Admin in their namespace declaration so that's why I make this check there.

Does Simple Injector provide the same functionality (or similar one)?


Coordinator
Aug 25, 2012 at 8:19 AM
Edited Aug 25, 2012 at 8:20 AM

Out of the box Simple Injector does not contain a feature like Ninject's Contextual Binding. However, there is documentation in the wiki that explains how to extend the Simple Injector to enable this. This documentation defines a RegisterWithContext extension method, provides similar functionality to Ninject's When statement. Your registration might look like this:

Func<Type, bool> adminAreaRequest = parentType => parentType.FullName.Contains("Areas.Admin");

container.RegisterPerWebRequest<XmlCategorizationProvider<PostCategory>>(); 

container.RegisterWithContext<ICategorizationRepository<PostCategory>>(context => 
{
    if (adminAreaRequest(context.ImplementationType))
        return container.GetInstance<XmlCategorizationProvider<PostCategory>>();
    else
        return container.GetInstance ... // default registration
});

The RegisterWithContext method takes a Func<DependencyContext, TService> delegate. When the registered service (in this case ICategorizationRepository<PostCategory>) is requested, the delegate will be called, supplied with a DependencyContext instance, which contains information about the parent object in which the ICategorizationRepository<PostCategory> is injected. In this example, a delegate is registered that calls back into the container to fetch an XmlCategorizationProvider<PostCategory>. This XmlCategorizationProvider<PostCategory> is registered explicitly with a Per Web Request lifetime using the RegisterPerWebRequest extension method.

When you have many of these registrations, it could be useful to write a convenient helper method, to make the registration easier:

public static void RegisterCategorizationRepositoryFor<TEntity>(this Container container)
{
    container.RegisterPerWebRequest<XmlCategorizationProvider<TEntity>>();
    container.RegisterWithContext<ICategorizationRepository<TEntity>>(context =>
    {
        if (context.ImplementationType.FullName.Contains("Areas.Admin")))
            return container.GetInstance<XmlCategorizationProvider<TEntity>>();
        else
            return container.GetInstance<DefaultCategorizationProvider<TEntity>>();
    });
}

This code does the same registration as before, but now wrapped in a generic extension method. This allows you to register your types as follows:

container.RegisterCategorizationRepositoryFor<DirectoryCategory>();
container.RegisterCategorizationRepositoryFor<PostCategory>();

However, instead of doing context based injection, you could also make your contract more specific. You could for instance let those Admin controllers depend on an IAdminCAtegorizationRepository<T> and register this interface to the XmlCategorizationProvider<T> implementation. I can't really look into your design, so this could very well be a very inconvenient design, but in general you should try to remove ambiguity in the design first, before falling back on context based injection.

Marked as answer by dot_NET_Junkie on 11/4/2013 at 2:00 AM
Aug 25, 2012 at 4:28 PM

Thank you for providing these details. I did in fact refactor my design so that I don't need this now but it wll come handy in the future.