Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Property Injection in Base Controller using Ninject 2

I have the following code in my Global.aspx

protected override void OnApplicationStarted()
{
    AreaRegistration.RegisterAllAreas();
    RegisterRoutes(RouteTable.Routes);
    RegisterAllControllersIn(Assembly.GetExecutingAssembly());
}

protected override IKernel CreateKernel()
{
    return new StandardKernel(new ServiceModule());
}

I also have the following Ninject Module:

internal class ServiceModule : NinjectModule
{
    public override void Load()
    {
        Bind<IProductService>().To<ProductService>().InRequestScope();
    }
}

I also have a base controller:

public class BaseController : Controller
{
    [Inject]
    public IProductService ProductService
    {
        get;
        set;
    }
}

This code works. The problem I am having is that I would like to remove the inject attribute from the base controller and specify this in the Ninject ServiceModule instead. I other words, how would I go about writing a binding rule in the ServiceModule that tells Ninject to inject ProductService into the property in the base controller?

If I remove the attribute I will get a NullReferenceException.

like image 589
Thomas Avatar asked Jun 30 '10 23:06

Thomas


2 Answers

Convention-based binding lives in http://github.com/ninject/ninject.extensions.conventions -- one implements IBindingGenerator. This is largely concerned with discovering interfaces and services though.

In general, constructor injection is a good default approach. However the manner in which ASP.NET MVC works makes this harder to do (Hence FubuMVC etc.). So property injection is the next best option.

You may find that using OnActivation in your Bind may allow you to do enough - and if you can, this is by far the simplest.

I'd characterise what you're trying to do as convention-based activation. The problem's are:

  • deciding what you are going to auto-inject. Are you going to inject everything public that isn't concrete? Everything that your Kernel knows about? Unless you can come up with a clean definition of what you want to do, the injection process can become unpredictable and difficult to understand. You end up debugging and explaining to colleagues a lot.

  • making it efficient. Ninject dynamically generates code behind the scenes to make the activation of an instance efficient (i.e., at the time of walking the class looking for [Inject] markers it generates code once to do that which then gets jitted as if you'd written it longhand).

Looking in the code, there's no easy way OOTB. Looks like adding a custom IInjectionHeuristic would do the trick.

However if you're getting this deep into containers, you need to

  1. pause and see if you can keep it simple by not going down this road
  2. go to the ninject mailing list and search for similar things
  3. if you still want to do it, send a mail there.
like image 181
Ruben Bartelink Avatar answered Nov 10 '22 06:11

Ruben Bartelink


Expanding on the ideas by Ruben Bartelink you could create a custom implementation of IInjectionHeuristic.

public class ControllerInjectionHeuristic : NinjectComponent, IInjectionHeuristic
{
    private readonly IKernel kernel;

    public BaseControllerInjectionHeuristic(IKernel kernel)
    {
        this.kernel = kernel;
    }

    public bool ShouldInject(MemberInfo member)
    {
        if (member.ReflectedType != typeof(BaseController))
        {
            return false;
        }

        var propertyInfo = member.ReflectedType.GetProperty(member.Name);
        object service = kernel.TryGet(propertyInfo.PropertyType);

        return service != null;
    }
}

The ControllerInjectionHeuristic will inject any property (service) on BaseController for which the kernel is able to resolve the service.

Register the custom implementation with the kernel.

var kernel = new StandardKernel();
kernel.Components.Add<IInjectionHeuristic, ControllerInjectionHeuristic>();

Another solution to the problem is to use OnActivation. (This solution is a untested, but it should give you an idea on how to proceed).

public class ControllerModule : NinjectModule
{
    public override void Load()
    {
        // Get all controller types. You could use
        // Ninject.Extensions.Conventions.
        IEnumerable<Type> controllerTypes = null;
        foreach (var controllerType in controllerTypes)
        {
            Bind(controllerType).ToSelf().InRequestScope()
                .OnActivation(ControllerActivation);
        }
    }

    private static void ControllerActivation(IContext context, object obj)
    {
        var controller = obj as BaseController;
        if (controller == null)
        {
            return;
        }

        controller.ProductService = context.Kernel.Get<IProductService>();
    }
}
like image 2
mrydengren Avatar answered Nov 10 '22 06:11

mrydengren