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;