views:

799

answers:

4

Hello all,

Recently I've switched to Ninject 2.0 release and started getting the following error:

Error occured: Error activating SomeController
More than one matching bindings are available.
Activation path:
  1) Request for SomeController

Suggestions:
  1) Ensure that you have defined a binding for SomeController only once.

However, I'm unable to find certain reproduction path. Sometimes it occurs, sometimes it does not. I'm using NinjectHttpApplication for automatic controllers injection. Controllers are defined in separate assembly:

public class App : NinjectHttpApplication
{
    protected override IKernel CreateKernel()
    {
        INinjectModule[] modules = new INinjectModule[] {
            new MiscModule(),
            new ProvidersModule(),
            new RepositoryModule(),
            new ServiceModule()
        };

        return new StandardKernel(modules);
    }

    protected override void OnApplicationStarted()
    {
        RegisterRoutes(RouteTable.Routes);
        RegisterAllControllersIn("Sample.Mvc");
        base.OnApplicationStarted();
    }

    /* ............. */

}

Maybe someone is familiar with this error.

Any advice?

+1  A: 

Are you sure you really are creating a single completely new Kernel from scratch in your OnApplicationStarted every time it's invoked ? If you're not and you're actually creating it once but potentially running the registration bit twice. Remember that you're not guaranteed to only ever have one App class instantiated ever within a given AppDomain.

Ruben Bartelink
Take a look at README in the bottom of the page http://github.com/enkari/ninject.web.mvc (I think that my code is pretty similar). However, when I look to NinjectHttpApplication Application_Start() method http://github.com/enkari/ninject.web.mvc/blob/master/mvc1/src/Ninject.Web.Mvc/NinjectHttpApplication.cs it makes me think that configuration is done per single kernel instance.
Denis Parchenko
@Cray: I will look in time - sorry dont have time now but wanted to answer in case it unblocked you. The main point of my post is to say "this exception suggests that you put a duplicate set or Bindings into the kernel somehow, perhaps by initialising twice into the same kernel", no matter where you got the code and regardless of whether your question represents an accurate reflection of your actual code that is failing. (I have done zero with the RTM NI2 but lots with various pre-2 builds)
Ruben Bartelink
+9  A: 

I finally figured this issue out recently. Apparently, the NinjectHttpApplication.RegisterAllControllersIn() function doesn't do all of the proper bindings needed. It binds your concrete controller implementations to IController requests. For example, if you have a controller class called SampleMvcController, which inherits from System.Web.Mvc.Controller. It would do the following named binding during application start:

kernel.Bind<IController>().To(SampleMvcController).InTransientScope().Named("SampleMvc");

But when debugging the NinjectControllerFactory, I find that request are being made for the Ninject Kernel to return an object for the class "SampleMvcController", not for a concrete implementation of IController, using the named binding of "SampleMvc".

Because of this, when the first web request that involves the SampleMvcController is made, it creates a binding of SampleMvcController to itself. This is not thread safe though. So if you have several web requests being made at once, the bindings can potentially happen more than once, and now you are left with this error for having multiple bindings for the SampleMvcController.

You can verify this by quickly refreshing an MVC URL, right after causing your web application to restart.

The fix:

The simplest way to fix this issue is to create a new NinjectModule for your controller bindings, and to load this module during application start. Within this module, you self bind each of your defined controllers, like so:

class ControllerModule : StandardModule {
      public override Load() {
        Bind<SampleMvcController>().ToSelf();
        Bind<AnotherMvcController>().ToSelf();
      }
    }

But if you don't mind changing the Ninject source code, you can modify the RegisterAllControllersIn() function to self bind each controller it comes across.

Aaron Ramirez
Thanx for the hint. I'll try it and mark your answer. Have you notified Nate Kohari already? =)
Denis Parchenko
A: 

I added this to my global.ascx.cs file:

        public void RegisterAllControllersInFix(Assembly assembly)
    {
        RegisterAllControllersInFix(assembly, GetControllerName);
    }

    public void RegisterAllControllersInFix(Assembly assembly, Func<Type, string> namingConvention)
    {
        foreach (Type type in assembly.GetExportedTypes().Where(IsController))
            Kernel.Bind(type).ToSelf();
    }

    private static bool IsController(Type type)
    {
        return typeof(IController).IsAssignableFrom(type) && type.IsPublic && !type.IsAbstract && !type.IsInterface;
    }

    private static string GetControllerName(Type type)
    {
        string name = type.Name.ToLowerInvariant();

        if (name.EndsWith("controller"))
            name = name.Substring(0, name.IndexOf("controller"));

        return name;
    }

Then called it from my OnApplicationStarted() method as follows:

        RegisterAllControllersIn(Assembly.GetExecutingAssembly());
        RegisterAllControllersInFix(Assembly.GetExecutingAssembly());

Difficult to know whether this fixed it though because it's so intermittent.

Try something like WebClient.DownloadString("http://yoursite/") in loop,... in several threads =)
Denis Parchenko
Will do - thanks Denis
+2  A: 

I have been dealing with this problem for months. I tried so many options but was unable to come to a solution. I knew that it was a threading problem because it would only occur when there was a heavy load on my site. Just recently a bug was reported and fixed in the ninject source code that solves this problem.

Here is a reference to the issue. It was fixed in build 2.1.0.70 of the Ninject source. The key change was in KernelBase.cs by removing the line

context.Plan = planner.GetPlan(service);

and replacing it with

lock (planner)
{
    context.Plan = planner.GetPlan(service);
}

To use this new build with MVC you will need to get the latest build of Ninject then get the latest build of ninject.web.mvc. Build ninject.web.mvc with the new Ninject build.

I have been using this new build for about a week with a heavy load and no problems. That is the longest it has gone without a problem so I would consider this to be a solution.

Steve Hook
Awesome! This is big news. Been dealing with it myself. Thanks for providing this answer, Steve!
John Nelson