Error with AccountController and Ninject 2 and ASP.NET MVC 2 Preview 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<Forms开发者_如何学JAVAAuthenticationService>();
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?
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:
- Perhaps you are not using the latest builds of Ninject 2 and Ninject.Web.Mvc
- The issue is at the MVC level -- i.e. it's reusing the controller created by the factory
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.
精彩评论