zcz527 / autofac

Automatically exported from code.google.com/p/autofac
Other
0 stars 0 forks source link

ExtensibleActionInvoker incorrectly holds attachment to expired lifetime scope in MVC4 #396

Closed GoogleCodeExporter closed 8 years ago

GoogleCodeExporter commented 8 years ago
Details in forum post here: 
https://groups.google.com/d/msg/autofac/_2M7hXZY8Ck/YIwXJhBVxNYJ

If you use the ExtensibleActionInvoker in its default state - not injecting 
action method parameters - things seem to work.

If, on the other hand, you add a parameter to an action method that needs to be 
resolved, you get an exception saying the current lifetime scope has already 
been disposed. Looking in the debugger, the scope is the web request scope that 
was passed in during initial IActionInvoker resolution.

This was originally discovered in handling Issue #368.

Original issue reported on code.google.com by travis.illig on 27 Nov 2012 at 12:03

GoogleCodeExporter commented 8 years ago

Original comment by travis.illig on 27 Nov 2012 at 12:03

GoogleCodeExporter commented 8 years ago
Note that, ideally, part of the solution here might include an easier way to 
register the invoker and turn on/off the action method injection. Currently 
it's sort of painful:

builder
  .RegisterType<ExtensibleActionInvoker>()
  .As<IActionInvoker>()
  .WithParameter(new NamedParameter("injectActionMethodParameters", true));

Original comment by travis.illig on 27 Nov 2012 at 12:05

GoogleCodeExporter commented 8 years ago
I added an integration test to the Remember.Web site that tests this exact 
thing.

You can enable/disable parameter injection from web.config. Access the test by 
visiting the home page and following the links.

To reproduce the behavior, enable parameter injection in web.config and visit 
the test page - you'll get the lifetime disposed exception.

Original comment by travis.illig on 27 Nov 2012 at 1:30

GoogleCodeExporter commented 8 years ago
It appears to have something to do with the async nature of the invoker. If you 
set a break point and watch as things get resolved in the GetParameterValue 
method, everything is fine. Remove your breakpoints and hit reload a couple of 
times, you'll end up with the error.

Here's the exception and call stack:

[ObjectDisposedException: Instances cannot be resolved and nested lifetimes 
cannot be created from this LifetimeScope as it has already been disposed.]
   Autofac.Core.Lifetime.LifetimeScope.CheckNotDisposed() in c:\dev\opensource\autofac\trunk\Core\Source\Autofac\Core\Lifetime\LifetimeScope.cs:327
   Autofac.Core.Lifetime.LifetimeScope.ResolveComponent(IComponentRegistration registration, IEnumerable`1 parameters) in c:\dev\opensource\autofac\trunk\Core\Source\Autofac\Core\Lifetime\LifetimeScope.cs:224
   Autofac.ResolutionExtensions.TryResolveService(IComponentContext context, Service service, IEnumerable`1 parameters, Object& instance) in c:\dev\opensource\autofac\trunk\Core\Source\Autofac\ResolutionExtensions.cs:736
   Autofac.ResolutionExtensions.ResolveOptionalService(IComponentContext context, Service service, IEnumerable`1 parameters) in c:\dev\opensource\autofac\trunk\Core\Source\Autofac\ResolutionExtensions.cs:604
   Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType, IEnumerable`1 parameters) in c:\dev\opensource\autofac\trunk\Core\Source\Autofac\ResolutionExtensions.cs:552
   Autofac.ResolutionExtensions.ResolveOptional(IComponentContext context, Type serviceType) in c:\dev\opensource\autofac\trunk\Core\Source\Autofac\ResolutionExtensions.cs:536
   Autofac.Integration.Mvc.ExtensibleActionInvoker.GetParameterValue(ControllerContext controllerContext, ParameterDescriptor parameterDescriptor) in c:\dev\opensource\autofac\trunk\Core\Source\Autofac.Integration.Mvc\ExtensibleActionInvoker.cs:135
   System.Web.Mvc.ControllerActionInvoker.GetParameterValues(ControllerContext controllerContext, ActionDescriptor actionDescriptor) +117
   System.Web.Mvc.Async.<>c__DisplayClass25.<BeginInvokeAction>b__1e(AsyncCallback asyncCallback, Object asyncState) +446
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.Async.AsyncControllerActionInvoker.BeginInvokeAction(ControllerContext controllerContext, String actionName, AsyncCallback callback, Object state) +302
   System.Web.Mvc.<>c__DisplayClass1d.<BeginExecuteCore>b__17(AsyncCallback asyncCallback, Object asyncState) +30
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.Controller.BeginExecuteCore(AsyncCallback callback, Object state) +382
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.Controller.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +317
   System.Web.Mvc.Controller.System.Web.Mvc.Async.IAsyncController.BeginExecute(RequestContext requestContext, AsyncCallback callback, Object state) +15
   System.Web.Mvc.<>c__DisplayClass8.<BeginProcessRequest>b__2(AsyncCallback asyncCallback, Object asyncState) +71
   System.Web.Mvc.Async.WrappedAsyncResult`1.Begin(AsyncCallback callback, Object state, Int32 timeout) +130
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContextBase httpContext, AsyncCallback callback, Object state) +249
   System.Web.Mvc.MvcHandler.BeginProcessRequest(HttpContext httpContext, AsyncCallback callback, Object state) +50
   System.Web.Mvc.MvcHandler.System.Web.IHttpAsyncHandler.BeginProcessRequest(HttpContext context, AsyncCallback cb, Object extraData) +16
   System.Web.CallHandlerExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +301
   System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +155

Original comment by travis.illig on 27 Nov 2012 at 10:49

GoogleCodeExporter commented 8 years ago
Looking at HttpContext.Current.Items, the lifetime scope there is actually NOT 
disposed. Only the context on the action invoker.

Both are tagged with "AutofacWebRequest" so I know they're both request scopes, 
but it looks like the invoker was created with a DIFFERENT request scope to 
determine the action filters than was being used in the resolution of action 
parameters.

Original comment by travis.illig on 27 Nov 2012 at 10:58

GoogleCodeExporter commented 8 years ago
Further investigation shows that the ExtensibleActionInvoker is actually being 
cached.

Looking at ASP.NET MVC 4 source, it appears the System.Web.Mvc.Controller class 
has started using DependencyResolver.CurrentCache internally for the way it 
resolves services.

Further looking into the DependencyResolver class, it appears this boils down 
to a new mechanism - the 
System.Web.Mvc.DependencyResolver+CacheDependencyResolver nested type, which is 
specifically there to enforce a single instance per resolved service. It 
automatically wraps any AutofacDependencyResolver (or other resolver) you set 
and caches specific instances in a type/object dictionary. For the life of the 
dependency resolver.

This is resulting in the ExtensibleActionInvoker being created on the first 
request (getting a valid component context from which to resolve dependencies) 
but having subsequent requests fail because they're not using the proper 
instance of the invoker - they're using a stale one. That actually affects 
filters, too, since fresh dependencies won't be injected.

Original comment by travis.illig on 27 Nov 2012 at 11:29

GoogleCodeExporter commented 8 years ago
Updating the title to accommodate the new findings. It's larger than just 
parameter injection.

Original comment by travis.illig on 27 Nov 2012 at 11:31

GoogleCodeExporter commented 8 years ago

Original comment by travis.illig on 3 Dec 2012 at 3:48

GoogleCodeExporter commented 8 years ago
This issue was closed by revision aac55af83e08.

Original comment by alex.meyergleaves on 9 Dec 2012 at 7:25