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.
This commit is contained in:
Ryan Nowak 2016-05-13 16:54:53 -07:00
parent 51e133ab9f
commit 1e02fd2e6b
4 changed files with 807 additions and 926 deletions

View File

@ -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<IValueProviderFactory> valueProviderFactories,
IControllerArgumentBinder controllerArgumentBinder,
ILogger logger,
DiagnosticSource diagnosticSource,
ActionContext actionContext,
IReadOnlyList<IValueProviderFactory> 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<IValueProviderFactory>(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<IActionResult> InvokeActionAsync(ActionExecutingContext actionExecutingContext)
{
if (actionExecutingContext == null)
var current = _cursor.GetNextFilter<IAuthorizationFilter, IAsyncAuthorizationFilter>();
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<ResourceExecutedContext> 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<string, object> arguments)
private async Task InvokeResourceFilterAsync()
{
if (arguments == null)
Debug.Assert(_resourceExecutingContext != null);
var item = _cursor.GetNextFilter<IResourceFilter, IAsyncResourceFilter>();
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<IExceptionFilter, IAsyncExceptionFilter>();
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<string, object>(StringComparer.OrdinalIgnoreCase);
await _controllerArgumentBinder.BindArgumentsAsync(_controllerContext, _controller, arguments);
_actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, arguments, _controller);
await InvokeActionFilterAsync();
}
private async Task<ActionExecutedContext> 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<IActionFilter, IAsyncActionFilter>();
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<ResultExecutedContext> 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<IResultFilter, IAsyncResultFilter>();
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];
}
/// <summary>
/// A one-way cursor for filters.
/// </summary>
/// <remarks>
/// 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
///
/// </remarks>
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<TFilter, TFilterAsync> GetNextFilter<TFilter, TFilterAsync>()
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<TFilter, TFilterAsync>(_index, filter, filterAsync);
}
}
return default(FilterCursorItem<TFilter, TFilterAsync>);
}
}
private struct FilterCursorItem<TFilter, TFilterAsync>
{
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;
}
}
}
}

View File

@ -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);
}
}

View File

@ -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<IValueProviderFactory> 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<IValueProviderFactory>(valueProviderFactories);
}
protected ControllerContext Context { get; }
protected object Instance { get; private set; }
protected ILogger Logger { get; }
/// <summary>
/// Called to create an instance of an object which will act as the reciever of the action invocation.
/// </summary>
/// <returns>The constructed instance or <c>null</c>.</returns>
protected abstract object CreateInstance();
/// <summary>
/// Called to create an instance of an object which will act as the reciever of the action invocation.
/// </summary>
/// <param name="instance">The instance to release.</param>
/// <remarks>This method will not be called if <see cref="CreateInstance"/> returns <c>null</c>.</remarks>
protected abstract void ReleaseInstance(object instance);
protected abstract Task<IActionResult> InvokeActionAsync(ActionExecutingContext actionExecutingContext);
protected abstract Task BindActionArgumentsAsync(IDictionary<string, object> 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<IAuthorizationFilter, IAsyncAuthorizationFilter>();
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<ResourceExecutedContext> 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<IResourceFilter, IAsyncResourceFilter>();
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<IExceptionFilter, IAsyncExceptionFilter>();
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<string, object>(StringComparer.OrdinalIgnoreCase);
await BindActionArgumentsAsync(arguments);
_actionExecutingContext = new ActionExecutingContext(Context, _filters, arguments, Instance);
await InvokeActionFilterAsync();
}
private async Task<ActionExecutedContext> 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<IActionFilter, IAsyncActionFilter>();
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<ResultExecutedContext> 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<IResultFilter, IAsyncResultFilter>();
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);
}
}
/// <summary>
/// A one-way cursor for filters.
/// </summary>
/// <remarks>
/// 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
///
/// </remarks>
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<TFilter, TFilterAsync> GetNextFilter<TFilter, TFilterAsync>()
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<TFilter, TFilterAsync>(_index, filter, filterAsync);
}
}
return default(FilterCursorItem<TFilter, TFilterAsync>);
}
}
private struct FilterCursorItem<TFilter, TFilterAsync>
{
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;
}
}
}
}

View File

@ -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<ControllerActionInvoker>(),
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<ControllerActionInvoker>(),
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<IValueProviderFactory> valueProviderFactories,
ILogger logger,
DiagnosticSource diagnosticSource,
ActionContext actionContext,
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
int maxAllowedErrorsInModelState)
: base(
actionContext,
CreateFilterCache(filterProviders),
controllerFactory,
descriptor,
argumentBinder,
valueProviderFactories,
logger,
diagnosticSource,
actionContext,
valueProviderFactories,
maxAllowedErrorsInModelState)
{
ControllerFactory = controllerFactory;