diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs index b073c599ef..3c0bcd640f 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -18,18 +18,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal { private readonly ControllerActionInvokerCacheEntry _cacheEntry; private readonly ControllerContext _controllerContext; - - private object _controller; + private Dictionary _arguments; - private ExceptionContext _exceptionContext; - private ActionExecutingContext _actionExecutingContext; private ActionExecutedContext _actionExecutedContext; - private ResultExecutingContext _resultExecutingContext; - private ResultExecutedContext _resultExecutedContext; - internal ControllerActionInvoker( ILogger logger, DiagnosticSource diagnosticSource, @@ -52,9 +46,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal protected override void ReleaseResources() { - if (_controller != null && _cacheEntry.ControllerReleaser != null) + if (_instance != null && _cacheEntry.ControllerReleaser != null) { - _cacheEntry.ControllerReleaser(_controllerContext, _controller); + _cacheEntry.ControllerReleaser(_controllerContext, _instance); } } @@ -65,214 +59,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal switch (next) { - case State.ResourceInsideBegin: - { - goto case State.ExceptionBegin; - } - - 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.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) - { - // We don't need to do anthing to trigger a short circuit. If there's another - // exception filter on the stack it will check the same set of conditions - // and then just skip itself. - _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) - { - // We don't need to do anthing to trigger a short circuit. If there's another - // exception filter on the stack it will check the same set of conditions - // and then just skip itself. - _logger.ExceptionFilterShortCircuited(filter); - } - } - - goto case State.ExceptionEnd; - } - - case State.ExceptionInside: - { - goto case State.ActionBegin; - } - - case State.ExceptionHandled: - { - // We arrive in this state when an exception happened, but was handled by exception filters - // either by setting ExceptionHandled, or nulling out the Exception or setting a result - // on the ExceptionContext. - // - // We need to execute the result (if any) and then exit gracefully which unwinding Resource - // filters. - - Debug.Assert(state != null); - Debug.Assert(_exceptionContext != null); - - if (_exceptionContext.Result == null) - { - _exceptionContext.Result = new EmptyResult(); - } - - if (scope == Scope.Resource) - { - Debug.Assert(_exceptionContext.Result != null); - _result = _exceptionContext.Result; - } - - var task = InvokeResultAsync(_exceptionContext.Result); - if (task.Status != TaskStatus.RanToCompletion) - { - next = State.ResourceInsideEnd; - return task; - } - - goto case State.ResourceInsideEnd; - } - - case State.ExceptionEnd: - { - var exceptionContext = _exceptionContext; - - if (scope == Scope.Exception) - { - isCompleted = true; - return TaskCache.CompletedTask; - } - - if (exceptionContext != null) - { - if (exceptionContext.Result != null || - exceptionContext.Exception == null || - exceptionContext.ExceptionHandled) - { - goto case State.ExceptionHandled; - } - - Rethrow(exceptionContext); - Debug.Fail("unreachable"); - } - - goto case State.ResultBegin; - } - case State.ActionBegin: { var controllerContext = _controllerContext; _cursor.Reset(); - _controller = _cacheEntry.ControllerFactory(controllerContext); + _instance = _cacheEntry.ControllerFactory(controllerContext); _arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); @@ -293,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { if (_actionExecutingContext == null) { - _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller); + _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance); } state = current.FilterAsync; @@ -303,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { if (_actionExecutingContext == null) { - _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller); + _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance); } state = current.Filter; @@ -350,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal _actionExecutedContext = new ActionExecutedContext( _controllerContext, _filters, - _controller) + _instance) { Canceled = true, Result = _actionExecutingContext.Result, @@ -384,7 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal _actionExecutedContext = new ActionExecutedContext( _actionExecutingContext, _filters, - _controller) + _instance) { Canceled = true, Result = _actionExecutingContext.Result, @@ -439,7 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { if (_actionExecutedContext == null) { - _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller) + _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance) { Result = _result, }; @@ -457,206 +250,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal _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.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); - - goto case State.ResourceInsideEnd; - } - - case State.ResourceInsideEnd: - { isCompleted = true; return TaskCache.CompletedTask; } @@ -666,28 +259,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - private async Task InvokeNextExceptionFilterAsync() - { - try - { - var next = State.ExceptionNext; - var state = (object)null; - var scope = Scope.Exception; - var isCompleted = false; - while (!isCompleted) - { - await Next(ref next, ref scope, ref state, ref isCompleted); - } - } - catch (Exception exception) - { - _exceptionContext = new ExceptionContext(_controllerContext, _filters) - { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), - }; - } - } - private async Task InvokeNextActionFilterAsync() { try @@ -703,7 +274,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } catch (Exception exception) { - _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller) + _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance) { ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), }; @@ -737,7 +308,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal { var controllerContext = _controllerContext; var executor = _cacheEntry.ActionMethodExecutor; - var controller = _controller; + var controller = _instance; var arguments = _arguments; var orderedArguments = PrepareArguments(arguments, executor); @@ -842,58 +413,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal return typeof(IActionResult).IsAssignableFrom(resultType); } - 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) - { - _resultExecutedContext = new ResultExecutedContext(_controllerContext, _filters, _result, _controller) - { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), - }; - } - - Debug.Assert(_resultExecutedContext != null); - } - - 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(). - // This is forbidden. - var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit( - typeof(IAsyncResultFilter).Name, - nameof(ResultExecutingContext.Cancel), - typeof(ResultExecutingContext).Name, - typeof(ResultExecutionDelegate).Name); - - throw new InvalidOperationException(message); - } - - await InvokeNextResultFilterAsync(); - - Debug.Assert(_resultExecutedContext != null); - return _resultExecutedContext; - } - /// for details on what the /// variables in this method represent. protected override async Task InvokeInnerFilterAsync() { - var next = State.ResourceInsideBegin; - var scope = Scope.Resource; + var next = State.ActionBegin; + var scope = Scope.Invoker; var state = (object)null; var isCompleted = false; @@ -903,29 +428,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - 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) @@ -949,29 +451,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } - 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 Task BindArgumentsAsync() { // Perf: Avoid allocating async state machines where possible. We only need the state @@ -984,7 +463,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal } Debug.Assert(_cacheEntry.ControllerBinderDelegate != null); - return _cacheEntry.ControllerBinderDelegate(_controllerContext, _controller, _arguments); + return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments); } private static object[] PrepareArguments( @@ -1016,25 +495,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal private enum Scope { - Resource, - Exception, + Invoker, Action, - Result, } private enum State { - ResourceInsideBegin, - ExceptionBegin, - ExceptionNext, - ExceptionAsyncBegin, - ExceptionAsyncResume, - ExceptionAsyncEnd, - ExceptionSyncBegin, - ExceptionSyncEnd, - ExceptionInside, - ExceptionHandled, - ExceptionEnd, ActionBegin, ActionNext, ActionAsyncBegin, @@ -1043,15 +509,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal ActionSyncEnd, ActionInside, ActionEnd, - ResultBegin, - ResultNext, - ResultAsyncBegin, - ResultAsyncEnd, - ResultSyncBegin, - ResultSyncEnd, - ResultInside, - ResultEnd, - ResourceInsideEnd, } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs index 0e95b14971..f58e76ef6b 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ResourceInvoker.cs @@ -24,11 +24,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal private AuthorizationFilterContext _authorizationContext; private ResourceExecutingContext _resourceExecutingContext; private ResourceExecutedContext _resourceExecutedContext; + private ExceptionContext _exceptionContext; + private ResultExecutingContext _resultExecutingContext; + private ResultExecutedContext _resultExecutedContext; // Do not make this readonly, it's mutable. We don't want to make a copy. // https://blogs.msdn.microsoft.com/ericlippert/2008/05/14/mutating-readonly-structs/ protected FilterCursor _cursor; protected IActionResult _result; + protected object _instance; public ResourceInvoker( DiagnosticSource diagnosticSource, @@ -313,18 +317,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal 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); - next = State.InvokeEnd; - return InvokeInnerFilterAsync(); + // All resource filters are currently on the stack - now execute the 'inside'. + goto case State.ResourceInside; } } @@ -447,11 +443,419 @@ namespace Microsoft.AspNetCore.Mvc.Internal } case State.ResourceInside: + { + goto case State.ExceptionBegin; + } + + case State.ExceptionBegin: { - next = State.ResourceOutside; - return InvokeInnerFilterAsync(); + _cursor.Reset(); + goto case State.ExceptionNext; } - case State.ResourceOutside: + + 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 the action. + 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) + { + // We don't need to do anthing to trigger a short circuit. If there's another + // exception filter on the stack it will check the same set of conditions + // and then just skip itself. + _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) + { + // We don't need to do anthing to trigger a short circuit. If there's another + // exception filter on the stack it will check the same set of conditions + // and then just skip itself. + _logger.ExceptionFilterShortCircuited(filter); + } + } + + goto case State.ExceptionEnd; + } + + case State.ExceptionInside: + { + goto case State.ActionBegin; + } + + case State.ExceptionHandled: + { + // We arrive in this state when an exception happened, but was handled by exception filters + // either by setting ExceptionHandled, or nulling out the Exception or setting a result + // on the ExceptionContext. + // + // We need to execute the result (if any) and then exit gracefully which unwinding Resource + // filters. + + Debug.Assert(state != null); + Debug.Assert(_exceptionContext != null); + + if (_exceptionContext.Result == null) + { + _exceptionContext.Result = new EmptyResult(); + } + + if (scope == Scope.Resource) + { + Debug.Assert(_exceptionContext.Result != null); + _result = _exceptionContext.Result; + } + + var task = InvokeResultAsync(_exceptionContext.Result); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ResourceInsideEnd; + return task; + } + + goto case State.ResourceInsideEnd; + } + + case State.ExceptionEnd: + { + var exceptionContext = _exceptionContext; + + if (scope == Scope.Exception) + { + isCompleted = true; + return TaskCache.CompletedTask; + } + + if (exceptionContext != null) + { + if (exceptionContext.Result != null || + exceptionContext.Exception == null || + exceptionContext.ExceptionHandled) + { + goto case State.ExceptionHandled; + } + + Rethrow(exceptionContext); + Debug.Fail("unreachable"); + } + + goto case State.ResultBegin; + } + + case State.ActionBegin: + { + var task = InvokeInnerFilterAsync(); + if (task.Status != TaskStatus.RanToCompletion) + { + next = State.ActionEnd; + return task; + } + + goto case State.ActionEnd; + } + + case State.ActionEnd: + { + 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(_actionContext, _filters, _result, _instance); + } + + state = current.FilterAsync; + goto case State.ResultAsyncBegin; + } + else if (current.Filter != null) + { + if (_resultExecutingContext == null) + { + _resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, _result, _instance); + } + + 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( + _actionContext, + _filters, + resultExecutingContext.Result, + _instance) + { + 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, + _instance) + { + 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(_actionContext, _filters, result, _instance); + } + + isCompleted = true; + return TaskCache.CompletedTask; + } + + Rethrow(_resultExecutedContext); + + goto case State.ResourceInsideEnd; + } + + case State.ResourceInsideEnd: { var result = _result; @@ -538,6 +942,74 @@ namespace Microsoft.AspNetCore.Mvc.Internal Debug.Assert(_resourceExecutedContext != null); } + private async Task InvokeNextExceptionFilterAsync() + { + try + { + var next = State.ExceptionNext; + var state = (object)null; + var scope = Scope.Exception; + var isCompleted = false; + while (!isCompleted) + { + await Next(ref next, ref scope, ref state, ref isCompleted); + } + } + catch (Exception exception) + { + _exceptionContext = new ExceptionContext(_actionContext, _filters) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + } + + 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) + { + _resultExecutedContext = new ResultExecutedContext(_actionContext, _filters, _result, _instance) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), + }; + } + + Debug.Assert(_resultExecutedContext != null); + } + + 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(). + // This is forbidden. + var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit( + typeof(IAsyncResultFilter).Name, + nameof(ResultExecutingContext.Cancel), + typeof(ResultExecutingContext).Name, + typeof(ResultExecutionDelegate).Name); + + throw new InvalidOperationException(message); + } + + await InvokeNextResultFilterAsync(); + + Debug.Assert(_resultExecutedContext != null); + return _resultExecutedContext; + } + private static void Rethrow(ResourceExecutedContext context) { if (context == null) @@ -561,10 +1033,58 @@ namespace Microsoft.AspNetCore.Mvc.Internal } } + 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(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, + Result, } private enum State @@ -585,8 +1105,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal ResourceSyncEnd, ResourceShortCircuit, ResourceInside, - ResourceOutside, + ResourceInsideEnd, ResourceEnd, + ExceptionBegin, + ExceptionNext, + ExceptionAsyncBegin, + ExceptionAsyncResume, + ExceptionAsyncEnd, + ExceptionSyncBegin, + ExceptionSyncEnd, + ExceptionInside, + ExceptionHandled, + ExceptionEnd, + ActionBegin, + ActionEnd, + ResultBegin, + ResultNext, + ResultAsyncBegin, + ResultAsyncEnd, + ResultSyncBegin, + ResultSyncEnd, + ResultInside, + ResultEnd, InvokeEnd, } } diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs index c451c143fc..818ff1dcee 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs @@ -32,6 +32,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal { public class ControllerActionInvokerTest : CommonResourceInvokerTest { + #region Diagnostics + [Fact] public async Task Invoke_Success_LogsCorrectValues() { @@ -142,6 +144,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.NotNull(listener.AfterAction?.HttpContext); } + #endregion + + #region Controller Context [Fact] public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInControllerContext() @@ -204,6 +209,599 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[0]); } + #endregion + + #region Action Filters + + [Fact] + public async Task InvokeAction_InvokesActionFilter() + { + // Arrange + IActionResult result = null; + + var filter = new Mock(MockBehavior.Strict); + filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => result = c.Result) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + Assert.Same(Result, result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter() + { + // Arrange + IActionResult result = null; + + var filter = new Mock(MockBehavior.Strict); + filter + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (context, next) => + { + var resultContext = await next(); + result = resultContext.Result; + }) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + Assert.Same(Result, result); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_ShortCircuit() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + ActionExecutedContext context = null; + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback(c => c.Result = result.Object) + .Verifiable(); + + var actionFilter3 = new Mock(MockBehavior.Strict); + + var resultFilter = new Mock(MockBehavior.Strict); + resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] + { + actionFilter1.Object, + actionFilter2.Object, + actionFilter3.Object, + resultFilter.Object, + }); + + // Act + await invoker.InvokeAsync(); + + // Assert + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); + + resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + Assert.True(context.Canceled); + Assert.Same(context.Result, result.Object); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns(Task.FromResult(true)) + .Verifiable(); + + ActionExecutedContext context = null; + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + // Notice we're not calling next + c.Result = result.Object; + return Task.FromResult(true); + }) + .Verifiable(); + + var actionFilter3 = new Mock(MockBehavior.Strict); + + var resultFilter1 = new Mock(MockBehavior.Strict); + resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + var resultFilter2 = new Mock(MockBehavior.Strict); + resultFilter2.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter2.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] + { + actionFilter1.Object, + actionFilter2.Object, + actionFilter3.Object, + resultFilter1.Object, + resultFilter2.Object, + }); + + // Act + await invoker.InvokeAsync(); + + // Assert + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + actionFilter2.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + Assert.True(context.Canceled); + Assert.Same(context.Result, result.Object); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult() + { + // Arrange + ActionExecutedContext context = null; + + var actionFilter1 = new Mock(MockBehavior.Strict); + actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => context = c) + .Verifiable(); + + var actionFilter2 = new Mock(MockBehavior.Strict); + actionFilter2 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns((c, next) => + { + // Notice we're not calling next + return Task.FromResult(true); + }) + .Verifiable(); + + var actionFilter3 = new Mock(MockBehavior.Strict); + + var resultFilter = new Mock(MockBehavior.Strict); + resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] + { + actionFilter1.Object, + actionFilter2.Object, + actionFilter3.Object, + resultFilter.Object, + }); + + // Act + await invoker.InvokeAsync(); + + // Assert + actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + actionFilter2.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + Assert.True(context.Canceled); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext() + { + // Arrange + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + c.Result = new EmptyResult(); + await next(); + }) + .Verifiable(); + + var message = + "If an IAsyncActionFilter provides a result value by setting the Result property of " + + "ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " + + "ActionExecutionDelegate."; + + var invoker = CreateInvoker(actionFilter.Object); + + // Act & Assert + await ExceptionAssert.ThrowsAsync( + async () => await invoker.InvokeAsync(), + message); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction() + { + // Arrange + ActionExecutedContext context = null; + + var filter = new Mock(MockBehavior.Strict); + filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => + { + context = c; + + // Handle the exception so the test doesn't throw. + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + }) + .Verifiable(); + + var invoker = CreateInvoker(filter.Object, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + Assert.Same(Exception, context.Exception); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter() + { + // Arrange + var exception = new DataMisalignedException(); + ActionExecutedContext context = null; + + var filter1 = new Mock(MockBehavior.Strict); + filter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter1 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => + { + context = c; + + // Handle the exception so the test doesn't throw. + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + }) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2 + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback(c => { throw exception; }) + .Verifiable(); + + var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + filter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); + + Assert.Same(exception, context.Exception); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter() + { + // Arrange + var exception = new DataMisalignedException(); + ActionExecutedContext context = null; + + var filter1 = new Mock(MockBehavior.Strict); + filter1 + .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + + // Handle the exception so the test doesn't throw. + Assert.False(context.ExceptionHandled); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var filter2 = new Mock(MockBehavior.Strict); + filter2.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + filter2 + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => { throw exception; }) + .Verifiable(); + + var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + filter1.Verify( + f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + + filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + + Assert.Same(exception, context.Exception); + Assert.Null(context.Result); + } + + [Fact] + public async Task InvokeAction_InvokesActionFilter_HandleException() + { + // Arrange + var result = new Mock(MockBehavior.Strict); + result + .Setup(r => r.ExecuteResultAsync(It.IsAny())) + .Returns((context) => Task.FromResult(true)) + .Verifiable(); + + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); + actionFilter + .Setup(f => f.OnActionExecuted(It.IsAny())) + .Callback(c => + { + // Handle the exception so the test doesn't throw. + Assert.False(c.ExceptionHandled); + c.ExceptionHandled = true; + + c.Result = result.Object; + }) + .Verifiable(); + + var resultFilter = new Mock(MockBehavior.Strict); + resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); + resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); + + var invoker = CreateInvoker( + new IFilterMetadata[] { actionFilter.Object, resultFilter.Object }, + exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + actionFilter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); + actionFilter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); + + resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); + resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); + + result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter() + { + // Arrange + var expected = Mock.Of(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + }) + .Verifiable(); + + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback((c) => + { + c.Result = expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Result); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var actionFilter = new Mock(MockBehavior.Strict); + actionFilter + .Setup(f => f.OnActionExecuting(It.IsAny())) + .Callback((c) => + { + throw expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter() + { + // Arrange + var expected = new DataMisalignedException(); + + ResourceExecutedContext context = null; + var resourceFilter = new Mock(MockBehavior.Strict); + resourceFilter + .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) + .Returns(async (c, next) => + { + context = await next(); + context.ExceptionHandled = true; + }) + .Verifiable(); + + var exceptionFilter = new Mock(MockBehavior.Strict); + exceptionFilter + .Setup(f => f.OnException(It.IsAny())) + .Callback((c) => + { + throw expected; + }); + + var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, exception: Exception); + + // Act + await invoker.InvokeAsync(); + + // Assert + Assert.Same(expected, context.Exception); + Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); + + resourceFilter.Verify( + f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), + Times.Once()); + } + + [Fact] + 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(Exception, 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 + exception: Exception); + + // Act & Assert + await invoker.InvokeAsync(); + } + + #endregion + + #region Action Method Signatures + [Fact] public async Task InvokeAction_AsyncAction_TaskReturnType() { @@ -794,6 +1392,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal Assert.Equal(5, context.Object.Items["Result"]); } + #endregion + protected override ResourceInvoker CreateInvoker( IFilterMetadata[] filters, Exception exception = null, diff --git a/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs index 37bffa11a6..d04ff0ae35 100644 --- a/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.TestCommon/CommonResourceInvokerTest.cs @@ -3,20 +3,11 @@ using System; using System.Collections.Generic; -using System.ComponentModel; -using System.Diagnostics; -using System.Linq; -using System.Reflection; using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; -using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; -using Microsoft.Extensions.Logging; using Moq; using Xunit; @@ -701,7 +692,6 @@ namespace Microsoft.AspNetCore.Mvc // None of these filters should run var authorizationFilter2 = new Mock(MockBehavior.Strict); var resourceFilter = new Mock(MockBehavior.Strict); - var actionFilter = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] @@ -710,7 +700,6 @@ namespace Microsoft.AspNetCore.Mvc authorizationFilter1.Object, authorizationFilter2.Object, resourceFilter.Object, - actionFilter.Object, resultFilter.Object, }); @@ -751,432 +740,6 @@ namespace Microsoft.AspNetCore.Mvc challenge.Verify(c => c.ExecuteResultAsync(It.IsAny()), Times.Once()); } - [Fact] - public async Task InvokeAction_InvokesActionFilter() - { - // Arrange - IActionResult result = null; - - var filter = new Mock(MockBehavior.Strict); - filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => result = c.Result) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - Assert.Same(Result, result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter() - { - // Arrange - IActionResult result = null; - - var filter = new Mock(MockBehavior.Strict); - filter - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (context, next) => - { - var resultContext = await next(); - result = resultContext.Result; - }) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - Assert.Same(Result, result); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_ShortCircuit() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - ActionExecutedContext context = null; - - var actionFilter1 = new Mock(MockBehavior.Strict); - actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var actionFilter2 = new Mock(MockBehavior.Strict); - actionFilter2 - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback(c => c.Result = result.Object) - .Verifiable(); - - var actionFilter3 = new Mock(MockBehavior.Strict); - - var resultFilter = new Mock(MockBehavior.Strict); - resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] - { - actionFilter1.Object, - actionFilter2.Object, - actionFilter3.Object, - resultFilter.Object, - }); - - // Act - await invoker.InvokeAsync(); - - // Assert - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); - - resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - Assert.True(context.Canceled); - Assert.Same(context.Result, result.Object); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns(Task.FromResult(true)) - .Verifiable(); - - ActionExecutedContext context = null; - - var actionFilter1 = new Mock(MockBehavior.Strict); - actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var actionFilter2 = new Mock(MockBehavior.Strict); - actionFilter2 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - // Notice we're not calling next - c.Result = result.Object; - return Task.FromResult(true); - }) - .Verifiable(); - - var actionFilter3 = new Mock(MockBehavior.Strict); - - var resultFilter1 = new Mock(MockBehavior.Strict); - resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - var resultFilter2 = new Mock(MockBehavior.Strict); - resultFilter2.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter2.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] - { - actionFilter1.Object, - actionFilter2.Object, - actionFilter3.Object, - resultFilter1.Object, - resultFilter2.Object, - }); - - // Act - await invoker.InvokeAsync(); - - // Assert - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - actionFilter2.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - Assert.True(context.Canceled); - Assert.Same(context.Result, result.Object); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult() - { - // Arrange - ActionExecutedContext context = null; - - var actionFilter1 = new Mock(MockBehavior.Strict); - actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => context = c) - .Verifiable(); - - var actionFilter2 = new Mock(MockBehavior.Strict); - actionFilter2 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns((c, next) => - { - // Notice we're not calling next - return Task.FromResult(true); - }) - .Verifiable(); - - var actionFilter3 = new Mock(MockBehavior.Strict); - - var resultFilter = new Mock(MockBehavior.Strict); - resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] - { - actionFilter1.Object, - actionFilter2.Object, - actionFilter3.Object, - resultFilter.Object, - }); - - // Act - await invoker.InvokeAsync(); - - // Assert - actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - actionFilter2.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - Assert.True(context.Canceled); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext() - { - // Arrange - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - c.Result = new EmptyResult(); - await next(); - }) - .Verifiable(); - - var message = - "If an IAsyncActionFilter provides a result value by setting the Result property of " + - "ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " + - "ActionExecutionDelegate."; - - var invoker = CreateInvoker(actionFilter.Object); - - // Act & Assert - await ExceptionAssert.ThrowsAsync( - async () => await invoker.InvokeAsync(), - message); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction() - { - // Arrange - ActionExecutedContext context = null; - - var filter = new Mock(MockBehavior.Strict); - filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => - { - context = c; - - // Handle the exception so the test doesn't throw. - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - }) - .Verifiable(); - - var invoker = CreateInvoker(filter.Object, exception: Exception); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - Assert.Same(Exception, context.Exception); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter() - { - // Arrange - var exception = new DataMisalignedException(); - ActionExecutedContext context = null; - - var filter1 = new Mock(MockBehavior.Strict); - filter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter1 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => - { - context = c; - - // Handle the exception so the test doesn't throw. - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - }) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2 - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback(c => { throw exception; }) - .Verifiable(); - - var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - filter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); - - Assert.Same(exception, context.Exception); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter() - { - // Arrange - var exception = new DataMisalignedException(); - ActionExecutedContext context = null; - - var filter1 = new Mock(MockBehavior.Strict); - filter1 - .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - - // Handle the exception so the test doesn't throw. - Assert.False(context.ExceptionHandled); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var filter2 = new Mock(MockBehavior.Strict); - filter2.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - filter2 - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => { throw exception; }) - .Verifiable(); - - var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - filter1.Verify( - f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - - filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - - Assert.Same(exception, context.Exception); - Assert.Null(context.Result); - } - - [Fact] - public async Task InvokeAction_InvokesActionFilter_HandleException() - { - // Arrange - var result = new Mock(MockBehavior.Strict); - result - .Setup(r => r.ExecuteResultAsync(It.IsAny())) - .Returns((context) => Task.FromResult(true)) - .Verifiable(); - - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); - actionFilter - .Setup(f => f.OnActionExecuted(It.IsAny())) - .Callback(c => - { - // Handle the exception so the test doesn't throw. - Assert.False(c.ExceptionHandled); - c.ExceptionHandled = true; - - c.Result = result.Object; - }) - .Verifiable(); - - var resultFilter = new Mock(MockBehavior.Strict); - resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); - resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); - - var invoker = CreateInvoker( - new IFilterMetadata[] { actionFilter.Object, resultFilter.Object }, - exception: Exception); - - // Act - await invoker.InvokeAsync(); - - // Assert - actionFilter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); - actionFilter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); - - resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); - resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); - - result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); - } - [Fact] public async Task InvokeAction_InvokesResultFilter() { @@ -1653,43 +1216,6 @@ namespace Microsoft.AspNetCore.Mvc Times.Once()); } - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter() - { - // Arrange - var expected = Mock.Of(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - }) - .Verifiable(); - - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback((c) => - { - c.Result = expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Result); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter() { @@ -1795,84 +1321,6 @@ namespace Microsoft.AspNetCore.Mvc Times.Once()); } - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var actionFilter = new Mock(MockBehavior.Strict); - actionFilter - .Setup(f => f.OnActionExecuting(It.IsAny())) - .Callback((c) => - { - throw expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - - [Fact] - public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter() - { - // Arrange - var expected = new DataMisalignedException(); - - ResourceExecutedContext context = null; - var resourceFilter = new Mock(MockBehavior.Strict); - resourceFilter - .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) - .Returns(async (c, next) => - { - context = await next(); - context.ExceptionHandled = true; - }) - .Verifiable(); - - var exceptionFilter = new Mock(MockBehavior.Strict); - exceptionFilter - .Setup(f => f.OnException(It.IsAny())) - .Callback((c) => - { - throw expected; - }); - - var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, exception: Exception); - - // Act - await invoker.InvokeAsync(); - - // Assert - Assert.Same(expected, context.Exception); - Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); - - resourceFilter.Verify( - f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), - Times.Once()); - } - [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter() { @@ -2082,7 +1530,6 @@ namespace Microsoft.AspNetCore.Mvc 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( @@ -2092,7 +1539,6 @@ namespace Microsoft.AspNetCore.Mvc 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 @@ -2132,7 +1578,6 @@ namespace Microsoft.AspNetCore.Mvc 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( @@ -2142,7 +1587,6 @@ namespace Microsoft.AspNetCore.Mvc 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 @@ -2184,7 +1628,6 @@ namespace Microsoft.AspNetCore.Mvc }); var resourceFilter3 = new Mock(MockBehavior.Strict); - var actionFilter = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( @@ -2193,7 +1636,6 @@ namespace Microsoft.AspNetCore.Mvc resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 resourceFilter2.Object, resourceFilter3.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 @@ -2268,49 +1710,5 @@ namespace Microsoft.AspNetCore.Mvc f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Never()); } - - [Fact] - 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(Exception, 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 - exception: Exception); - - // Act & Assert - await invoker.InvokeAsync(); - } } }