From 1e02fd2e6b46e78ab3f442752b59eb6f4f70e8e9 Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Fri, 13 May 2016 16:54:53 -0700 Subject: [PATCH] Merge invoker back into one class This will help future generations maintain this class. Notice that the protected methods that are going away all just call into another extensibility point (other than the executor). If we need to we could make that extensible in the future and then we have the same support with fewer hooks and less complexity. --- .../Internal/ControllerActionInvoker.cs | 851 ++++++++++++++++-- .../ControllerActionInvokerProvider.cs | 5 +- .../Internal/FilterActionInvoker.cs | 845 ----------------- .../Internal/ControllerActionInvokerTest.cs | 32 +- 4 files changed, 807 insertions(+), 926 deletions(-) delete mode 100644 src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs index 316eba6dbd..7c2c3280f5 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvoker.cs @@ -7,7 +7,9 @@ using System.Diagnostics; #if NETSTANDARD1_5 using System.Reflection; #endif +using System.Runtime.ExceptionServices; using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Core; using Microsoft.AspNetCore.Mvc.Filters; @@ -17,108 +19,758 @@ using Microsoft.Extensions.Logging; namespace Microsoft.AspNetCore.Mvc.Internal { - public class ControllerActionInvoker : FilterActionInvoker + public class ControllerActionInvoker : IActionInvoker { - private readonly ControllerActionDescriptor _descriptor; private readonly IControllerFactory _controllerFactory; - private readonly IControllerArgumentBinder _argumentBinder; + private readonly IControllerArgumentBinder _controllerArgumentBinder; + private readonly DiagnosticSource _diagnosticSource; + private readonly ILogger _logger; + + private readonly ControllerContext _controllerContext; + private readonly IFilterMetadata[] _filters; + private readonly ObjectMethodExecutor _executor; + + // 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/ + private FilterCursor _cursor; + private object _controller; + + private AuthorizationFilterContext _authorizationContext; + + private ResourceExecutingContext _resourceExecutingContext; + private ResourceExecutedContext _resourceExecutedContext; + + private ExceptionContext _exceptionContext; + + private ActionExecutingContext _actionExecutingContext; + private ActionExecutedContext _actionExecutedContext; + + private ResultExecutingContext _resultExecutingContext; + private ResultExecutedContext _resultExecutedContext; public ControllerActionInvoker( - ActionContext actionContext, - ControllerActionInvokerCache controllerActionInvokerCache, + ControllerActionInvokerCache cache, IControllerFactory controllerFactory, - ControllerActionDescriptor descriptor, - IControllerArgumentBinder argumentBinder, - IReadOnlyList valueProviderFactories, + IControllerArgumentBinder controllerArgumentBinder, ILogger logger, DiagnosticSource diagnosticSource, + ActionContext actionContext, + IReadOnlyList valueProviderFactories, int maxModelValidationErrors) - : base( - actionContext, - controllerActionInvokerCache, - valueProviderFactories, - logger, - diagnosticSource, - maxModelValidationErrors) { + if (cache == null) + { + throw new ArgumentNullException(nameof(cache)); + } + if (controllerFactory == null) { throw new ArgumentNullException(nameof(controllerFactory)); } - if (descriptor == null) + if (controllerArgumentBinder == null) { - throw new ArgumentNullException(nameof(descriptor)); + throw new ArgumentNullException(nameof(controllerArgumentBinder)); } - if (argumentBinder == null) + if (logger == null) { - throw new ArgumentNullException(nameof(argumentBinder)); + throw new ArgumentNullException(nameof(logger)); } + if (diagnosticSource == null) + { + throw new ArgumentNullException(nameof(diagnosticSource)); + } + + if (actionContext == null) + { + throw new ArgumentNullException(nameof(actionContext)); + } + + if (valueProviderFactories == null) + { + throw new ArgumentNullException(nameof(valueProviderFactories)); + } + _controllerFactory = controllerFactory; - _descriptor = descriptor; - _argumentBinder = argumentBinder; + _controllerArgumentBinder = controllerArgumentBinder; + _logger = logger; + _diagnosticSource = diagnosticSource; - if (descriptor.MethodInfo == null) + _controllerContext = new ControllerContext(actionContext); + _controllerContext.ModelState.MaxAllowedErrors = maxModelValidationErrors; + + // PERF: These are rarely going to be changed, so let's go copy-on-write. + _controllerContext.ValueProviderFactories = new CopyOnWriteList(valueProviderFactories); + + var cacheEntry = cache.GetState(_controllerContext); + _filters = cacheEntry.Filters; + _executor = cacheEntry.ActionMethodExecutor; + _cursor = new FilterCursor(_filters); + } + + public virtual async Task InvokeAsync() + { + await InvokeAllAuthorizationFiltersAsync(); + + // 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) { - throw new ArgumentException( - Resources.FormatPropertyOfTypeCannotBeNull( - nameof(descriptor.MethodInfo), - typeof(ControllerActionDescriptor)), - nameof(descriptor)); + await InvokeResultAsync(_authorizationContext.Result); + return; + } + + try + { + await InvokeAllResourceFiltersAsync(); + } + 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(); + } } } - protected override object CreateInstance() + private Task InvokeAllAuthorizationFiltersAsync() { - return _controllerFactory.CreateController(Context); + _cursor.Reset(); + + _authorizationContext = new AuthorizationFilterContext(_controllerContext, _filters); + return InvokeAuthorizationFilterAsync(); } - protected override void ReleaseInstance(object instance) + private async Task InvokeAuthorizationFilterAsync() { - _controllerFactory.ReleaseController(Context, instance); - } + // We should never get here if we already have a result. + Debug.Assert(_authorizationContext != null); + Debug.Assert(_authorizationContext.Result == null); - protected override async Task InvokeActionAsync(ActionExecutingContext actionExecutingContext) - { - if (actionExecutingContext == null) + var current = _cursor.GetNextFilter(); + if (current.FilterAsync != null) { - throw new ArgumentNullException(nameof(actionExecutingContext)); + _diagnosticSource.BeforeOnAuthorizationAsync(_authorizationContext, current.FilterAsync); + + await current.FilterAsync.OnAuthorizationAsync(_authorizationContext); + + _diagnosticSource.AfterOnAuthorizationAsync(_authorizationContext, current.FilterAsync); + + 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); + + current.Filter.OnAuthorization(_authorizationContext); + + _diagnosticSource.AfterOnAuthorization(_authorizationContext, current.Filter); + + 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. + } + } + + private Task InvokeAllResourceFiltersAsync() + { + _cursor.Reset(); + + _resourceExecutingContext = new ResourceExecutingContext(_controllerContext, _filters); + return InvokeResourceFilterAsync(); + } + + private async Task InvokeResourceFilterAwaitedAsync() + { + Debug.Assert(_resourceExecutingContext != null); + + if (_resourceExecutingContext.Result != null) + { + // If we get here, it means that an async filter set a result AND called next(). This is forbidden. + var message = Resources.FormatAsyncResourceFilter_InvalidShortCircuit( + typeof(IAsyncResourceFilter).Name, + nameof(ResourceExecutingContext.Result), + typeof(ResourceExecutingContext).Name, + typeof(ResourceExecutionDelegate).Name); + + throw new InvalidOperationException(message); } - var actionMethodInfo = _descriptor.MethodInfo; + await InvokeResourceFilterAsync(); - var methodExecutor = GetControllerActionMethodExecutor(); - - var arguments = ControllerActionExecutor.PrepareArguments( - actionExecutingContext.ActionArguments, - actionMethodInfo.GetParameters()); - - Logger.ActionMethodExecuting(actionExecutingContext, arguments); - - var actionReturnValue = await ControllerActionExecutor.ExecuteAsync( - methodExecutor, - actionExecutingContext.Controller, - arguments); - - var actionResult = CreateActionResult( - actionMethodInfo.ReturnType, - actionReturnValue); - - Logger.ActionMethodExecuted(actionExecutingContext, actionResult); - - return actionResult; + Debug.Assert(_resourceExecutedContext != null); + return _resourceExecutedContext; } - protected override Task BindActionArgumentsAsync(IDictionary arguments) + private async Task InvokeResourceFilterAsync() { - if (arguments == null) + Debug.Assert(_resourceExecutingContext != null); + + var item = _cursor.GetNextFilter(); + try { - throw new ArgumentNullException(nameof(arguments)); + if (item.FilterAsync != null) + { + _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) + { + // 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, + }; + } + } + } + catch (Exception exception) + { + _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) + }; } - return _argumentBinder.BindArgumentsAsync(Context, Instance, arguments); + Debug.Assert(_resourceExecutedContext != 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) + { + _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) + { + _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) + { + _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) + { + _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() + { + Debug.Assert(_actionExecutingContext != null); + if (_actionExecutingContext.Result != null) + { + // If we get here, it means that an async filter set a result AND called next(). This is forbidden. + var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit( + typeof(IAsyncActionFilter).Name, + nameof(ActionExecutingContext.Result), + typeof(ActionExecutingContext).Name, + typeof(ActionExecutionDelegate).Name); + + throw new InvalidOperationException(message); + } + + await InvokeActionFilterAsync(); + + Debug.Assert(_actionExecutedContext != null); + return _actionExecutedContext; + } + + private async Task InvokeActionFilterAsync() + { + Debug.Assert(_actionExecutingContext != null); + + var item = _cursor.GetNextFilter(); + try + { + if (item.FilterAsync != null) + { + _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); + } + else if (item.Filter != null) + { + _diagnosticSource.BeforeOnActionExecuting(_actionExecutingContext, item.Filter); + + item.Filter.OnActionExecuting(_actionExecutingContext); + + _diagnosticSource.AfterOnActionExecuting(_actionExecutingContext, item.Filter); + + if (_actionExecutingContext.Result != null) + { + // Short-circuited by setting a result. + _logger.ActionFilterShortCircuited(item.Filter); + + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + _controller) + { + Canceled = true, + Result = _actionExecutingContext.Result, + }; + } + else + { + await InvokeActionFilterAsync(); + Debug.Assert(_actionExecutedContext != null); + + _diagnosticSource.BeforeOnActionExecuted(_actionExecutedContext, item.Filter); + + item.Filter.OnActionExecuted(_actionExecutedContext); + + _diagnosticSource.BeforeOnActionExecuted(_actionExecutedContext, item.Filter); + } + } + else + { + // All action filters have run, execute the action method. + IActionResult result = null; + + try + { + _diagnosticSource.BeforeActionMethod( + _controllerContext, + _actionExecutingContext.ActionArguments, + _actionExecutingContext.Controller); + + var actionMethodInfo = _controllerContext.ActionDescriptor.MethodInfo; + + var arguments = ControllerActionExecutor.PrepareArguments( + _actionExecutingContext.ActionArguments, + actionMethodInfo.GetParameters()); + + _logger.ActionMethodExecuting(_actionExecutingContext, arguments); + + var actionReturnValue = await ControllerActionExecutor.ExecuteAsync( + _executor, + _controller, + arguments); + + result = CreateActionResult( actionMethodInfo.ReturnType, actionReturnValue); + + _logger.ActionMethodExecuted(_actionExecutingContext, result); + } + finally + { + _diagnosticSource.AfterActionMethod( + _controllerContext, + _actionExecutingContext.ActionArguments, + _actionExecutingContext.Controller, + result); + } + + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + _controller) + { + Result = result + }; + } + } + catch (Exception exception) + { + // Exceptions thrown by the action method OR filters bubble back up through ActionExcecutedContext. + _actionExecutedContext = new ActionExecutedContext( + _actionExecutingContext, + _filters, + _controller) + { + ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) + }; + } + + Debug.Assert(_actionExecutedContext != 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() + { + 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 InvokeResultFilterAsync(); + + 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); + + try + { + await result.ExecuteResultAsync(_controllerContext); + } + finally + { + _diagnosticSource.AfterActionResult(_controllerContext, result); + } } // Marking as internal for Unit Testing purposes. @@ -163,5 +815,82 @@ namespace Microsoft.AspNetCore.Mvc.Internal return genericType?.GenericTypeArguments[0]; } + + /// + /// A one-way cursor for filters. + /// + /// + /// This will iterate the filter collection once per-stage, and skip any filters that don't have + /// the one of interfaces that applies to the current stage. + /// + /// Filters are always executed in the following order, but short circuiting plays a role. + /// + /// Indentation reflects nesting. + /// + /// 1. Exception Filters + /// 2. Authorization Filters + /// 3. Action Filters + /// Action + /// + /// 4. Result Filters + /// Result + /// + /// + private struct FilterCursor + { + private int _index; + private readonly IFilterMetadata[] _filters; + + public FilterCursor(int index, IFilterMetadata[] filters) + { + _index = index; + _filters = filters; + } + + public FilterCursor(IFilterMetadata[] filters) + { + _index = 0; + _filters = filters; + } + + public void Reset() + { + _index = 0; + } + + public FilterCursorItem GetNextFilter() + where TFilter : class + where TFilterAsync : class + { + while (_index < _filters.Length) + { + var filter = _filters[_index] as TFilter; + var filterAsync = _filters[_index] as TFilterAsync; + + _index += 1; + + if (filter != null || filterAsync != null) + { + return new FilterCursorItem(_index, filter, filterAsync); + } + } + + return default(FilterCursorItem); + } + } + + private struct FilterCursorItem + { + public readonly int Index; + public readonly TFilter Filter; + public readonly TFilterAsync FilterAsync; + + public FilterCursorItem(int index, TFilter filter, TFilterAsync filterAsync) + { + Index = index; + Filter = filter; + FilterAsync = filterAsync; + } + } } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs index f27f5b2697..8114594a1a 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ControllerActionInvokerProvider.cs @@ -58,14 +58,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal if (actionDescriptor != null) { context.Result = new ControllerActionInvoker( - context.ActionContext, _controllerActionInvokerCache, _controllerFactory, - actionDescriptor, _argumentBinder, - _valueProviderFactories, _logger, _diagnosticSource, + context.ActionContext, + _valueProviderFactories, _maxModelValidationErrors); } } diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs deleted file mode 100644 index f46f738785..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/FilterActionInvoker.cs +++ /dev/null @@ -1,845 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Runtime.ExceptionServices; -using System.Threading.Tasks; -using Microsoft.AspNetCore.Mvc.Abstractions; -using Microsoft.AspNetCore.Mvc.Core; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.ModelBinding; -using Microsoft.Extensions.Logging; - -namespace Microsoft.AspNetCore.Mvc.Internal -{ - public abstract class FilterActionInvoker : IActionInvoker - { - private readonly ControllerActionInvokerCache _controllerActionInvokerCache; - - private readonly DiagnosticSource _diagnosticSource; - private readonly int _maxModelValidationErrors; - - private IFilterMetadata[] _filters; - private ObjectMethodExecutor _controllerActionMethodExecutor; - private FilterCursor _cursor; - - private AuthorizationFilterContext _authorizationContext; - - private ResourceExecutingContext _resourceExecutingContext; - private ResourceExecutedContext _resourceExecutedContext; - - private ExceptionContext _exceptionContext; - - private ActionExecutingContext _actionExecutingContext; - private ActionExecutedContext _actionExecutedContext; - - private ResultExecutingContext _resultExecutingContext; - private ResultExecutedContext _resultExecutedContext; - - public FilterActionInvoker( - ActionContext actionContext, - ControllerActionInvokerCache controllerActionInvokerCache, - IReadOnlyList valueProviderFactories, - ILogger logger, - DiagnosticSource diagnosticSource, - int maxModelValidationErrors) - { - if (actionContext == null) - { - throw new ArgumentNullException(nameof(actionContext)); - } - - if (controllerActionInvokerCache == null) - { - throw new ArgumentNullException(nameof(controllerActionInvokerCache)); - } - - if (valueProviderFactories == null) - { - throw new ArgumentNullException(nameof(valueProviderFactories)); - } - - if (logger == null) - { - throw new ArgumentNullException(nameof(logger)); - } - - if (diagnosticSource == null) - { - throw new ArgumentNullException(nameof(diagnosticSource)); - } - - - _controllerActionInvokerCache = controllerActionInvokerCache; - Logger = logger; - _diagnosticSource = diagnosticSource; - _maxModelValidationErrors = maxModelValidationErrors; - - Context = new ControllerContext(actionContext); - Context.ModelState.MaxAllowedErrors = _maxModelValidationErrors; - - // PERF: These are rarely going to be changed, so let's go copy-on-write. - Context.ValueProviderFactories = new CopyOnWriteList(valueProviderFactories); - } - - protected ControllerContext Context { get; } - - protected object Instance { get; private set; } - - protected ILogger Logger { get; } - - /// - /// Called to create an instance of an object which will act as the reciever of the action invocation. - /// - /// The constructed instance or null. - protected abstract object CreateInstance(); - - /// - /// Called to create an instance of an object which will act as the reciever of the action invocation. - /// - /// The instance to release. - /// This method will not be called if returns null. - protected abstract void ReleaseInstance(object instance); - - protected abstract Task InvokeActionAsync(ActionExecutingContext actionExecutingContext); - - protected abstract Task BindActionArgumentsAsync(IDictionary arguments); - - public virtual async Task InvokeAsync() - { - var controllerActionInvokerState = _controllerActionInvokerCache.GetState(Context); - _filters = controllerActionInvokerState.Filters; - _controllerActionMethodExecutor = controllerActionInvokerState.ActionMethodExecutor; - _cursor = new FilterCursor(_filters); - - await InvokeAllAuthorizationFiltersAsync(); - - // 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; - } - - try - { - await InvokeAllResourceFiltersAsync(); - } - 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 (Instance != null) - { - ReleaseInstance(Instance); - } - } - - // 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(); - } - } - } - - protected ObjectMethodExecutor GetControllerActionMethodExecutor() - { - return _controllerActionMethodExecutor; - } - - private Task InvokeAllAuthorizationFiltersAsync() - { - _cursor.Reset(); - - _authorizationContext = new AuthorizationFilterContext(Context, _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) - { - _diagnosticSource.BeforeOnAuthorizationAsync(_authorizationContext, current.FilterAsync); - - await current.FilterAsync.OnAuthorizationAsync(_authorizationContext); - - _diagnosticSource.AfterOnAuthorizationAsync(_authorizationContext, current.FilterAsync); - - 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); - - current.Filter.OnAuthorization(_authorizationContext); - - _diagnosticSource.AfterOnAuthorization(_authorizationContext, current.Filter); - - 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. - } - } - - private Task InvokeAllResourceFiltersAsync() - { - _cursor.Reset(); - - _resourceExecutingContext = new ResourceExecutingContext(Context, _filters); - return InvokeResourceFilterAsync(); - } - - private async Task InvokeResourceFilterAwaitedAsync() - { - Debug.Assert(_resourceExecutingContext != null); - - if (_resourceExecutingContext.Result != null) - { - // If we get here, it means that an async filter set a result AND called next(). This is forbidden. - var message = Resources.FormatAsyncResourceFilter_InvalidShortCircuit( - typeof(IAsyncResourceFilter).Name, - nameof(ResourceExecutingContext.Result), - typeof(ResourceExecutingContext).Name, - typeof(ResourceExecutionDelegate).Name); - - throw new InvalidOperationException(message); - } - - await InvokeResourceFilterAsync(); - - Debug.Assert(_resourceExecutedContext != null); - return _resourceExecutedContext; - } - - private async Task InvokeResourceFilterAsync() - { - Debug.Assert(_resourceExecutingContext != null); - - var item = _cursor.GetNextFilter(); - try - { - if (item.FilterAsync != null) - { - _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) - { - // 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, - }; - } - } - } - catch (Exception exception) - { - _resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters) - { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) - }; - } - - Debug.Assert(_resourceExecutedContext != 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) - { - _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) - { - 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) - { - _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) - { - 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(Context, _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(); - - Instance = CreateInstance(); - - var arguments = new Dictionary(StringComparer.OrdinalIgnoreCase); - await BindActionArgumentsAsync(arguments); - _actionExecutingContext = new ActionExecutingContext(Context, _filters, arguments, Instance); - - await InvokeActionFilterAsync(); - } - - private async Task InvokeActionFilterAwaitedAsync() - { - Debug.Assert(_actionExecutingContext != null); - if (_actionExecutingContext.Result != null) - { - // If we get here, it means that an async filter set a result AND called next(). This is forbidden. - var message = Resources.FormatAsyncActionFilter_InvalidShortCircuit( - typeof(IAsyncActionFilter).Name, - nameof(ActionExecutingContext.Result), - typeof(ActionExecutingContext).Name, - typeof(ActionExecutionDelegate).Name); - - throw new InvalidOperationException(message); - } - - await InvokeActionFilterAsync(); - - Debug.Assert(_actionExecutedContext != null); - return _actionExecutedContext; - } - - private async Task InvokeActionFilterAsync() - { - Debug.Assert(_actionExecutingContext != null); - - var item = _cursor.GetNextFilter(); - try - { - if (item.FilterAsync != null) - { - _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, - Instance) - { - Canceled = true, - Result = _actionExecutingContext.Result, - }; - } - - _diagnosticSource.AfterOnActionExecution(_actionExecutedContext, item.FilterAsync); - } - else if (item.Filter != null) - { - _diagnosticSource.BeforeOnActionExecuting(_actionExecutingContext, item.Filter); - - item.Filter.OnActionExecuting(_actionExecutingContext); - - _diagnosticSource.AfterOnActionExecuting(_actionExecutingContext, item.Filter); - - if (_actionExecutingContext.Result != null) - { - // Short-circuited by setting a result. - Logger.ActionFilterShortCircuited(item.Filter); - - _actionExecutedContext = new ActionExecutedContext( - _actionExecutingContext, - _filters, - Instance) - { - Canceled = true, - Result = _actionExecutingContext.Result, - }; - } - else - { - await InvokeActionFilterAsync(); - Debug.Assert(_actionExecutedContext != null); - - _diagnosticSource.BeforeOnActionExecuted(_actionExecutedContext, item.Filter); - - item.Filter.OnActionExecuted(_actionExecutedContext); - - _diagnosticSource.BeforeOnActionExecuted(_actionExecutedContext, item.Filter); - } - } - else - { - // All action filters have run, execute the action method. - IActionResult result = null; - - try - { - _diagnosticSource.BeforeActionMethod( - Context, - _actionExecutingContext.ActionArguments, - _actionExecutingContext.Controller); - - result = await InvokeActionAsync(_actionExecutingContext); - } - finally - { - _diagnosticSource.AfterActionMethod( - Context, - _actionExecutingContext.ActionArguments, - _actionExecutingContext.Controller, - result); - } - - _actionExecutedContext = new ActionExecutedContext( - _actionExecutingContext, - _filters, - Instance) - { - Result = result - }; - } - } - catch (Exception exception) - { - // Exceptions thrown by the action method OR filters bubble back up through ActionExcecutedContext. - _actionExecutedContext = new ActionExecutedContext( - _actionExecutingContext, - _filters, - Instance) - { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) - }; - } - - Debug.Assert(_actionExecutedContext != null); - } - - private async Task InvokeAllResultFiltersAsync(IActionResult result) - { - _cursor.Reset(); - - _resultExecutingContext = new ResultExecutingContext(Context, _filters, result, Instance); - 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() - { - 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 InvokeResultFilterAsync(); - - 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, - Instance) - { - 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, - Instance) - { - 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, - Instance); - } - } - catch (Exception exception) - { - _resultExecutedContext = new ResultExecutedContext( - _resultExecutingContext, - _filters, - _resultExecutingContext.Result, - Instance) - { - ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception) - }; - } - - Debug.Assert(_resultExecutedContext != null); - } - - private async Task InvokeResultAsync(IActionResult result) - { - _diagnosticSource.BeforeActionResult(Context, result); - - try - { - await result.ExecuteResultAsync(Context); - } - finally - { - _diagnosticSource.AfterActionResult(Context, result); - } - } - - /// - /// A one-way cursor for filters. - /// - /// - /// This will iterate the filter collection once per-stage, and skip any filters that don't have - /// the one of interfaces that applies to the current stage. - /// - /// Filters are always executed in the following order, but short circuiting plays a role. - /// - /// Indentation reflects nesting. - /// - /// 1. Exception Filters - /// 2. Authorization Filters - /// 3. Action Filters - /// Action - /// - /// 4. Result Filters - /// Result - /// - /// - private struct FilterCursor - { - private int _index; - private readonly IFilterMetadata[] _filters; - - public FilterCursor(int index, IFilterMetadata[] filters) - { - _index = index; - _filters = filters; - } - - public FilterCursor(IFilterMetadata[] filters) - { - _index = 0; - _filters = filters; - } - - public void Reset() - { - _index = 0; - } - - public FilterCursorItem GetNextFilter() - where TFilter : class - where TFilterAsync : class - { - while (_index < _filters.Length) - { - var filter = _filters[_index] as TFilter; - var filterAsync = _filters[_index] as TFilterAsync; - - _index += 1; - - if (filter != null || filterAsync != null) - { - return new FilterCursorItem(_index, filter, filterAsync); - } - } - - return default(FilterCursorItem); - } - } - - private struct FilterCursorItem - { - public readonly int Index; - public readonly TFilter Filter; - public readonly TFilterAsync FilterAsync; - - public FilterCursorItem(int index, TFilter filter, TFilterAsync filterAsync) - { - Index = index; - Filter = filter; - FilterAsync = filterAsync; - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs index 48828ef0f1..1e0833c753 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Internal/ControllerActionInvokerTest.cs @@ -2050,14 +2050,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal .Returns(-1000); var invoker = new TestControllerActionInvoker( - actionContext, new[] { filterProvider.Object }, new MockControllerFactory(this), - actionDescriptor, argumentBinder.Object, - new IValueProviderFactory[0], new NullLoggerFactory().CreateLogger(), new DiagnosticListener("Microsoft.AspNetCore"), + actionContext, + new IValueProviderFactory[0], maxAllowedErrorsInModelState); return invoker; } @@ -2100,18 +2099,19 @@ namespace Microsoft.AspNetCore.Mvc.Internal var metadataProvider = new EmptyModelMetadataProvider(); - var invoker = new ControllerActionInvoker( - actionContext, - CreateFilterCache(), - controllerFactory.Object, - actionDescriptor, - new ControllerArgumentBinder( + var argumentBinder = new ControllerArgumentBinder( metadataProvider, TestModelBinderFactory.CreateDefault(metadataProvider), - new DefaultObjectValidator(metadataProvider, new IModelValidatorProvider[0])), - new IValueProviderFactory[0], + new DefaultObjectValidator(metadataProvider, new IModelValidatorProvider[0])); + + var invoker = new ControllerActionInvoker( + CreateFilterCache(), + controllerFactory.Object, + argumentBinder, new NullLoggerFactory().CreateLogger(), new DiagnosticListener("Microsoft.AspNetCore"), + actionContext, + new IValueProviderFactory[0], 200); // Act @@ -2226,24 +2226,22 @@ namespace Microsoft.AspNetCore.Mvc.Internal private class TestControllerActionInvoker : ControllerActionInvoker { public TestControllerActionInvoker( - ActionContext actionContext, IFilterProvider[] filterProviders, MockControllerFactory controllerFactory, - ControllerActionDescriptor descriptor, IControllerArgumentBinder argumentBinder, - IReadOnlyList valueProviderFactories, ILogger logger, DiagnosticSource diagnosticSource, + ActionContext actionContext, + IReadOnlyList valueProviderFactories, int maxAllowedErrorsInModelState) : base( - actionContext, CreateFilterCache(filterProviders), controllerFactory, - descriptor, argumentBinder, - valueProviderFactories, logger, diagnosticSource, + actionContext, + valueProviderFactories, maxAllowedErrorsInModelState) { ControllerFactory = controllerFactory;