views:

378

answers:

2

I'm using Ninject 2 and the Ninject.Web.MVC and using the NinjectHttpApplication

Receiving the following error during the logon process: "A single instance of controller 'MySite.Controllers.AccountController' cannot be used to handle multiple requests. If a custom controller factory is in use, make sure that it creates a new instance of the controller for each request."

My global.asax has this:

 protected override void OnApplicationStarted()
    {
        RegisterRoutes(RouteTable.Routes);


        RegisterAllControllersIn(Assembly.GetExecutingAssembly());
} 
protected override IKernel CreateKernel()
        {
            return new StandardKernel(new MySite.IoCModules.FakeRepositoriesModule(), new MySite.IoCModules.AccountControllerModule());
        }

The AccountControllerModule looks like this:

 public class AccountControllerModule:Module
{
    public override void Load()
    {
        Bind<IFormsAuthentication>().To<FormsAuthenticationService>();
        Bind<IMembershipService>().To<AccountMembershipService>();
        Bind<MembershipProvider>().ToConstant(Membership.Provider);
    }
}

My guess is that it has something to do the lifecycle set during RegisterAllControllersIn...but I'm just not sure...any ideas where to go from here?

UPDATE: Just saw it happen to the HomeController too...it's gotta be trying to make a singleton out of it or something right?

A: 

Sounds like it's using a singleton, all right. See this page for documentation as to how to control activation behaviors using Ninject.

Note that transient (not singleton) is the default behavior for Ninject.

Craig Stuntz
As of this post and comment, this link references Ninject 1.x documentation. Activation scope is controlled differently in Ninject 2.
Peter Meyer
Ah. They should fix the documentation link on their home page, then.
Craig Stuntz
+2  A: 

The most recent version of Ninject.Web.Mvc is using a transient scope to register the controllers in RegisterAllControllersIn:

public void RegisterAllControllersIn(Assembly assembly, 
                                       Func<Type, string> namingConvention)
{
  foreach (Type type in assembly.GetExportedTypes().Where(IsController))
     _kernel.Bind<IController>()
        .To(type)
        .InTransientScope()
        .Named(namingConvention(type));
}

I looked into the the NinjectControllerFactory class as well. Its CreateController function is pretty basic. It does a TryGet on the kernel for the controller and returns what it gets back -- if it can't find the controller, it delegates to the base class:

public override IController CreateController(RequestContext requestContext, 
                                                    string controllerName)
{
  var controller = Kernel.TryGet<IController>(controllerName.ToLowerInvariant());

  if (controller == null)
    return base.CreateController(requestContext, controllerName);

  var standardController = controller as Controller;

  if (standardController != null)
    standardController.ActionInvoker = new NinjectActionInvoker(Kernel);

  return controller;
}

So, based on the binding setup and based on the factory, it would seem it's not creating objects in Singleton scope. One thing you could do is write a little debug code after you create your kernel and check the bindings yourself to confirm what the scope is. I did a little experiment and added the code to my HttpApplication class show below. Full disclosure, this is using ASP.Net MVC 1.0, so your mileage may vary. If I have the opportunity, I will get the latest MVC 2 preview and try the same experiment.

protected void DumpBindings() {
  var bindings = Kernel.GetBindings(typeof(IController));

  var dummyRequest = new RequestContext(
                           new HttpContextWrapper(HttpContext.Current), 
                           new RouteData());

  foreach (var binding in bindings) {
    var scope = "Custom";
    if (binding.ScopeCallback == StandardScopeCallbacks.Request)
      scope = "Request";
    else if (binding.ScopeCallback == StandardScopeCallbacks.Singleton)
      scope = "Singleton";
    else if (binding.ScopeCallback == StandardScopeCallbacks.Thread)
      scope = "Thread";
    else if (binding.ScopeCallback == StandardScopeCallbacks.Transient)
      scope = "Transient";

    HttpContext.Current.Trace.Write(
      string.Format(
        "Controller: {0} Named: {1} Scope: {2}",
        binding.Service.Name,
        binding.Metadata.Name,
        scope));
    var controllerFactory = ControllerBuilder.Current.GetControllerFactory();

    var controller1 = controllerFactory.CreateController(
                               dummyRequest, binding.Metadata.Name);
    var controller2 = controllerFactory.CreateController(
                               dummyRequest, binding.Metadata.Name);

    HttpContext.Current.Trace.Write(
      string.Format(
        "{0} controller1 == {0} controller2 ? {1}",
        binding.Metadata.Name,
        object.Equals(controller1, controller2)));
  }
}

I called this right after the call to RegisterAllControllersIn in the OnApplicationStarted. It created the following messages in the trace output:

Controller: IController Named: home
Scope: Transient home controller1
== home controller2 ? False Controller: IController Named: account
Scope: Transient account controller1
== account controller2 ? False

So, all this does is confirm that transient scope is being used and that the controller factory is returning a different instance of the same controller when requested. So, the only thing I can think of is that:

  1. Perhaps you are not using the latest builds of Ninject 2 and Ninject.Web.Mvc
  2. The issue is at the MVC level -- i.e. it's reusing the controller created by the factory
Peter Meyer
I was just coming back to update that the build I had was old...it came from the Experimental branch of the previous source control...had no idea it was on Git...sigh... New build fixed everything...as it almost always does. :-)
Webjedi
Yeah, I played around quite a while with that one experimental branch until I found it on github, too. Good luck!
Peter Meyer