diff --git a/samples/MvcSandbox/Startup.cs b/samples/MvcSandbox/Startup.cs index 9658311044..2602d9baf9 100644 --- a/samples/MvcSandbox/Startup.cs +++ b/samples/MvcSandbox/Startup.cs @@ -2,10 +2,12 @@ // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System.IO; +using Microsoft.AspNetCore.Antiforgery; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Options; namespace MvcSandbox { @@ -15,6 +17,10 @@ namespace MvcSandbox public void ConfigureServices(IServiceCollection services) { services.AddMvc(); + + services.Insert(0, ServiceDescriptor.Singleton( + typeof(IConfigureOptions), + new ConfigureOptions(options => options.CookieName = ""))); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs index 404c9267eb..3cf7d0003d 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -33,6 +33,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal // https://blogs.msdn.microsoft.com/ericlippert/2008/05/14/mutating-readonly-structs/ private FilterCursor _cursor; private object _controller; + private Dictionary _arguments; + private IActionResult _result; private AuthorizationFilterContext _authorizationContext; @@ -124,48 +126,53 @@ namespace Microsoft.AspNetCore.Mvc.Internal var startTimestamp = _logger.IsEnabled(LogLevel.Information) ? Stopwatch.GetTimestamp() : 0; - await InvokeAllAuthorizationFiltersAsync(); + // The invoker is implemented using a 'Taskerator' or perhaps an 'Asyncerator' (both terms are correct + // and in common usage). This method is the main 'driver' loop and will call into the `Next` method + // (`await`ing the result) until a terminal state is reached. + // + // The `Next` method walks through the state transitions of the invoker and returns a `Task` when there's + // actual async work that we need to await. As an optimization that Next method won't return a `Task` + // that completes synchronously. + // + // Additionally the `Next` funtion will be called recursively when we're 'inside' a filter invocation. + // Executing 'inside' a filter requires an async method call within a `try`/`catch` for error handling, so + // we have to recurse. Each 'frame' calls into `Next` with a value of `Scope` that communicates what kind + // of 'frame' is executing. This has an effect on the state machine transitions as well as what kinds of + // contexts need to be constructed to communicate the result of execution of the 'frame'. - // If Authorization Filters return a result, it's a short circuit because - // authorization failed. We don't execute Result Filters around the result. - Debug.Assert(_authorizationContext != null); - if (_authorizationContext.Result != null) - { - await InvokeResultAsync(_authorizationContext.Result); - return; - } + // When returning, the `Next` method will set `next` to the state to goto on the subsequent invocation. + // This is similar to `Task.ContinueWith`, but since we have a fixed number of states we can avoid + // the overhead of actually using `Task.ContinueWith`. + var next = State.InvokeBegin; + + // The `scope` tells the `Next` method who the caller is, and what kind of state to initialize to + // communicate a result. The outermost scope is `Scope.Invoker` and doesn't require any type + // of context or result other than throwing. + var scope = Scope.Invoker; + + // The `state` is used for internal state handling during transitions between states. In practice this + // means storing a filter instance in `state` and then retrieving it in the next state. + var state = (object)null; + + // `isCompleted` will be set to true when we've reached a terminal state. + var isCompleted = false; try { - await InvokeAllResourceFiltersAsync(); + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } } finally { - // Release the instance after all filters have run. We don't need to surround - // Authorizations filters because the instance will be created much later than - // that. if (_controller != null) { _controllerFactory.ReleaseController(_controllerContext, _controller); } - } - // We've reached the end of resource filters. If there's an unhandled exception on the context then - // it should be thrown and middleware has a chance to handle it. - Debug.Assert(_resourceExecutedContext != null); - if (_resourceExecutedContext.Exception != null && !_resourceExecutedContext.ExceptionHandled) - { - if (_resourceExecutedContext.ExceptionDispatchInfo == null) - { - throw _resourceExecutedContext.Exception; - } - else - { - _resourceExecutedContext.ExceptionDispatchInfo.Throw(); - } + _logger.ExecutedAction(_controllerContext.ActionDescriptor, startTimestamp); } - - _logger.ExecutedAction(_controllerContext.ActionDescriptor, startTimestamp); } } finally @@ -177,77 +184,938 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private Task InvokeAllAuthorizationFiltersAsync() + private Task Next(ref State next, ref Scope scope, ref object state, ref bool isCompleted) { - _cursor.Reset(); + var diagnosticSource = _diagnosticSource; + var logger = _logger; - _authorizationContext = new AuthorizationFilterContext(_controllerContext, _filters); - return InvokeAuthorizationFilterAsync(); - } - - private async Task InvokeAuthorizationFilterAsync() - { - // We should never get here if we already have a result. - Debug.Assert(_authorizationContext != null); - Debug.Assert(_authorizationContext.Result == null); - - var current = _cursor.GetNextFilter(); - if (current.FilterAsync != null) + switch (next) { - _diagnosticSource.BeforeOnAuthorizationAsync(_authorizationContext, current.FilterAsync); + case State.InvokeBegin: + { + goto case State.AuthorizationBegin; + } - await current.FilterAsync.OnAuthorizationAsync(_authorizationContext); + case State.AuthorizationBegin: + { + _cursor.Reset(); + goto case State.AuthorizationNext; + } - _diagnosticSource.AfterOnAuthorizationAsync(_authorizationContext, current.FilterAsync); + case State.AuthorizationNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_authorizationContext == null) + { + _authorizationContext = new AuthorizationFilterContext(_controllerContext, _filters); + } - if (_authorizationContext.Result == null) - { - // Only keep going if we don't have a result - await InvokeAuthorizationFilterAsync(); - } - else - { - _logger.AuthorizationFailure(current.FilterAsync); - } - } - else if (current.Filter != null) - { - _diagnosticSource.BeforeOnAuthorization(_authorizationContext, current.Filter); + state = current.FilterAsync; + goto case State.AuthorizationAsyncBegin; + } + else if (current.Filter != null) + { + if (_authorizationContext == null) + { + _authorizationContext = new AuthorizationFilterContext(_controllerContext, _filters); + } - current.Filter.OnAuthorization(_authorizationContext); + state = current.Filter; + goto case State.AuthorizationSync; + } + else + { + goto case State.AuthorizationEnd; + } + } - _diagnosticSource.AfterOnAuthorization(_authorizationContext, current.Filter); + case State.AuthorizationAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); - if (_authorizationContext.Result == null) - { - // Only keep going if we don't have a result - await InvokeAuthorizationFilterAsync(); - } - else - { - _logger.AuthorizationFailure(current.Filter); - } - } - else - { - // We've run out of Authorization Filters - if we haven't short circuited by now then this - // request is authorized. + var filter = (IAsyncAuthorizationFilter)state; + var authorizationContext = _authorizationContext; + + _diagnosticSource.BeforeOnAuthorizationAsync(authorizationContext, filter); + + var task = filter.OnAuthorizationAsync(authorizationContext); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.AuthorizationAsyncEnd; + return task; + } + + goto case State.AuthorizationAsyncEnd; + } + + case State.AuthorizationAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); + + var filter = (IAsyncAuthorizationFilter)state; + var authorizationContext = _authorizationContext; + + _diagnosticSource.AfterOnAuthorizationAsync(authorizationContext, filter); + + if (authorizationContext.Result != null) + { + goto case State.AuthorizationShortCircuit; + } + + goto case State.AuthorizationNext; + } + + case State.AuthorizationSync: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); + + var filter = (IAuthorizationFilter)state; + var authorizationContext = _authorizationContext; + + _diagnosticSource.BeforeOnAuthorization(authorizationContext, filter); + + filter.OnAuthorization(authorizationContext); + + _diagnosticSource.AfterOnAuthorization(authorizationContext, filter); + + if (authorizationContext.Result != null) + { + goto case State.AuthorizationShortCircuit; + } + + goto case State.AuthorizationNext; + } + + case State.AuthorizationShortCircuit: + { + Debug.Assert(state != null); + Debug.Assert(_authorizationContext != null); + + _logger.AuthorizationFailure((IFilterMetadata)state); + + // If an authorization filter short circuits, the result is the last thing we execute + // so just return that task instead of calling back into the state machine. + isCompleted = true; + return InvokeResultAsync(_authorizationContext.Result); + } + + case State.AuthorizationEnd: + { + goto case State.ResourceBegin; + } + + case State.ResourceBegin: + { + _cursor.Reset(); + goto case State.ResourceNext; + } + + case State.ResourceNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_resourceExecutingContext == null) + { + _resourceExecutingContext = new ResourceExecutingContext( + _controllerContext, + _filters, + _controllerContext.ValueProviderFactories); + } + + state = current.FilterAsync; + goto case State.ResourceAsyncBegin; + } + else if (current.Filter != null) + { + if (_resourceExecutingContext == null) + { + _resourceExecutingContext = new ResourceExecutingContext( + _controllerContext, + _filters, + _controllerContext.ValueProviderFactories); + } + + state = current.Filter; + goto case State.ResourceSyncBegin; + } + else if (scope == Scope.Resource) + { + // All resource filters are currently on the stack - now execute the 'inside'. + Debug.Assert(_resourceExecutingContext != null); + goto case State.ResourceInside; + } + else + { + // There are no resource filters - so jump right to 'inside'. + Debug.Assert(scope == Scope.Invoker); + goto case State.ExceptionBegin; + } + } + + case State.ResourceAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + + var filter = (IAsyncResourceFilter)state; + var resourceExecutingContext = _resourceExecutingContext; + + _diagnosticSource.BeforeOnResourceExecution(resourceExecutingContext, filter); + + var task = filter.OnResourceExecutionAsync(resourceExecutingContext, InvokeNextResourceFilterAwaitedAsync); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceAsyncEnd; + return task; + } + + goto case State.ResourceAsyncEnd; + } + + case State.ResourceAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + + var filter = (IAsyncResourceFilter)state; + if (_resourceExecutedContext == null) + { + // If we get here then the filter didn't call 'next' indicating a short circuit. + _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) + { + Canceled = true, + Result = _resourceExecutingContext.Result, + }; + } + + _diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter); + + if (_resourceExecutingContext.Result != null) + { + goto case State.ResourceShortCircuit; + } + + goto case State.ResourceEnd; + } + + case State.ResourceSyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + + var filter = (IResourceFilter)state; + var resourceExecutingContext = _resourceExecutingContext; + + _diagnosticSource.BeforeOnResourceExecuting(resourceExecutingContext, filter); + + filter.OnResourceExecuting(resourceExecutingContext); + + _diagnosticSource.AfterOnResourceExecuting(resourceExecutingContext, filter); + + if (resourceExecutingContext.Result != null) + { + _resourceExecutedContext = new ResourceExecutedContext(resourceExecutingContext, _filters) + { + Canceled = true, + Result = _resourceExecutingContext.Result, + }; + + goto case State.ResourceShortCircuit; + } + + var task = InvokeNextResourceFilter(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceSyncEnd; + return task; + } + + goto case State.ResourceSyncEnd; + } + + case State.ResourceSyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + Debug.Assert(_resourceExecutedContext != null); + + var filter = (IResourceFilter)state; + var resourceExecutedContext = _resourceExecutedContext; + + _diagnosticSource.BeforeOnResourceExecuted(resourceExecutedContext, filter); + + filter.OnResourceExecuted(resourceExecutedContext); + + _diagnosticSource.AfterOnResourceExecuted(resourceExecutedContext, filter); + + goto case State.ResourceEnd; + } + + case State.ResourceShortCircuit: + { + Debug.Assert(state != null); + Debug.Assert(_resourceExecutingContext != null); + Debug.Assert(_resourceExecutedContext != null); + + _logger.ResourceFilterShortCircuited((IFilterMetadata)state); + + var task = InvokeResultAsync(_resourceExecutingContext.Result); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceEnd; + return task; + } + + goto case State.ResourceEnd; + } + + case State.ResourceInside: + { + goto case State.ExceptionBegin; + } + + case State.ResourceEnd: + { + if (scope == Scope.Resource) + { + isCompleted = true; + return TaskCache.CompletedTask; + } + + Debug.Assert(scope == Scope.Invoker); + Rethrow(_resourceExecutedContext); + + goto case State.InvokeEnd; + } + + case State.ExceptionBegin: + { + _cursor.Reset(); + goto case State.ExceptionNext; + } + + case State.ExceptionNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + state = current.FilterAsync; + goto case State.ExceptionAsyncBegin; + } + else if (current.Filter != null) + { + state = current.Filter; + goto case State.ExceptionSyncBegin; + } + else if (scope == Scope.Exception) + { + // All exception filters are on the stack already - so execute the 'inside'. + goto case State.ExceptionInside; + } + else + { + // There are no exception filters - so jump right to 'inside'. + Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource); + goto case State.ActionBegin; + } + } + + case State.ExceptionAsyncBegin: + { + var task = InvokeNextExceptionFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ExceptionAsyncResume; + return task; + } + + goto case State.ExceptionAsyncResume; + } + + case State.ExceptionAsyncResume: + { + Debug.Assert(state != null); + + var filter = (IAsyncExceptionFilter)state; + var exceptionContext = _exceptionContext; + + // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception, + // we'll call the filter. Otherwise there's nothing to do. + if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled) + { + _diagnosticSource.BeforeOnExceptionAsync(exceptionContext, filter); + + var task = filter.OnExceptionAsync(exceptionContext); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ExceptionAsyncEnd; + return task; + } + + goto case State.ExceptionAsyncEnd; + } + + goto case State.ExceptionEnd; + } + + case State.ExceptionAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_exceptionContext != null); + + var filter = (IAsyncExceptionFilter)state; + var exceptionContext = _exceptionContext; + + _diagnosticSource.AfterOnExceptionAsync(exceptionContext, filter); + + if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled) + { + _logger.ExceptionFilterShortCircuited(filter); + } + + goto case State.ExceptionEnd; + } + + case State.ExceptionSyncBegin: + { + var task = InvokeNextExceptionFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ExceptionSyncEnd; + return task; + } + + goto case State.ExceptionSyncEnd; + } + + case State.ExceptionSyncEnd: + { + Debug.Assert(state != null); + + var filter = (IExceptionFilter)state; + var exceptionContext = _exceptionContext; + + // When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception, + // we'll call the filter. Otherwise there's nothing to do. + if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled) + { + _diagnosticSource.BeforeOnException(exceptionContext, filter); + + filter.OnException(exceptionContext); + + _diagnosticSource.AfterOnException(exceptionContext, filter); + + if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled) + { + _logger.ExceptionFilterShortCircuited(filter); + } + } + + goto case State.ExceptionEnd; + } + + case State.ExceptionInside: + { + goto case State.ActionBegin; + } + + case State.ExceptionShortCircuit: + { + Debug.Assert(state != null); + Debug.Assert(_exceptionContext != null); + + Task task; + if (scope == Scope.Resource) + { + Debug.Assert(_exceptionContext.Result != null); + _resourceExecutedContext = new ResourceExecutedContext(_controllerContext, _filters) + { + Result = _exceptionContext.Result, + }; + + task = InvokeResultAsync(_exceptionContext.Result); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceEnd; + return task; + } + + goto case State.ResourceEnd; + } + + task = InvokeResultAsync(_exceptionContext.Result); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.InvokeEnd; + return task; + } + + goto case State.ResourceEnd; + } + + case State.ExceptionEnd: + { + var exceptionContext = _exceptionContext; + + if (scope == Scope.Exception) + { + isCompleted = true; + return TaskCache.CompletedTask; + } + + if (exceptionContext != null) + { + if (exceptionContext.Result != null && !exceptionContext.ExceptionHandled) + { + goto case State.ExceptionShortCircuit; + } + + Rethrow(exceptionContext); + } + + goto case State.ResultBegin; + } + + case State.ActionBegin: + { + var controllerContext = _controllerContext; + + _cursor.Reset(); + + _controller = _controllerFactory.CreateController(controllerContext); + + _arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); + var task = _controllerArgumentBinder.BindArgumentsAsync(controllerContext, _controller, _arguments); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionNext; + return task; + } + + goto case State.ActionNext; + } + + case State.ActionNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_actionExecutingContext == null) + { + _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller); + } + + state = current.FilterAsync; + goto case State.ActionAsyncBegin; + } + else if (current.Filter != null) + { + if (_actionExecutingContext == null) + { + _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller); + } + + state = current.Filter; + goto case State.ActionSyncBegin; + } + else + { + goto case State.ActionInside; + } + } + + case State.ActionAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + + var filter = (IAsyncActionFilter)state; + var actionExecutingContext = _actionExecutingContext; + + _diagnosticSource.BeforeOnActionExecution(actionExecutingContext, filter); + + var task = filter.OnActionExecutionAsync(actionExecutingContext, InvokeNextActionFilterAwaitedAsync); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionAsyncEnd; + return task; + } + + goto case State.ActionAsyncEnd; + } + + case State.ActionAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + + var filter = (IAsyncActionFilter)state; + + if (_actionExecutedContext == null) + { + // If we get here then the filter didn't call 'next' indicating a short circuit. + _logger.ActionFilterShortCircuited(filter); + + _actionExecutedContext = new ActionExecutedContext( + _controllerContext, + _filters, + _controller) + { + Canceled = true, + Result = _actionExecutingContext.Result, + }; + } + + _diagnosticSource.AfterOnActionExecution(_actionExecutedContext, filter); + + goto case State.ActionEnd; + } + + case State.ActionSyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + + var filter = (IActionFilter)state; + var actionExecutingContext = _actionExecutingContext; + + _diagnosticSource.BeforeOnActionExecuting(actionExecutingContext, filter); + + filter.OnActionExecuting(actionExecutingContext); + + _diagnosticSource.AfterOnActionExecuting(actionExecutingContext, filter); + + if (actionExecutingContext.Result != null) + { + // Short-circuited by setting a result. + _logger.ActionFilterShortCircuited(filter); + + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + _controller) + { + Canceled = true, + Result = _actionExecutingContext.Result, + }; + + goto case State.ActionEnd; + } + + var task = InvokeNextActionFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionSyncEnd; + return task; + } + + goto case State.ActionSyncEnd; + } + + case State.ActionSyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_actionExecutingContext != null); + Debug.Assert(_actionExecutedContext != null); + + var filter = (IActionFilter)state; + var actionExecutedContext = _actionExecutedContext; + + _diagnosticSource.BeforeOnActionExecuted(actionExecutedContext, filter); + + filter.OnActionExecuted(actionExecutedContext); + + _diagnosticSource.BeforeOnActionExecuted(actionExecutedContext, filter); + + goto case State.ActionEnd; + } + + case State.ActionInside: + { + var task = InvokeActionMethodAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionEnd; + return task; + } + + goto case State.ActionEnd; + } + + case State.ActionEnd: + { + if (scope == Scope.Action) + { + if (_actionExecutedContext == null) + { + _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller) + { + Result = _result, + }; + } + + isCompleted = true; + return TaskCache.CompletedTask; + } + + var actionExecutedContext = _actionExecutedContext; + Rethrow(actionExecutedContext); + + if (actionExecutedContext != null) + { + _result = actionExecutedContext.Result; + } + + if (scope == Scope.Exception) + { + // If we're inside an exception filter, let's allow those filters to 'unwind' before + // the result. + isCompleted = true; + return TaskCache.CompletedTask; + } + + Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource); + goto case State.ResultBegin; + } + + case State.ResultBegin: + { + _cursor.Reset(); + goto case State.ResultNext; + } + + case State.ResultNext: + { + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) + { + if (_resultExecutingContext == null) + { + _resultExecutingContext = new ResultExecutingContext(_controllerContext, _filters, _result, _controller); + } + + state = current.FilterAsync; + goto case State.ResultAsyncBegin; + } + else if (current.Filter != null) + { + if (_resultExecutingContext == null) + { + _resultExecutingContext = new ResultExecutingContext(_controllerContext, _filters, _result, _controller); + } + + state = current.Filter; + goto case State.ResultSyncBegin; + } + else + { + goto case State.ResultInside; + } + } + + case State.ResultAsyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + + var filter = (IAsyncResultFilter)state; + var resultExecutingContext = _resultExecutingContext; + + _diagnosticSource.BeforeOnResultExecution(resultExecutingContext, filter); + + var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResultAsyncEnd; + return task; + } + + goto case State.ResultAsyncEnd; + } + + case State.ResultAsyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + + var filter = (IAsyncResultFilter)state; + var resultExecutingContext = _resultExecutingContext; + var resultExecutedContext = _resultExecutedContext; + + if (resultExecutedContext == null || resultExecutingContext.Cancel == true) + { + // Short-circuited by not calling next || Short-circuited by setting Cancel == true + _logger.ResourceFilterShortCircuited(filter); + + _resultExecutedContext = new ResultExecutedContext( + _controllerContext, + _filters, + resultExecutingContext.Result, + _controller) + { + Canceled = true, + }; + } + + _diagnosticSource.AfterOnResultExecution(_resultExecutedContext, filter); + goto case State.ResultEnd; + } + + case State.ResultSyncBegin: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + + var filter = (IResultFilter)state; + var resultExecutingContext = _resultExecutingContext; + + _diagnosticSource.BeforeOnResultExecuting(resultExecutingContext, filter); + + filter.OnResultExecuting(resultExecutingContext); + + _diagnosticSource.AfterOnResultExecuting(resultExecutingContext, filter); + + if (_resultExecutingContext.Cancel == true) + { + // Short-circuited by setting Cancel == true + _logger.ResourceFilterShortCircuited(filter); + + _resultExecutedContext = new ResultExecutedContext( + resultExecutingContext, + _filters, + resultExecutingContext.Result, + _controller) + { + Canceled = true, + }; + + goto case State.ResultEnd; + } + + var task = InvokeNextResultFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResultSyncEnd; + return task; + } + + goto case State.ResultSyncEnd; + } + + case State.ResultSyncEnd: + { + Debug.Assert(state != null); + Debug.Assert(_resultExecutingContext != null); + Debug.Assert(_resultExecutedContext != null); + + var filter = (IResultFilter)state; + var resultExecutedContext = _resultExecutedContext; + + _diagnosticSource.BeforeOnResultExecuted(resultExecutedContext, filter); + + filter.OnResultExecuted(resultExecutedContext); + + _diagnosticSource.AfterOnResultExecuted(resultExecutedContext, filter); + + goto case State.ResultEnd; + } + + case State.ResultInside: + { + // If we executed result filters then we need to grab the result from there. + if (_resultExecutingContext != null) + { + _result = _resultExecutingContext.Result; + } + + if (_result == null) + { + // The empty result is always flowed back as the 'executed' result if we don't have one. + _result = new EmptyResult(); + } + + var task = InvokeResultAsync(_result); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResultEnd; + return task; + } + + goto case State.ResultEnd; + } + + case State.ResultEnd: + { + var result = _result; + + if (scope == Scope.Result) + { + if (_resultExecutedContext == null) + { + _resultExecutedContext = new ResultExecutedContext(_controllerContext, _filters, result, _controller); + } + + isCompleted = true; + return TaskCache.CompletedTask; + } + + Rethrow(_resultExecutedContext); + + if (scope == Scope.Resource) + { + _resourceExecutedContext = new ResourceExecutedContext(_controllerContext, _filters) + { + Result = result, + }; + + goto case State.ResourceEnd; + } + + goto case State.InvokeEnd; + } + + case State.InvokeEnd: + { + isCompleted = true; + return TaskCache.CompletedTask; + } + + default: + throw new InvalidOperationException(); } } - private Task InvokeAllResourceFiltersAsync() + private async Task InvokeNextResourceFilter() { - _cursor.Reset(); + try + { + var next = State.ResourceNext; + var state = (object)null; + var scope = Scope.Resource; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } - _resourceExecutingContext = new ResourceExecutingContext( - _controllerContext, - _filters, - _controllerContext.ValueProviderFactories); - - return InvokeResourceFilterAsync(); + Debug.Assert(_resourceExecutedContext != null); } - private async Task InvokeResourceFilterAwaitedAsync() + private async Task InvokeNextResourceFilterAwaitedAsync() { Debug.Assert(_resourceExecutingContext != null); @@ -259,243 +1127,62 @@ namespace Microsoft.AspNetCore.Mvc.Internal nameof(ResourceExecutingContext.Result), typeof(ResourceExecutingContext).Name, typeof(ResourceExecutionDelegate).Name); - throw new InvalidOperationException(message); } - await InvokeResourceFilterAsync(); + await InvokeNextResourceFilter(); Debug.Assert(_resourceExecutedContext != null); return _resourceExecutedContext; } - private async Task InvokeResourceFilterAsync() + private async Task InvokeNextExceptionFilterAsync() { - Debug.Assert(_resourceExecutingContext != null); - - var item = _cursor.GetNextFilter(); try { - if (item.FilterAsync != null) + var next = State.ExceptionNext; + var state = (object)null; + var scope = Scope.Exception; + var isCompleted = false; + while (!isCompleted) { - _diagnosticSource.BeforeOnResourceExecution(_resourceExecutingContext, item.FilterAsync); - - await item.FilterAsync.OnResourceExecutionAsync(_resourceExecutingContext, InvokeResourceFilterAwaitedAsync); - - if (_resourceExecutedContext == null) - { - // If we get here then the filter didn't call 'next' indicating a short circuit - _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) - { - Canceled = true, - Result = _resourceExecutingContext.Result, - }; - } - - _diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, item.FilterAsync); - - if (_resourceExecutingContext.Result != null) - { - _logger.ResourceFilterShortCircuited(item.FilterAsync); - - await InvokeResultAsync(_resourceExecutingContext.Result); - } - } - else if (item.Filter != null) - { - _diagnosticSource.BeforeOnResourceExecuting(_resourceExecutingContext, item.Filter); - - item.Filter.OnResourceExecuting(_resourceExecutingContext); - - _diagnosticSource.AfterOnResourceExecuting(_resourceExecutingContext, item.Filter); - - if (_resourceExecutingContext.Result != null) - { - // Short-circuited by setting a result. - _logger.ResourceFilterShortCircuited(item.Filter); - - await InvokeResultAsync(_resourceExecutingContext.Result); - - _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) - { - Canceled = true, - Result = _resourceExecutingContext.Result, - }; - } - else - { - await InvokeResourceFilterAsync(); - Debug.Assert(_resourceExecutedContext != null); - - _diagnosticSource.BeforeOnResourceExecuted(_resourceExecutedContext, item.Filter); - - item.Filter.OnResourceExecuted(_resourceExecutedContext); - - _diagnosticSource.AfterOnResourceExecuted(_resourceExecutedContext, item.Filter); - } - } - else - { - // >> ExceptionFilters >> Model Binding >> ActionFilters >> Action - await InvokeAllExceptionFiltersAsync(); - - // If Exception Filters provide a result, it's a short-circuit due to an exception. - // We don't execute Result Filters around the result. - Debug.Assert(_exceptionContext != null); - if (_exceptionContext.Result != null) - { - // This means that exception filters returned a result to 'handle' an error. - // We're not interested in seeing the exception details since it was handled. - await InvokeResultAsync(_exceptionContext.Result); - - _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) - { - Result = _exceptionContext.Result, - }; - } - else if (_exceptionContext.Exception != null && !_exceptionContext.ExceptionHandled) - { - // If we get here, this means that we have an unhandled exception. - // Exception filted didn't handle this, so send it on to resource filters. - _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters); - - // Preserve the stack trace if possible. - _resourceExecutedContext.Exception = _exceptionContext.Exception; - if (_exceptionContext.ExceptionDispatchInfo != null) - { - _resourceExecutedContext.ExceptionDispatchInfo = _exceptionContext.ExceptionDispatchInfo; - } - } - else - { - // We have a successful 'result' from the action or an Action Filter, so run - // Result Filters. - Debug.Assert(_actionExecutedContext != null); - var result = _actionExecutedContext.Result; - - // >> ResultFilters >> (Result) - await InvokeAllResultFiltersAsync(result); - - _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) - { - Result = _resultExecutedContext.Result, - }; - } + await Next(ref next, ref scope, ref state, ref isCompleted); } } catch (Exception exception) { - _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) + _exceptionContext = new ExceptionContext(_controllerContext, _filters) { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + } + + private async Task InvokeNextActionFilterAsync() + { + try + { + var next = State.ActionNext; + var state = (object)null; + var scope = Scope.Action; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), }; } - Debug.Assert(_resourceExecutedContext != null); + Debug.Assert(_actionExecutedContext != null); } - private Task InvokeAllExceptionFiltersAsync() - { - _cursor.Reset(); - - return InvokeExceptionFilterAsync(); - } - - private async Task InvokeExceptionFilterAsync() - { - var current = _cursor.GetNextFilter(); - if (current.FilterAsync != null) - { - // Exception filters run "on the way out" - so the filter is run after the rest of the - // pipeline. - await InvokeExceptionFilterAsync(); - - Debug.Assert(_exceptionContext != null); - if (_exceptionContext.Exception != null && !_exceptionContext.ExceptionHandled) - { - _diagnosticSource.BeforeOnExceptionAsync(_exceptionContext, current.FilterAsync); - - // Exception filters only run when there's an exception - unsetting it will short-circuit - // other exception filters. - await current.FilterAsync.OnExceptionAsync(_exceptionContext); - - _diagnosticSource.AfterOnExceptionAsync(_exceptionContext, current.FilterAsync); - - if (_exceptionContext.Exception == null || _exceptionContext.ExceptionHandled) - { - _logger.ExceptionFilterShortCircuited(current.FilterAsync); - } - } - } - else if (current.Filter != null) - { - // Exception filters run "on the way out" - so the filter is run after the rest of the - // pipeline. - await InvokeExceptionFilterAsync(); - - Debug.Assert(_exceptionContext != null); - if (_exceptionContext.Exception != null && !_exceptionContext.ExceptionHandled) - { - _diagnosticSource.BeforeOnException(_exceptionContext, current.Filter); - - // Exception filters only run when there's an exception - unsetting it will short-circuit - // other exception filters. - current.Filter.OnException(_exceptionContext); - - _diagnosticSource.AfterOnException(_exceptionContext, current.Filter); - - if (_exceptionContext.Exception == null || _exceptionContext.ExceptionHandled) - { - _logger.ExceptionFilterShortCircuited(current.Filter); - } - } - } - else - { - // We've reached the 'end' of the exception filter pipeline - this means that one stack frame has - // been built for each exception. When we return from here, these frames will either: - // - // 1) Call the filter (if we have an exception) - // 2) No-op (if we don't have an exception) - Debug.Assert(_exceptionContext == null); - _exceptionContext = new ExceptionContext(_controllerContext, _filters); - - try - { - await InvokeAllActionFiltersAsync(); - - // Action filters might 'return' an unhandled exception instead of throwing - Debug.Assert(_actionExecutedContext != null); - if (_actionExecutedContext.Exception != null && !_actionExecutedContext.ExceptionHandled) - { - _exceptionContext.Exception = _actionExecutedContext.Exception; - if (_actionExecutedContext.ExceptionDispatchInfo != null) - { - _exceptionContext.ExceptionDispatchInfo = _actionExecutedContext.ExceptionDispatchInfo; - } - } - } - catch (Exception exception) - { - _exceptionContext.ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception); - } - } - } - - private async Task InvokeAllActionFiltersAsync() - { - _cursor.Reset(); - - _controller = _controllerFactory.CreateController(_controllerContext); - - var arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); - await _controllerArgumentBinder.BindArgumentsAsync(_controllerContext, _controller, arguments); - _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, arguments, _controller); - - await InvokeActionFilterAsync(); - } - - private async Task InvokeActionFilterAwaitedAsync() + private async Task InvokeNextActionFilterAwaitedAsync() { Debug.Assert(_actionExecutingContext != null); if (_actionExecutingContext.Result != null) @@ -510,219 +1197,133 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new InvalidOperationException(message); } - await InvokeActionFilterAsync(); + await InvokeNextActionFilterAsync(); Debug.Assert(_actionExecutedContext != null); return _actionExecutedContext; } - private async Task InvokeActionFilterAsync() + private async Task InvokeActionMethodAsync() { - Debug.Assert(_actionExecutingContext != null); + var controllerContext = _controllerContext; + var executor = _executor; + var controller = _controller; + var arguments = _arguments; + var orderedArguments = ControllerActionExecutor.PrepareArguments(arguments, executor); - var item = _cursor.GetNextFilter(); + var diagnosticSource = _diagnosticSource; + var logger = _logger; + + IActionResult result = null; try { - if (item.FilterAsync != null) + diagnosticSource.BeforeActionMethod( + controllerContext, + arguments, + controller); + logger.ActionMethodExecuting(controllerContext, orderedArguments); + + var returnType = executor.MethodReturnType; + if (returnType == typeof(void)) { - _diagnosticSource.BeforeOnActionExecution(_actionExecutingContext, item.FilterAsync); - - await item.FilterAsync.OnActionExecutionAsync(_actionExecutingContext, InvokeActionFilterAwaitedAsync); - - if (_actionExecutedContext == null) - { - // If we get here then the filter didn't call 'next' indicating a short circuit - _logger.ActionFilterShortCircuited(item.FilterAsync); - - _actionExecutedContext = new ActionExecutedContext( - _actionExecutingContext, - _filters, - _controller) - { - Canceled = true, - Result = _actionExecutingContext.Result, - }; - } - - _diagnosticSource.AfterOnActionExecution(_actionExecutedContext, item.FilterAsync); + executor.Execute(controller, orderedArguments); + result = new EmptyResult(); } - else if (item.Filter != null) + else if (returnType == typeof(Task)) { - _diagnosticSource.BeforeOnActionExecuting(_actionExecutingContext, item.Filter); - - item.Filter.OnActionExecuting(_actionExecutingContext); - - _diagnosticSource.AfterOnActionExecuting(_actionExecutingContext, item.Filter); - - if (_actionExecutingContext.Result != null) + await (Task)executor.Execute(controller, orderedArguments); + result = new EmptyResult(); + } + else if (executor.TaskGenericType == typeof(IActionResult)) + { + result = await (Task)executor.Execute(controller, orderedArguments); + if (result == null) { - // Short-circuited by setting a result. - _logger.ActionFilterShortCircuited(item.Filter); - - _actionExecutedContext = new ActionExecutedContext( - _actionExecutingContext, - _filters, - _controller) - { - Canceled = true, - Result = _actionExecutingContext.Result, - }; + throw new InvalidOperationException( + Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult))); + } + } + else if (executor.IsTypeAssignableFromIActionResult) + { + if (_executor.IsMethodAsync) + { + result = (IActionResult)await _executor.ExecuteAsync(controller, orderedArguments); } else { - await InvokeActionFilterAsync(); - Debug.Assert(_actionExecutedContext != null); - - _diagnosticSource.BeforeOnActionExecuted(_actionExecutedContext, item.Filter); - - item.Filter.OnActionExecuted(_actionExecutedContext); - - _diagnosticSource.BeforeOnActionExecuted(_actionExecutedContext, item.Filter); + result = (IActionResult)_executor.Execute(controller, orderedArguments); } + + if (result == null) + { + throw new InvalidOperationException( + Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType)); + } + } + else if (!executor.IsMethodAsync) + { + var resultAsObject = executor.Execute(controller, orderedArguments); + result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) + { + DeclaredType = returnType, + }; + } + else if (executor.TaskGenericType != null) + { + var resultAsObject = await executor.ExecuteAsync(controller, orderedArguments); + result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) + { + DeclaredType = executor.TaskGenericType, + }; } else { - // All action filters have run, execute the action method. - IActionResult result = null; + // This will be the case for types which have derived from Task and Task or non Task types. + throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance( + executor.MethodInfo.Name, + executor.MethodInfo.DeclaringType)); + } - try - { - _diagnosticSource.BeforeActionMethod( - _controllerContext, - _actionExecutingContext.ActionArguments, - _actionExecutingContext.Controller); + _result = result; + logger.ActionMethodExecuted(controllerContext, result); + } + finally + { + diagnosticSource.AfterActionMethod( + controllerContext, + arguments, + controllerContext, + result); + } + } - var actionMethodInfo = _controllerContext.ActionDescriptor.MethodInfo; - - var arguments = ControllerActionExecutor.PrepareArguments( - _actionExecutingContext.ActionArguments, - _executor); - - _logger.ActionMethodExecuting(_actionExecutingContext, arguments); - - var returnType = _executor.MethodReturnType; - - if (returnType == typeof(void)) - { - _executor.Execute(_controller, arguments); - result = new EmptyResult(); - } - else if (returnType == typeof(Task)) - { - await (Task)_executor.Execute(_controller, arguments); - result = new EmptyResult(); - } - else if (_executor.TaskGenericType == typeof(IActionResult)) - { - result = await (Task)_executor.Execute(_controller, arguments); - if (result == null) - { - throw new InvalidOperationException( - Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult))); - } - } - else if (_executor.IsTypeAssignableFromIActionResult) - { - if (_executor.IsMethodAsync) - { - result = (IActionResult)await _executor.ExecuteAsync(_controller, arguments); - } - else - { - result = (IActionResult)_executor.Execute(_controller, arguments); - } - - if (result == null) - { - throw new InvalidOperationException( - Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType)); - } - } - else if (!_executor.IsMethodAsync) - { - var resultAsObject = _executor.Execute(_controller, arguments); - result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) - { - DeclaredType = returnType, - }; - } - else if (_executor.TaskGenericType != null) - { - var resultAsObject = await _executor.ExecuteAsync(_controller, arguments); - result = resultAsObject as IActionResult ?? new ObjectResult(resultAsObject) - { - DeclaredType = _executor.TaskGenericType, - }; - } - else - { - // This will be the case for types which have derived from Task and Task or non Task types. - throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance( - _executor.MethodInfo.Name, - _executor.MethodInfo.DeclaringType)); - } - - _logger.ActionMethodExecuted(_actionExecutingContext, result); - } - finally - { - _diagnosticSource.AfterActionMethod( - _controllerContext, - _actionExecutingContext.ActionArguments, - _actionExecutingContext.Controller, - result); - } - - _actionExecutedContext = new ActionExecutedContext( - _actionExecutingContext, - _filters, - _controller) - { - Result = result - }; + private async Task InvokeNextResultFilterAsync() + { + try + { + var next = State.ResultNext; + var state = (object)null; + var scope = Scope.Result; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); } } catch (Exception exception) { - // Exceptions thrown by the action method OR filters bubble back up through ActionExcecutedContext. - _actionExecutedContext = new ActionExecutedContext( - _actionExecutingContext, - _filters, - _controller) + _resultExecutedContext = new ResultExecutedContext(_controllerContext, _filters, _result, _controller) { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), }; } - Debug.Assert(_actionExecutedContext != null); + Debug.Assert(_resultExecutedContext != null); } - private async Task InvokeAllResultFiltersAsync(IActionResult result) - { - _cursor.Reset(); - - _resultExecutingContext = new ResultExecutingContext(_controllerContext, _filters, result, _controller); - await InvokeResultFilterAsync(); - - Debug.Assert(_resultExecutingContext != null); - if (_resultExecutedContext.Exception != null && !_resultExecutedContext.ExceptionHandled) - { - // There's an unhandled exception in filters - if (_resultExecutedContext.ExceptionDispatchInfo != null) - { - _resultExecutedContext.ExceptionDispatchInfo.Throw(); - } - else - { - throw _resultExecutedContext.Exception; - } - } - } - - private async Task InvokeResultFilterAwaitedAsync() + private async Task InvokeNextResultFilterAwaitedAsync() { Debug.Assert(_resultExecutingContext != null); - if (_resultExecutingContext.Cancel == true) { // If we get here, it means that an async filter set cancel == true AND called next(). @@ -736,125 +1337,177 @@ namespace Microsoft.AspNetCore.Mvc.Internal throw new InvalidOperationException(message); } - await InvokeResultFilterAsync(); + await InvokeNextResultFilterAsync(); Debug.Assert(_resultExecutedContext != null); return _resultExecutedContext; } - private async Task InvokeResultFilterAsync() - { - Debug.Assert(_resultExecutingContext != null); - - try - { - var item = _cursor.GetNextFilter(); - if (item.FilterAsync != null) - { - _diagnosticSource.BeforeOnResultExecution(_resultExecutingContext, item.FilterAsync); - - await item.FilterAsync.OnResultExecutionAsync(_resultExecutingContext, InvokeResultFilterAwaitedAsync); - - if (_resultExecutedContext == null || _resultExecutingContext.Cancel == true) - { - // Short-circuited by not calling next || Short-circuited by setting Cancel == true - _logger.ResourceFilterShortCircuited(item.FilterAsync); - - _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, - _resultExecutingContext.Result, - _controller) - { - Canceled = true, - }; - } - - _diagnosticSource.AfterOnResultExecution(_resultExecutedContext, item.FilterAsync); - } - else if (item.Filter != null) - { - _diagnosticSource.BeforeOnResultExecuting(_resultExecutingContext, item.Filter); - - item.Filter.OnResultExecuting(_resultExecutingContext); - - _diagnosticSource.AfterOnResultExecuting(_resultExecutingContext, item.Filter); - - if (_resultExecutingContext.Cancel == true) - { - // Short-circuited by setting Cancel == true - _logger.ResourceFilterShortCircuited(item.Filter); - - _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, - _resultExecutingContext.Result, - _controller) - { - Canceled = true, - }; - } - else - { - await InvokeResultFilterAsync(); - Debug.Assert(_resultExecutedContext != null); - - _diagnosticSource.BeforeOnResultExecuted(_resultExecutedContext, item.Filter); - - item.Filter.OnResultExecuted(_resultExecutedContext); - - _diagnosticSource.AfterOnResultExecuted(_resultExecutedContext, item.Filter); - } - } - else - { - _cursor.Reset(); - - // The empty result is always flowed back as the 'executed' result - if (_resultExecutingContext.Result == null) - { - _resultExecutingContext.Result = new EmptyResult(); - } - - await InvokeResultAsync(_resultExecutingContext.Result); - - Debug.Assert(_resultExecutedContext == null); - _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, - _resultExecutingContext.Result, - _controller); - } - } - catch (Exception exception) - { - _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, - _resultExecutingContext.Result, - _controller) - { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) - }; - } - - Debug.Assert(_resultExecutedContext != null); - } - private async Task InvokeResultAsync(IActionResult result) { - _diagnosticSource.BeforeActionResult(_controllerContext, result); + var controllerContext = _controllerContext; + + _diagnosticSource.BeforeActionResult(controllerContext, result); try { - await result.ExecuteResultAsync(_controllerContext); + await result.ExecuteResultAsync(controllerContext); } finally { - _diagnosticSource.AfterActionResult(_controllerContext, result); + _diagnosticSource.AfterActionResult(controllerContext, result); } } + private static void Rethrow(ResourceExecutedContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private static void Rethrow(ExceptionContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private static void Rethrow(ActionExecutedContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private static void Rethrow(ResultExecutedContext context) + { + if (context == null) + { + return; + } + + if (context.ExceptionHandled) + { + return; + } + + if (context.ExceptionDispatchInfo != null) + { + context.ExceptionDispatchInfo.Throw(); + } + + if (context.Exception != null) + { + throw context.Exception; + } + } + + private enum Scope + { + Invoker, + Resource, + Exception, + Action, + Result, + } + + private enum State + { + InvokeBegin, + AuthorizationBegin, + AuthorizationNext, + AuthorizationAsyncBegin, + AuthorizationAsyncEnd, + AuthorizationSync, + AuthorizationShortCircuit, + AuthorizationEnd, + ResourceBegin, + ResourceNext, + ResourceAsyncBegin, + ResourceAsyncEnd, + ResourceSyncBegin, + ResourceSyncEnd, + ResourceShortCircuit, + ResourceInside, + ResourceEnd, + ExceptionBegin, + ExceptionNext, + ExceptionAsyncBegin, + ExceptionAsyncResume, + ExceptionAsyncEnd, + ExceptionSyncBegin, + ExceptionSyncEnd, + ExceptionInside, + ExceptionShortCircuit, + ExceptionEnd, + ActionBegin, + ActionNext, + ActionAsyncBegin, + ActionAsyncEnd, + ActionSyncBegin, + ActionSyncEnd, + ActionInside, + ActionEnd, + ResultBegin, + ResultNext, + ResultAsyncBegin, + ResultAsyncEnd, + ResultSyncBegin, + ResultSyncEnd, + ResultInside, + ResultEnd, + InvokeEnd, + } + /// /// A one-way cursor for filters. /// diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs index 4625992641..d234ae5e3e 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/MvcCoreLoggerExtensions.cs @@ -243,7 +243,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal _contentResultExecuting(logger, contentType, null); } - public static void ActionMethodExecuting(this ILogger logger, ActionExecutingContext context, object[] arguments) + public static void ActionMethodExecuting(this ILogger logger, ControllerContext context, object[] arguments) { if (logger.IsEnabled(LogLevel.Information)) { @@ -269,7 +269,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - public static void ActionMethodExecuted(this ILogger logger, ActionExecutingContext context, IActionResult result) + public static void ActionMethodExecuted(this ILogger logger, ControllerContext context, IActionResult result) { if (logger.IsEnabled(LogLevel.Debug)) { diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs index 5b9166aa08..7ff95e85d4 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs @@ -1802,6 +1802,56 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.False(invoker.ControllerFactory.CreateCalled); } + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit_WithoutResult() + { + // Arrange + ResourceExecutedContext context = null; + var resourceFilter1 = new Mock(MockBehavior.Strict); + resourceFilter1 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var resourceFilter2 = new Mock(MockBehavior.Strict); + resourceFilter2 + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + return Task.FromResult(true); + }) + .Verifiable(); + + var resourceFilter3 = new Mock(MockBehavior.Strict); + var exceptionFilter = new Mock(MockBehavior.Strict); + var actionFilter = new Mock(MockBehavior.Strict); + var resultFilter = new Mock(MockBehavior.Strict); + + var invoker = CreateInvoker( + new IFilterMetadata[] + { + resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 + resourceFilter2.Object, // This filter will short circuit + resourceFilter3.Object, // This shouldn't run - it will throw if it does + exceptionFilter.Object, // This shouldn't run - it will throw if it does + actionFilter.Object, // This shouldn't run - it will throw if it does + resultFilter.Object // This shouldn't run - it will throw if it does + }, + // The action won't run + actionThrows: true); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Null(context.Result); + Assert.True(context.Canceled); + Assert.False(invoker.ControllerFactory.CreateCalled); + } + [Fact] public async Task InvokeAction_InvokesResourceFilter_ShortCircuit() { @@ -2701,6 +2751,49 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.NotNull(listener.AfterAction?.ActionDescriptor); Assert.NotNull(listener.AfterAction?.HttpContext); } + + public async Task InvokeAction_ExceptionBubbling_AsyncActionFilter_To_ResourceFilter() + { + // Arrange + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + var context = await next(); + Assert.Same(_actionException, context.Exception); + context.ExceptionHandled = true; + }); + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + await next(); + }); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + await next(); + }); + + var invoker = CreateInvoker( + new IFilterMetadata[] + { + resourceFilter.Object, + actionFilter1.Object, + actionFilter2.Object, + }, + // The action won't run + actionThrows: true); + + // Act & Assert + await invoker.InvokeAsync(); + } private TestControllerActionInvoker CreateInvoker( IFilterMetadata filter,