936 lines
38 KiB
C#
936 lines
38 KiB
C#
// 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;
|
|
#if NETSTANDARD1_6
|
|
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;
|
|
using Microsoft.AspNetCore.Mvc.ModelBinding;
|
|
using Microsoft.Extensions.Logging;
|
|
|
|
namespace Microsoft.AspNetCore.Mvc.Internal
|
|
{
|
|
public class ControllerActionInvoker : IActionInvoker
|
|
{
|
|
private readonly IControllerFactory _controllerFactory;
|
|
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(
|
|
ControllerActionInvokerCache cache,
|
|
IControllerFactory controllerFactory,
|
|
IControllerArgumentBinder controllerArgumentBinder,
|
|
ILogger logger,
|
|
DiagnosticSource diagnosticSource,
|
|
ActionContext actionContext,
|
|
IReadOnlyList<IValueProviderFactory> valueProviderFactories,
|
|
int maxModelValidationErrors)
|
|
{
|
|
if (cache == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(cache));
|
|
}
|
|
|
|
if (controllerFactory == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(controllerFactory));
|
|
}
|
|
|
|
if (controllerArgumentBinder == null)
|
|
{
|
|
throw new ArgumentNullException(nameof(controllerArgumentBinder));
|
|
}
|
|
|
|
if (logger == null)
|
|
{
|
|
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;
|
|
_controllerArgumentBinder = controllerArgumentBinder;
|
|
_logger = logger;
|
|
_diagnosticSource = diagnosticSource;
|
|
|
|
_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()
|
|
{
|
|
try
|
|
{
|
|
_diagnosticSource.BeforeAction(
|
|
_controllerContext.ActionDescriptor,
|
|
_controllerContext.HttpContext,
|
|
_controllerContext.RouteData);
|
|
|
|
using (_logger.ActionScope(_controllerContext.ActionDescriptor))
|
|
{
|
|
_logger.ExecutingAction(_controllerContext.ActionDescriptor);
|
|
|
|
var startTimestamp = _logger.IsEnabled(LogLevel.Information) ? Stopwatch.GetTimestamp() : 0;
|
|
|
|
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 (_controller != null)
|
|
{
|
|
_controllerFactory.ReleaseController(_controllerContext, _controller);
|
|
}
|
|
}
|
|
|
|
// We've reached the end of resource filters. If there's an unhandled exception on the context then
|
|
// it should be thrown and middleware has a chance to handle it.
|
|
Debug.Assert(_resourceExecutedContext != null);
|
|
if (_resourceExecutedContext.Exception != null && !_resourceExecutedContext.ExceptionHandled)
|
|
{
|
|
if (_resourceExecutedContext.ExceptionDispatchInfo == null)
|
|
{
|
|
throw _resourceExecutedContext.Exception;
|
|
}
|
|
else
|
|
{
|
|
_resourceExecutedContext.ExceptionDispatchInfo.Throw();
|
|
}
|
|
}
|
|
|
|
_logger.ExecutedAction(_controllerContext.ActionDescriptor, startTimestamp);
|
|
}
|
|
}
|
|
finally
|
|
{
|
|
_diagnosticSource.AfterAction(
|
|
_controllerContext.ActionDescriptor,
|
|
_controllerContext.HttpContext,
|
|
_controllerContext.RouteData);
|
|
}
|
|
}
|
|
|
|
private Task InvokeAllAuthorizationFiltersAsync()
|
|
{
|
|
_cursor.Reset();
|
|
|
|
_authorizationContext = new AuthorizationFilterContext(_controllerContext, _filters);
|
|
return InvokeAuthorizationFilterAsync();
|
|
}
|
|
|
|
private async Task InvokeAuthorizationFilterAsync()
|
|
{
|
|
// We should never get here if we already have a result.
|
|
Debug.Assert(_authorizationContext != null);
|
|
Debug.Assert(_authorizationContext.Result == null);
|
|
|
|
var current = _cursor.GetNextFilter<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(
|
|
_controllerContext,
|
|
_filters,
|
|
_controllerContext.ValueProviderFactories);
|
|
|
|
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 && !_exceptionContext.ExceptionHandled)
|
|
{
|
|
// If we get here, this means that we have an unhandled exception.
|
|
// Exception filted didn't handle this, so send it on to resource filters.
|
|
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters);
|
|
|
|
// Preserve the stack trace if possible.
|
|
_resourceExecutedContext.Exception = _exceptionContext.Exception;
|
|
if (_exceptionContext.ExceptionDispatchInfo != null)
|
|
{
|
|
_resourceExecutedContext.ExceptionDispatchInfo = _exceptionContext.ExceptionDispatchInfo;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We have a successful 'result' from the action or an Action Filter, so run
|
|
// Result Filters.
|
|
Debug.Assert(_actionExecutedContext != null);
|
|
var result = _actionExecutedContext.Result;
|
|
|
|
// >> ResultFilters >> (Result)
|
|
await InvokeAllResultFiltersAsync(result);
|
|
|
|
_resourceExecutedContext = new ResourceExecutedContext(_resourceExecutingContext, _filters)
|
|
{
|
|
Result = _resultExecutedContext.Result,
|
|
};
|
|
}
|
|
}
|
|
}
|
|
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 && !_exceptionContext.ExceptionHandled)
|
|
{
|
|
_diagnosticSource.BeforeOnExceptionAsync(_exceptionContext, current.FilterAsync);
|
|
|
|
// Exception filters only run when there's an exception - unsetting it will short-circuit
|
|
// other exception filters.
|
|
await current.FilterAsync.OnExceptionAsync(_exceptionContext);
|
|
|
|
_diagnosticSource.AfterOnExceptionAsync(_exceptionContext, current.FilterAsync);
|
|
|
|
if (_exceptionContext.Exception == null || _exceptionContext.ExceptionHandled)
|
|
{
|
|
_logger.ExceptionFilterShortCircuited(current.FilterAsync);
|
|
}
|
|
}
|
|
}
|
|
else if (current.Filter != null)
|
|
{
|
|
// Exception filters run "on the way out" - so the filter is run after the rest of the
|
|
// pipeline.
|
|
await InvokeExceptionFilterAsync();
|
|
|
|
Debug.Assert(_exceptionContext != null);
|
|
if (_exceptionContext.Exception != null && !_exceptionContext.ExceptionHandled)
|
|
{
|
|
_diagnosticSource.BeforeOnException(_exceptionContext, current.Filter);
|
|
|
|
// Exception filters only run when there's an exception - unsetting it will short-circuit
|
|
// other exception filters.
|
|
current.Filter.OnException(_exceptionContext);
|
|
|
|
_diagnosticSource.AfterOnException(_exceptionContext, current.Filter);
|
|
|
|
if (_exceptionContext.Exception == null || _exceptionContext.ExceptionHandled)
|
|
{
|
|
_logger.ExceptionFilterShortCircuited(current.Filter);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We've reached the 'end' of the exception filter pipeline - this means that one stack frame has
|
|
// been built for each exception. When we return from here, these frames will either:
|
|
//
|
|
// 1) Call the filter (if we have an exception)
|
|
// 2) No-op (if we don't have an exception)
|
|
Debug.Assert(_exceptionContext == null);
|
|
_exceptionContext = new ExceptionContext(_controllerContext, _filters);
|
|
|
|
try
|
|
{
|
|
await InvokeAllActionFiltersAsync();
|
|
|
|
// Action filters might 'return' an unhandled exception instead of throwing
|
|
Debug.Assert(_actionExecutedContext != null);
|
|
if (_actionExecutedContext.Exception != null && !_actionExecutedContext.ExceptionHandled)
|
|
{
|
|
_exceptionContext.Exception = _actionExecutedContext.Exception;
|
|
if (_actionExecutedContext.ExceptionDispatchInfo != null)
|
|
{
|
|
_exceptionContext.ExceptionDispatchInfo = _actionExecutedContext.ExceptionDispatchInfo;
|
|
}
|
|
}
|
|
}
|
|
catch (Exception exception)
|
|
{
|
|
_exceptionContext.ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception);
|
|
}
|
|
}
|
|
}
|
|
|
|
private async Task InvokeAllActionFiltersAsync()
|
|
{
|
|
_cursor.Reset();
|
|
|
|
_controller = _controllerFactory.CreateController(_controllerContext);
|
|
|
|
var arguments = new Dictionary<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,
|
|
_executor);
|
|
|
|
_logger.ActionMethodExecuting(_actionExecutingContext, arguments);
|
|
|
|
var returnType = _executor.MethodReturnType;
|
|
|
|
if (returnType == typeof(void))
|
|
{
|
|
_executor.Execute(_controller, arguments);
|
|
result = new EmptyResult();
|
|
}
|
|
else if (returnType == typeof(Task))
|
|
{
|
|
await (Task)_executor.Execute(_controller, arguments);
|
|
result = new EmptyResult();
|
|
}
|
|
else if (_executor.TaskGenericType == typeof(IActionResult))
|
|
{
|
|
result = await (Task<IActionResult>)_executor.Execute(_controller, arguments);
|
|
if (result == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
Resources.FormatActionResult_ActionReturnValueCannotBeNull(typeof(IActionResult)));
|
|
}
|
|
}
|
|
else if (_executor.IsTypeAssignableFromIActionResult)
|
|
{
|
|
if (_executor.IsMethodAsync)
|
|
{
|
|
result = (IActionResult)await _executor.ExecuteAsync(_controller, arguments);
|
|
}
|
|
else
|
|
{
|
|
result = (IActionResult)_executor.Execute(_controller, arguments);
|
|
}
|
|
|
|
if (result == null)
|
|
{
|
|
throw new InvalidOperationException(
|
|
Resources.FormatActionResult_ActionReturnValueCannotBeNull(_executor.TaskGenericType ?? returnType));
|
|
}
|
|
}
|
|
else if (!_executor.IsMethodAsync)
|
|
{
|
|
var resultAsObject = _executor.Execute(_controller, arguments);
|
|
result = new ObjectResult(resultAsObject)
|
|
{
|
|
DeclaredType = returnType,
|
|
};
|
|
}
|
|
else if (_executor.TaskGenericType != null)
|
|
{
|
|
var resultAsObject = await _executor.ExecuteAsync(_controller, arguments);
|
|
result = new ObjectResult(resultAsObject)
|
|
{
|
|
DeclaredType = _executor.TaskGenericType,
|
|
};
|
|
}
|
|
else
|
|
{
|
|
// This will be the case for types which have derived from Task and Task<T> or non Task types.
|
|
throw new InvalidOperationException(Resources.FormatActionExecutor_UnexpectedTaskInstance(
|
|
_executor.MethodInfo.Name,
|
|
_executor.MethodInfo.DeclaringType));
|
|
}
|
|
|
|
_logger.ActionMethodExecuted(_actionExecutingContext, result);
|
|
}
|
|
finally
|
|
{
|
|
_diagnosticSource.AfterActionMethod(
|
|
_controllerContext,
|
|
_actionExecutingContext.ActionArguments,
|
|
_actionExecutingContext.Controller,
|
|
result);
|
|
}
|
|
|
|
_actionExecutedContext = new ActionExecutedContext(
|
|
_actionExecutingContext,
|
|
_filters,
|
|
_controller)
|
|
{
|
|
Result = result
|
|
};
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
|
|
/// <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;
|
|
}
|
|
}
|
|
}
|
|
}
|