Move exception and result filters in base

This commit is contained in:
Ryan Nowak 2017-05-25 22:04:19 -07:00
parent e7bd6cfc06
commit 84e007a2a7
4 changed files with 1169 additions and 1174 deletions

View File

@ -18,18 +18,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
private readonly ControllerActionInvokerCacheEntry _cacheEntry;
private readonly ControllerContext _controllerContext;
private object _controller;
private Dictionary<string, object> _arguments;
private ExceptionContext _exceptionContext;
private ActionExecutingContext _actionExecutingContext;
private ActionExecutedContext _actionExecutedContext;
private ResultExecutingContext _resultExecutingContext;
private ResultExecutedContext _resultExecutedContext;
internal ControllerActionInvoker(
ILogger logger,
DiagnosticSource diagnosticSource,
@ -52,9 +46,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
protected override void ReleaseResources()
{
if (_controller != null && _cacheEntry.ControllerReleaser != null)
if (_instance != null && _cacheEntry.ControllerReleaser != null)
{
_cacheEntry.ControllerReleaser(_controllerContext, _controller);
_cacheEntry.ControllerReleaser(_controllerContext, _instance);
}
}
@ -65,214 +59,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
switch (next)
{
case State.ResourceInsideBegin:
{
goto case State.ExceptionBegin;
}
case State.ExceptionBegin:
{
_cursor.Reset();
goto case State.ExceptionNext;
}
case State.ExceptionNext:
{
var current = _cursor.GetNextFilter<IExceptionFilter, IAsyncExceptionFilter>();
if (current.FilterAsync != null)
{
state = current.FilterAsync;
goto case State.ExceptionAsyncBegin;
}
else if (current.Filter != null)
{
state = current.Filter;
goto case State.ExceptionSyncBegin;
}
else if (scope == Scope.Exception)
{
// All exception filters are on the stack already - so execute the 'inside'.
goto case State.ExceptionInside;
}
else
{
// There are no exception filters - so jump right to 'inside'.
Debug.Assert(scope == Scope.Resource);
goto case State.ActionBegin;
}
}
case State.ExceptionAsyncBegin:
{
var task = InvokeNextExceptionFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionAsyncResume;
return task;
}
goto case State.ExceptionAsyncResume;
}
case State.ExceptionAsyncResume:
{
Debug.Assert(state != null);
var filter = (IAsyncExceptionFilter)state;
var exceptionContext = _exceptionContext;
// When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
// we'll call the filter. Otherwise there's nothing to do.
if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
{
_diagnosticSource.BeforeOnExceptionAsync(exceptionContext, filter);
var task = filter.OnExceptionAsync(exceptionContext);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionAsyncEnd;
return task;
}
goto case State.ExceptionAsyncEnd;
}
goto case State.ExceptionEnd;
}
case State.ExceptionAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_exceptionContext != null);
var filter = (IAsyncExceptionFilter)state;
var exceptionContext = _exceptionContext;
_diagnosticSource.AfterOnExceptionAsync(exceptionContext, filter);
if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
{
// We don't need to do anthing to trigger a short circuit. If there's another
// exception filter on the stack it will check the same set of conditions
// and then just skip itself.
_logger.ExceptionFilterShortCircuited(filter);
}
goto case State.ExceptionEnd;
}
case State.ExceptionSyncBegin:
{
var task = InvokeNextExceptionFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionSyncEnd;
return task;
}
goto case State.ExceptionSyncEnd;
}
case State.ExceptionSyncEnd:
{
Debug.Assert(state != null);
var filter = (IExceptionFilter)state;
var exceptionContext = _exceptionContext;
// When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
// we'll call the filter. Otherwise there's nothing to do.
if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
{
_diagnosticSource.BeforeOnException(exceptionContext, filter);
filter.OnException(exceptionContext);
_diagnosticSource.AfterOnException(exceptionContext, filter);
if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
{
// We don't need to do anthing to trigger a short circuit. If there's another
// exception filter on the stack it will check the same set of conditions
// and then just skip itself.
_logger.ExceptionFilterShortCircuited(filter);
}
}
goto case State.ExceptionEnd;
}
case State.ExceptionInside:
{
goto case State.ActionBegin;
}
case State.ExceptionHandled:
{
// We arrive in this state when an exception happened, but was handled by exception filters
// either by setting ExceptionHandled, or nulling out the Exception or setting a result
// on the ExceptionContext.
//
// We need to execute the result (if any) and then exit gracefully which unwinding Resource
// filters.
Debug.Assert(state != null);
Debug.Assert(_exceptionContext != null);
if (_exceptionContext.Result == null)
{
_exceptionContext.Result = new EmptyResult();
}
if (scope == Scope.Resource)
{
Debug.Assert(_exceptionContext.Result != null);
_result = _exceptionContext.Result;
}
var task = InvokeResultAsync(_exceptionContext.Result);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResourceInsideEnd;
return task;
}
goto case State.ResourceInsideEnd;
}
case State.ExceptionEnd:
{
var exceptionContext = _exceptionContext;
if (scope == Scope.Exception)
{
isCompleted = true;
return TaskCache.CompletedTask;
}
if (exceptionContext != null)
{
if (exceptionContext.Result != null ||
exceptionContext.Exception == null ||
exceptionContext.ExceptionHandled)
{
goto case State.ExceptionHandled;
}
Rethrow(exceptionContext);
Debug.Fail("unreachable");
}
goto case State.ResultBegin;
}
case State.ActionBegin:
{
var controllerContext = _controllerContext;
_cursor.Reset();
_controller = _cacheEntry.ControllerFactory(controllerContext);
_instance = _cacheEntry.ControllerFactory(controllerContext);
_arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
@ -293,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
if (_actionExecutingContext == null)
{
_actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller);
_actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance);
}
state = current.FilterAsync;
@ -303,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
if (_actionExecutingContext == null)
{
_actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller);
_actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance);
}
state = current.Filter;
@ -350,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_actionExecutedContext = new ActionExecutedContext(
_controllerContext,
_filters,
_controller)
_instance)
{
Canceled = true,
Result = _actionExecutingContext.Result,
@ -384,7 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext,
_filters,
_controller)
_instance)
{
Canceled = true,
Result = _actionExecutingContext.Result,
@ -439,7 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
if (_actionExecutedContext == null)
{
_actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller)
_actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance)
{
Result = _result,
};
@ -457,206 +250,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_result = actionExecutedContext.Result;
}
if (scope == Scope.Exception)
{
// If we're inside an exception filter, let's allow those filters to 'unwind' before
// the result.
isCompleted = true;
return TaskCache.CompletedTask;
}
Debug.Assert(scope == Scope.Resource);
goto case State.ResultBegin;
}
case State.ResultBegin:
{
_cursor.Reset();
goto case State.ResultNext;
}
case State.ResultNext:
{
var current = _cursor.GetNextFilter<IResultFilter, IAsyncResultFilter>();
if (current.FilterAsync != null)
{
if (_resultExecutingContext == null)
{
_resultExecutingContext = new ResultExecutingContext(_controllerContext, _filters, _result, _controller);
}
state = current.FilterAsync;
goto case State.ResultAsyncBegin;
}
else if (current.Filter != null)
{
if (_resultExecutingContext == null)
{
_resultExecutingContext = new ResultExecutingContext(_controllerContext, _filters, _result, _controller);
}
state = current.Filter;
goto case State.ResultSyncBegin;
}
else
{
goto case State.ResultInside;
}
}
case State.ResultAsyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
var filter = (IAsyncResultFilter)state;
var resultExecutingContext = _resultExecutingContext;
_diagnosticSource.BeforeOnResultExecution(resultExecutingContext, filter);
var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultAsyncEnd;
return task;
}
goto case State.ResultAsyncEnd;
}
case State.ResultAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
var filter = (IAsyncResultFilter)state;
var resultExecutingContext = _resultExecutingContext;
var resultExecutedContext = _resultExecutedContext;
if (resultExecutedContext == null || resultExecutingContext.Cancel == true)
{
// Short-circuited by not calling next || Short-circuited by setting Cancel == true
_logger.ResourceFilterShortCircuited(filter);
_resultExecutedContext = new ResultExecutedContext(
_controllerContext,
_filters,
resultExecutingContext.Result,
_controller)
{
Canceled = true,
};
}
_diagnosticSource.AfterOnResultExecution(_resultExecutedContext, filter);
goto case State.ResultEnd;
}
case State.ResultSyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
var filter = (IResultFilter)state;
var resultExecutingContext = _resultExecutingContext;
_diagnosticSource.BeforeOnResultExecuting(resultExecutingContext, filter);
filter.OnResultExecuting(resultExecutingContext);
_diagnosticSource.AfterOnResultExecuting(resultExecutingContext, filter);
if (_resultExecutingContext.Cancel == true)
{
// Short-circuited by setting Cancel == true
_logger.ResourceFilterShortCircuited(filter);
_resultExecutedContext = new ResultExecutedContext(
resultExecutingContext,
_filters,
resultExecutingContext.Result,
_controller)
{
Canceled = true,
};
goto case State.ResultEnd;
}
var task = InvokeNextResultFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultSyncEnd;
return task;
}
goto case State.ResultSyncEnd;
}
case State.ResultSyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
Debug.Assert(_resultExecutedContext != null);
var filter = (IResultFilter)state;
var resultExecutedContext = _resultExecutedContext;
_diagnosticSource.BeforeOnResultExecuted(resultExecutedContext, filter);
filter.OnResultExecuted(resultExecutedContext);
_diagnosticSource.AfterOnResultExecuted(resultExecutedContext, filter);
goto case State.ResultEnd;
}
case State.ResultInside:
{
// If we executed result filters then we need to grab the result from there.
if (_resultExecutingContext != null)
{
_result = _resultExecutingContext.Result;
}
if (_result == null)
{
// The empty result is always flowed back as the 'executed' result if we don't have one.
_result = new EmptyResult();
}
var task = InvokeResultAsync(_result);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultEnd;
return task;
}
goto case State.ResultEnd;
}
case State.ResultEnd:
{
var result = _result;
if (scope == Scope.Result)
{
if (_resultExecutedContext == null)
{
_resultExecutedContext = new ResultExecutedContext(_controllerContext, _filters, result, _controller);
}
isCompleted = true;
return TaskCache.CompletedTask;
}
Rethrow(_resultExecutedContext);
goto case State.ResourceInsideEnd;
}
case State.ResourceInsideEnd:
{
isCompleted = true;
return TaskCache.CompletedTask;
}
@ -666,28 +259,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
private async Task InvokeNextExceptionFilterAsync()
{
try
{
var next = State.ExceptionNext;
var state = (object)null;
var scope = Scope.Exception;
var isCompleted = false;
while (!isCompleted)
{
await Next(ref next, ref scope, ref state, ref isCompleted);
}
}
catch (Exception exception)
{
_exceptionContext = new ExceptionContext(_controllerContext, _filters)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
};
}
}
private async Task InvokeNextActionFilterAsync()
{
try
@ -703,7 +274,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
catch (Exception exception)
{
_actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller)
_actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
};
@ -737,7 +308,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
var controllerContext = _controllerContext;
var executor = _cacheEntry.ActionMethodExecutor;
var controller = _controller;
var controller = _instance;
var arguments = _arguments;
var orderedArguments = PrepareArguments(arguments, executor);
@ -842,58 +413,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return typeof(IActionResult).IsAssignableFrom(resultType);
}
private async Task InvokeNextResultFilterAsync()
{
try
{
var next = State.ResultNext;
var state = (object)null;
var scope = Scope.Result;
var isCompleted = false;
while (!isCompleted)
{
await Next(ref next, ref scope, ref state, ref isCompleted);
}
}
catch (Exception exception)
{
_resultExecutedContext = new ResultExecutedContext(_controllerContext, _filters, _result, _controller)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
};
}
Debug.Assert(_resultExecutedContext != null);
}
private async Task<ResultExecutedContext> InvokeNextResultFilterAwaitedAsync()
{
Debug.Assert(_resultExecutingContext != null);
if (_resultExecutingContext.Cancel == true)
{
// If we get here, it means that an async filter set cancel == true AND called next().
// This is forbidden.
var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit(
typeof(IAsyncResultFilter).Name,
nameof(ResultExecutingContext.Cancel),
typeof(ResultExecutingContext).Name,
typeof(ResultExecutionDelegate).Name);
throw new InvalidOperationException(message);
}
await InvokeNextResultFilterAsync();
Debug.Assert(_resultExecutedContext != null);
return _resultExecutedContext;
}
/// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the
/// variables in this method represent.</remarks>
protected override async Task InvokeInnerFilterAsync()
{
var next = State.ResourceInsideBegin;
var scope = Scope.Resource;
var next = State.ActionBegin;
var scope = Scope.Invoker;
var state = (object)null;
var isCompleted = false;
@ -903,29 +428,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
private static void Rethrow(ExceptionContext context)
{
if (context == null)
{
return;
}
if (context.ExceptionHandled)
{
return;
}
if (context.ExceptionDispatchInfo != null)
{
context.ExceptionDispatchInfo.Throw();
}
if (context.Exception != null)
{
throw context.Exception;
}
}
private static void Rethrow(ActionExecutedContext context)
{
if (context == null)
@ -949,29 +451,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
private static void Rethrow(ResultExecutedContext context)
{
if (context == null)
{
return;
}
if (context.ExceptionHandled)
{
return;
}
if (context.ExceptionDispatchInfo != null)
{
context.ExceptionDispatchInfo.Throw();
}
if (context.Exception != null)
{
throw context.Exception;
}
}
private Task BindArgumentsAsync()
{
// Perf: Avoid allocating async state machines where possible. We only need the state
@ -984,7 +463,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
Debug.Assert(_cacheEntry.ControllerBinderDelegate != null);
return _cacheEntry.ControllerBinderDelegate(_controllerContext, _controller, _arguments);
return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments);
}
private static object[] PrepareArguments(
@ -1016,25 +495,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private enum Scope
{
Resource,
Exception,
Invoker,
Action,
Result,
}
private enum State
{
ResourceInsideBegin,
ExceptionBegin,
ExceptionNext,
ExceptionAsyncBegin,
ExceptionAsyncResume,
ExceptionAsyncEnd,
ExceptionSyncBegin,
ExceptionSyncEnd,
ExceptionInside,
ExceptionHandled,
ExceptionEnd,
ActionBegin,
ActionNext,
ActionAsyncBegin,
@ -1043,15 +509,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ActionSyncEnd,
ActionInside,
ActionEnd,
ResultBegin,
ResultNext,
ResultAsyncBegin,
ResultAsyncEnd,
ResultSyncBegin,
ResultSyncEnd,
ResultInside,
ResultEnd,
ResourceInsideEnd,
}
}
}

View File

@ -24,11 +24,15 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private AuthorizationFilterContext _authorizationContext;
private ResourceExecutingContext _resourceExecutingContext;
private ResourceExecutedContext _resourceExecutedContext;
private ExceptionContext _exceptionContext;
private ResultExecutingContext _resultExecutingContext;
private ResultExecutedContext _resultExecutedContext;
// Do not make this readonly, it's mutable. We don't want to make a copy.
// https://blogs.msdn.microsoft.com/ericlippert/2008/05/14/mutating-readonly-structs/
protected FilterCursor _cursor;
protected IActionResult _result;
protected object _instance;
public ResourceInvoker(
DiagnosticSource diagnosticSource,
@ -313,18 +317,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
state = current.Filter;
goto case State.ResourceSyncBegin;
}
else if (scope == Scope.Resource)
{
// All resource filters are currently on the stack - now execute the 'inside'.
Debug.Assert(_resourceExecutingContext != null);
goto case State.ResourceInside;
}
else
{
// There are no resource filters - so jump right to 'inside'.
Debug.Assert(scope == Scope.Invoker);
next = State.InvokeEnd;
return InvokeInnerFilterAsync();
// All resource filters are currently on the stack - now execute the 'inside'.
goto case State.ResourceInside;
}
}
@ -447,11 +443,419 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
case State.ResourceInside:
{
goto case State.ExceptionBegin;
}
case State.ExceptionBegin:
{
next = State.ResourceOutside;
return InvokeInnerFilterAsync();
_cursor.Reset();
goto case State.ExceptionNext;
}
case State.ResourceOutside:
case State.ExceptionNext:
{
var current = _cursor.GetNextFilter<IExceptionFilter, IAsyncExceptionFilter>();
if (current.FilterAsync != null)
{
state = current.FilterAsync;
goto case State.ExceptionAsyncBegin;
}
else if (current.Filter != null)
{
state = current.Filter;
goto case State.ExceptionSyncBegin;
}
else if (scope == Scope.Exception)
{
// All exception filters are on the stack already - so execute the 'inside'.
goto case State.ExceptionInside;
}
else
{
// There are no exception filters - so jump right to the action.
Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
goto case State.ActionBegin;
}
}
case State.ExceptionAsyncBegin:
{
var task = InvokeNextExceptionFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionAsyncResume;
return task;
}
goto case State.ExceptionAsyncResume;
}
case State.ExceptionAsyncResume:
{
Debug.Assert(state != null);
var filter = (IAsyncExceptionFilter)state;
var exceptionContext = _exceptionContext;
// When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
// we'll call the filter. Otherwise there's nothing to do.
if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
{
_diagnosticSource.BeforeOnExceptionAsync(exceptionContext, filter);
var task = filter.OnExceptionAsync(exceptionContext);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionAsyncEnd;
return task;
}
goto case State.ExceptionAsyncEnd;
}
goto case State.ExceptionEnd;
}
case State.ExceptionAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_exceptionContext != null);
var filter = (IAsyncExceptionFilter)state;
var exceptionContext = _exceptionContext;
_diagnosticSource.AfterOnExceptionAsync(exceptionContext, filter);
if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
{
// We don't need to do anthing to trigger a short circuit. If there's another
// exception filter on the stack it will check the same set of conditions
// and then just skip itself.
_logger.ExceptionFilterShortCircuited(filter);
}
goto case State.ExceptionEnd;
}
case State.ExceptionSyncBegin:
{
var task = InvokeNextExceptionFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ExceptionSyncEnd;
return task;
}
goto case State.ExceptionSyncEnd;
}
case State.ExceptionSyncEnd:
{
Debug.Assert(state != null);
var filter = (IExceptionFilter)state;
var exceptionContext = _exceptionContext;
// When we get here we're 'unwinding' the stack of exception filters. If we have an unhandled exception,
// we'll call the filter. Otherwise there's nothing to do.
if (exceptionContext?.Exception != null && !exceptionContext.ExceptionHandled)
{
_diagnosticSource.BeforeOnException(exceptionContext, filter);
filter.OnException(exceptionContext);
_diagnosticSource.AfterOnException(exceptionContext, filter);
if (exceptionContext.Exception == null || exceptionContext.ExceptionHandled)
{
// We don't need to do anthing to trigger a short circuit. If there's another
// exception filter on the stack it will check the same set of conditions
// and then just skip itself.
_logger.ExceptionFilterShortCircuited(filter);
}
}
goto case State.ExceptionEnd;
}
case State.ExceptionInside:
{
goto case State.ActionBegin;
}
case State.ExceptionHandled:
{
// We arrive in this state when an exception happened, but was handled by exception filters
// either by setting ExceptionHandled, or nulling out the Exception or setting a result
// on the ExceptionContext.
//
// We need to execute the result (if any) and then exit gracefully which unwinding Resource
// filters.
Debug.Assert(state != null);
Debug.Assert(_exceptionContext != null);
if (_exceptionContext.Result == null)
{
_exceptionContext.Result = new EmptyResult();
}
if (scope == Scope.Resource)
{
Debug.Assert(_exceptionContext.Result != null);
_result = _exceptionContext.Result;
}
var task = InvokeResultAsync(_exceptionContext.Result);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResourceInsideEnd;
return task;
}
goto case State.ResourceInsideEnd;
}
case State.ExceptionEnd:
{
var exceptionContext = _exceptionContext;
if (scope == Scope.Exception)
{
isCompleted = true;
return TaskCache.CompletedTask;
}
if (exceptionContext != null)
{
if (exceptionContext.Result != null ||
exceptionContext.Exception == null ||
exceptionContext.ExceptionHandled)
{
goto case State.ExceptionHandled;
}
Rethrow(exceptionContext);
Debug.Fail("unreachable");
}
goto case State.ResultBegin;
}
case State.ActionBegin:
{
var task = InvokeInnerFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ActionEnd;
return task;
}
goto case State.ActionEnd;
}
case State.ActionEnd:
{
if (scope == Scope.Exception)
{
// If we're inside an exception filter, let's allow those filters to 'unwind' before
// the result.
isCompleted = true;
return TaskCache.CompletedTask;
}
Debug.Assert(scope == Scope.Invoker || scope == Scope.Resource);
goto case State.ResultBegin;
}
case State.ResultBegin:
{
_cursor.Reset();
goto case State.ResultNext;
}
case State.ResultNext:
{
var current = _cursor.GetNextFilter<IResultFilter, IAsyncResultFilter>();
if (current.FilterAsync != null)
{
if (_resultExecutingContext == null)
{
_resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, _result, _instance);
}
state = current.FilterAsync;
goto case State.ResultAsyncBegin;
}
else if (current.Filter != null)
{
if (_resultExecutingContext == null)
{
_resultExecutingContext = new ResultExecutingContext(_actionContext, _filters, _result, _instance);
}
state = current.Filter;
goto case State.ResultSyncBegin;
}
else
{
goto case State.ResultInside;
}
}
case State.ResultAsyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
var filter = (IAsyncResultFilter)state;
var resultExecutingContext = _resultExecutingContext;
_diagnosticSource.BeforeOnResultExecution(resultExecutingContext, filter);
var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultAsyncEnd;
return task;
}
goto case State.ResultAsyncEnd;
}
case State.ResultAsyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
var filter = (IAsyncResultFilter)state;
var resultExecutingContext = _resultExecutingContext;
var resultExecutedContext = _resultExecutedContext;
if (resultExecutedContext == null || resultExecutingContext.Cancel == true)
{
// Short-circuited by not calling next || Short-circuited by setting Cancel == true
_logger.ResourceFilterShortCircuited(filter);
_resultExecutedContext = new ResultExecutedContext(
_actionContext,
_filters,
resultExecutingContext.Result,
_instance)
{
Canceled = true,
};
}
_diagnosticSource.AfterOnResultExecution(_resultExecutedContext, filter);
goto case State.ResultEnd;
}
case State.ResultSyncBegin:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
var filter = (IResultFilter)state;
var resultExecutingContext = _resultExecutingContext;
_diagnosticSource.BeforeOnResultExecuting(resultExecutingContext, filter);
filter.OnResultExecuting(resultExecutingContext);
_diagnosticSource.AfterOnResultExecuting(resultExecutingContext, filter);
if (_resultExecutingContext.Cancel == true)
{
// Short-circuited by setting Cancel == true
_logger.ResourceFilterShortCircuited(filter);
_resultExecutedContext = new ResultExecutedContext(
resultExecutingContext,
_filters,
resultExecutingContext.Result,
_instance)
{
Canceled = true,
};
goto case State.ResultEnd;
}
var task = InvokeNextResultFilterAsync();
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultSyncEnd;
return task;
}
goto case State.ResultSyncEnd;
}
case State.ResultSyncEnd:
{
Debug.Assert(state != null);
Debug.Assert(_resultExecutingContext != null);
Debug.Assert(_resultExecutedContext != null);
var filter = (IResultFilter)state;
var resultExecutedContext = _resultExecutedContext;
_diagnosticSource.BeforeOnResultExecuted(resultExecutedContext, filter);
filter.OnResultExecuted(resultExecutedContext);
_diagnosticSource.AfterOnResultExecuted(resultExecutedContext, filter);
goto case State.ResultEnd;
}
case State.ResultInside:
{
// If we executed result filters then we need to grab the result from there.
if (_resultExecutingContext != null)
{
_result = _resultExecutingContext.Result;
}
if (_result == null)
{
// The empty result is always flowed back as the 'executed' result if we don't have one.
_result = new EmptyResult();
}
var task = InvokeResultAsync(_result);
if (task.Status != TaskStatus.RanToCompletion)
{
next = State.ResultEnd;
return task;
}
goto case State.ResultEnd;
}
case State.ResultEnd:
{
var result = _result;
if (scope == Scope.Result)
{
if (_resultExecutedContext == null)
{
_resultExecutedContext = new ResultExecutedContext(_actionContext, _filters, result, _instance);
}
isCompleted = true;
return TaskCache.CompletedTask;
}
Rethrow(_resultExecutedContext);
goto case State.ResourceInsideEnd;
}
case State.ResourceInsideEnd:
{
var result = _result;
@ -538,6 +942,74 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Debug.Assert(_resourceExecutedContext != null);
}
private async Task InvokeNextExceptionFilterAsync()
{
try
{
var next = State.ExceptionNext;
var state = (object)null;
var scope = Scope.Exception;
var isCompleted = false;
while (!isCompleted)
{
await Next(ref next, ref scope, ref state, ref isCompleted);
}
}
catch (Exception exception)
{
_exceptionContext = new ExceptionContext(_actionContext, _filters)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
};
}
}
private async Task InvokeNextResultFilterAsync()
{
try
{
var next = State.ResultNext;
var state = (object)null;
var scope = Scope.Result;
var isCompleted = false;
while (!isCompleted)
{
await Next(ref next, ref scope, ref state, ref isCompleted);
}
}
catch (Exception exception)
{
_resultExecutedContext = new ResultExecutedContext(_actionContext, _filters, _result, _instance)
{
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
};
}
Debug.Assert(_resultExecutedContext != null);
}
private async Task<ResultExecutedContext> InvokeNextResultFilterAwaitedAsync()
{
Debug.Assert(_resultExecutingContext != null);
if (_resultExecutingContext.Cancel == true)
{
// If we get here, it means that an async filter set cancel == true AND called next().
// This is forbidden.
var message = Resources.FormatAsyncResultFilter_InvalidShortCircuit(
typeof(IAsyncResultFilter).Name,
nameof(ResultExecutingContext.Cancel),
typeof(ResultExecutingContext).Name,
typeof(ResultExecutionDelegate).Name);
throw new InvalidOperationException(message);
}
await InvokeNextResultFilterAsync();
Debug.Assert(_resultExecutedContext != null);
return _resultExecutedContext;
}
private static void Rethrow(ResourceExecutedContext context)
{
if (context == null)
@ -561,10 +1033,58 @@ namespace Microsoft.AspNetCore.Mvc.Internal
}
}
private static void Rethrow(ExceptionContext context)
{
if (context == null)
{
return;
}
if (context.ExceptionHandled)
{
return;
}
if (context.ExceptionDispatchInfo != null)
{
context.ExceptionDispatchInfo.Throw();
}
if (context.Exception != null)
{
throw context.Exception;
}
}
private static void Rethrow(ResultExecutedContext context)
{
if (context == null)
{
return;
}
if (context.ExceptionHandled)
{
return;
}
if (context.ExceptionDispatchInfo != null)
{
context.ExceptionDispatchInfo.Throw();
}
if (context.Exception != null)
{
throw context.Exception;
}
}
private enum Scope
{
Invoker,
Resource,
Exception,
Result,
}
private enum State
@ -585,8 +1105,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ResourceSyncEnd,
ResourceShortCircuit,
ResourceInside,
ResourceOutside,
ResourceInsideEnd,
ResourceEnd,
ExceptionBegin,
ExceptionNext,
ExceptionAsyncBegin,
ExceptionAsyncResume,
ExceptionAsyncEnd,
ExceptionSyncBegin,
ExceptionSyncEnd,
ExceptionInside,
ExceptionHandled,
ExceptionEnd,
ActionBegin,
ActionEnd,
ResultBegin,
ResultNext,
ResultAsyncBegin,
ResultAsyncEnd,
ResultSyncBegin,
ResultSyncEnd,
ResultInside,
ResultEnd,
InvokeEnd,
}
}

View File

@ -32,6 +32,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{
public class ControllerActionInvokerTest : CommonResourceInvokerTest
{
#region Diagnostics
[Fact]
public async Task Invoke_Success_LogsCorrectValues()
{
@ -142,6 +144,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.NotNull(listener.AfterAction?.HttpContext);
}
#endregion
#region Controller Context
[Fact]
public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInControllerContext()
@ -204,6 +209,599 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[0]);
}
#endregion
#region Action Filters
[Fact]
public async Task InvokeAction_InvokesActionFilter()
{
// Arrange
IActionResult result = null;
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => result = c.Result)
.Verifiable();
var invoker = CreateInvoker(filter.Object);
// Act
await invoker.InvokeAsync();
// Assert
filter.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
Assert.Same(Result, result);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter()
{
// Arrange
IActionResult result = null;
var filter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
filter
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (context, next) =>
{
var resultContext = await next();
result = resultContext.Result;
})
.Verifiable();
var invoker = CreateInvoker(filter.Object);
// Act
await invoker.InvokeAsync();
// Assert
filter.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
Assert.Same(Result, result);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_ShortCircuit()
{
// Arrange
var result = new Mock<IActionResult>(MockBehavior.Strict);
result
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(true))
.Verifiable();
ActionExecutedContext context = null;
var actionFilter1 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => context = c)
.Verifiable();
var actionFilter2 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>(c => c.Result = result.Object)
.Verifiable();
var actionFilter3 = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[]
{
actionFilter1.Object,
actionFilter2.Object,
actionFilter3.Object,
resultFilter.Object,
});
// Act
await invoker.InvokeAsync();
// Assert
result.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter2.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Never());
resultFilter.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
Assert.True(context.Canceled);
Assert.Same(context.Result, result.Object);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult()
{
// Arrange
var result = new Mock<IActionResult>(MockBehavior.Strict);
result
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(true))
.Verifiable();
ActionExecutedContext context = null;
var actionFilter1 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => context = c)
.Verifiable();
var actionFilter2 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>((c, next) =>
{
// Notice we're not calling next
c.Result = result.Object;
return Task.FromResult(true);
})
.Verifiable();
var actionFilter3 = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter1 = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var resultFilter2 = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter2.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter2.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[]
{
actionFilter1.Object,
actionFilter2.Object,
actionFilter3.Object,
resultFilter1.Object,
resultFilter2.Object,
});
// Act
await invoker.InvokeAsync();
// Assert
result.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
actionFilter2.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter2.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
Assert.True(context.Canceled);
Assert.Same(context.Result, result.Object);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult()
{
// Arrange
ActionExecutedContext context = null;
var actionFilter1 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => context = c)
.Verifiable();
var actionFilter2 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>((c, next) =>
{
// Notice we're not calling next
return Task.FromResult(true);
})
.Verifiable();
var actionFilter3 = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[]
{
actionFilter1.Object,
actionFilter2.Object,
actionFilter3.Object,
resultFilter.Object,
});
// Act
await invoker.InvokeAsync();
// Assert
actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
actionFilter2.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
resultFilter.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
Assert.True(context.Canceled);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext()
{
// Arrange
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
c.Result = new EmptyResult();
await next();
})
.Verifiable();
var message =
"If an IAsyncActionFilter provides a result value by setting the Result property of " +
"ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " +
"ActionExecutionDelegate.";
var invoker = CreateInvoker(actionFilter.Object);
// Act & Assert
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
async () => await invoker.InvokeAsync(),
message);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction()
{
// Arrange
ActionExecutedContext context = null;
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c =>
{
context = c;
// Handle the exception so the test doesn't throw.
Assert.False(c.ExceptionHandled);
c.ExceptionHandled = true;
})
.Verifiable();
var invoker = CreateInvoker(filter.Object, exception: Exception);
// Act
await invoker.InvokeAsync();
// Assert
filter.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
Assert.Same(Exception, context.Exception);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter()
{
// Arrange
var exception = new DataMisalignedException();
ActionExecutedContext context = null;
var filter1 = new Mock<IActionFilter>(MockBehavior.Strict);
filter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c =>
{
context = c;
// Handle the exception so the test doesn't throw.
Assert.False(c.ExceptionHandled);
c.ExceptionHandled = true;
})
.Verifiable();
var filter2 = new Mock<IActionFilter>(MockBehavior.Strict);
filter2
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>(c => { throw exception; })
.Verifiable();
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object });
// Act
await invoker.InvokeAsync();
// Assert
filter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
filter2.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter2.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Never());
Assert.Same(exception, context.Exception);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter()
{
// Arrange
var exception = new DataMisalignedException();
ActionExecutedContext context = null;
var filter1 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
filter1
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
context = await next();
// Handle the exception so the test doesn't throw.
Assert.False(context.ExceptionHandled);
context.ExceptionHandled = true;
})
.Verifiable();
var filter2 = new Mock<IActionFilter>(MockBehavior.Strict);
filter2.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter2
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => { throw exception; })
.Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object });
// Act
await invoker.InvokeAsync();
// Assert
filter1.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
filter2.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
Assert.Same(exception, context.Exception);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_HandleException()
{
// Arrange
var result = new Mock<IActionResult>(MockBehavior.Strict);
result
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
.Returns<ActionContext>((context) => Task.FromResult(true))
.Verifiable();
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c =>
{
// Handle the exception so the test doesn't throw.
Assert.False(c.ExceptionHandled);
c.ExceptionHandled = true;
c.Result = result.Object;
})
.Verifiable();
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(
new IFilterMetadata[] { actionFilter.Object, resultFilter.Object },
exception: Exception);
// Act
await invoker.InvokeAsync();
// Assert
actionFilter.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
result.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter()
{
// Arrange
var expected = Mock.Of<IActionResult>();
ResourceExecutedContext context = null;
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
context = await next();
})
.Verifiable();
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>((c) =>
{
c.Result = expected;
});
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object });
// Act
await invoker.InvokeAsync();
// Assert
Assert.Same(expected, context.Result);
resourceFilter.Verify(
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter()
{
// Arrange
var expected = new DataMisalignedException();
ResourceExecutedContext context = null;
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
context = await next();
context.ExceptionHandled = true;
})
.Verifiable();
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>((c) =>
{
throw expected;
});
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object });
// Act
await invoker.InvokeAsync();
// Assert
Assert.Same(expected, context.Exception);
Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
resourceFilter.Verify(
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter()
{
// Arrange
var expected = new DataMisalignedException();
ResourceExecutedContext context = null;
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
context = await next();
context.ExceptionHandled = true;
})
.Verifiable();
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
exceptionFilter
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
.Callback<ExceptionContext>((c) =>
{
throw expected;
});
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, exception: Exception);
// Act
await invoker.InvokeAsync();
// Assert
Assert.Same(expected, context.Exception);
Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
resourceFilter.Verify(
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Once());
}
[Fact]
public async Task InvokeAction_ExceptionBubbling_AsyncActionFilter_To_ResourceFilter()
{
// Arrange
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
var context = await next();
Assert.Same(Exception, context.Exception);
context.ExceptionHandled = true;
});
var actionFilter1 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter1
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
await next();
});
var actionFilter2 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
await next();
});
var invoker = CreateInvoker(
new IFilterMetadata[]
{
resourceFilter.Object,
actionFilter1.Object,
actionFilter2.Object,
},
// The action won't run
exception: Exception);
// Act & Assert
await invoker.InvokeAsync();
}
#endregion
#region Action Method Signatures
[Fact]
public async Task InvokeAction_AsyncAction_TaskReturnType()
{
@ -794,6 +1392,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(5, context.Object.Items["Result"]);
}
#endregion
protected override ResourceInvoker CreateInvoker(
IFilterMetadata[] filters,
Exception exception = null,

View File

@ -3,20 +3,11 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Moq;
using Xunit;
@ -701,7 +692,6 @@ namespace Microsoft.AspNetCore.Mvc
// None of these filters should run
var authorizationFilter2 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
var resourceFilter = new Mock<IResourceFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker(new IFilterMetadata[]
@ -710,7 +700,6 @@ namespace Microsoft.AspNetCore.Mvc
authorizationFilter1.Object,
authorizationFilter2.Object,
resourceFilter.Object,
actionFilter.Object,
resultFilter.Object,
});
@ -751,432 +740,6 @@ namespace Microsoft.AspNetCore.Mvc
challenge.Verify(c => c.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesActionFilter()
{
// Arrange
IActionResult result = null;
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => result = c.Result)
.Verifiable();
var invoker = CreateInvoker(filter.Object);
// Act
await invoker.InvokeAsync();
// Assert
filter.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
Assert.Same(Result, result);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter()
{
// Arrange
IActionResult result = null;
var filter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
filter
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (context, next) =>
{
var resultContext = await next();
result = resultContext.Result;
})
.Verifiable();
var invoker = CreateInvoker(filter.Object);
// Act
await invoker.InvokeAsync();
// Assert
filter.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
Assert.Same(Result, result);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_ShortCircuit()
{
// Arrange
var result = new Mock<IActionResult>(MockBehavior.Strict);
result
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(true))
.Verifiable();
ActionExecutedContext context = null;
var actionFilter1 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => context = c)
.Verifiable();
var actionFilter2 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>(c => c.Result = result.Object)
.Verifiable();
var actionFilter3 = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[]
{
actionFilter1.Object,
actionFilter2.Object,
actionFilter3.Object,
resultFilter.Object,
});
// Act
await invoker.InvokeAsync();
// Assert
result.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter2.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Never());
resultFilter.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
Assert.True(context.Canceled);
Assert.Same(context.Result, result.Object);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult()
{
// Arrange
var result = new Mock<IActionResult>(MockBehavior.Strict);
result
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
.Returns(Task.FromResult(true))
.Verifiable();
ActionExecutedContext context = null;
var actionFilter1 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => context = c)
.Verifiable();
var actionFilter2 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>((c, next) =>
{
// Notice we're not calling next
c.Result = result.Object;
return Task.FromResult(true);
})
.Verifiable();
var actionFilter3 = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter1 = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var resultFilter2 = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter2.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter2.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[]
{
actionFilter1.Object,
actionFilter2.Object,
actionFilter3.Object,
resultFilter1.Object,
resultFilter2.Object,
});
// Act
await invoker.InvokeAsync();
// Assert
result.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
actionFilter2.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter2.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
Assert.True(context.Canceled);
Assert.Same(context.Result, result.Object);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult()
{
// Arrange
ActionExecutedContext context = null;
var actionFilter1 = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => context = c)
.Verifiable();
var actionFilter2 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>((c, next) =>
{
// Notice we're not calling next
return Task.FromResult(true);
})
.Verifiable();
var actionFilter3 = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[]
{
actionFilter1.Object,
actionFilter2.Object,
actionFilter3.Object,
resultFilter.Object,
});
// Act
await invoker.InvokeAsync();
// Assert
actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
actionFilter2.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
resultFilter.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
Assert.True(context.Canceled);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext()
{
// Arrange
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
c.Result = new EmptyResult();
await next();
})
.Verifiable();
var message =
"If an IAsyncActionFilter provides a result value by setting the Result property of " +
"ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " +
"ActionExecutionDelegate.";
var invoker = CreateInvoker(actionFilter.Object);
// Act & Assert
await ExceptionAssert.ThrowsAsync<InvalidOperationException>(
async () => await invoker.InvokeAsync(),
message);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction()
{
// Arrange
ActionExecutedContext context = null;
var filter = new Mock<IActionFilter>(MockBehavior.Strict);
filter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c =>
{
context = c;
// Handle the exception so the test doesn't throw.
Assert.False(c.ExceptionHandled);
c.ExceptionHandled = true;
})
.Verifiable();
var invoker = CreateInvoker(filter.Object, exception: Exception);
// Act
await invoker.InvokeAsync();
// Assert
filter.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
Assert.Same(Exception, context.Exception);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter()
{
// Arrange
var exception = new DataMisalignedException();
ActionExecutedContext context = null;
var filter1 = new Mock<IActionFilter>(MockBehavior.Strict);
filter1.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter1
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c =>
{
context = c;
// Handle the exception so the test doesn't throw.
Assert.False(c.ExceptionHandled);
c.ExceptionHandled = true;
})
.Verifiable();
var filter2 = new Mock<IActionFilter>(MockBehavior.Strict);
filter2
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>(c => { throw exception; })
.Verifiable();
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object });
// Act
await invoker.InvokeAsync();
// Assert
filter1.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter1.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
filter2.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
filter2.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Never());
Assert.Same(exception, context.Exception);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter()
{
// Arrange
var exception = new DataMisalignedException();
ActionExecutedContext context = null;
var filter1 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
filter1
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
context = await next();
// Handle the exception so the test doesn't throw.
Assert.False(context.ExceptionHandled);
context.ExceptionHandled = true;
})
.Verifiable();
var filter2 = new Mock<IActionFilter>(MockBehavior.Strict);
filter2.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
filter2
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c => { throw exception; })
.Verifiable();
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object });
// Act
await invoker.InvokeAsync();
// Assert
filter1.Verify(
f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()),
Times.Once());
filter2.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
Assert.Same(exception, context.Exception);
Assert.Null(context.Result);
}
[Fact]
public async Task InvokeAction_InvokesActionFilter_HandleException()
{
// Arrange
var result = new Mock<IActionResult>(MockBehavior.Strict);
result
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
.Returns<ActionContext>((context) => Task.FromResult(true))
.Verifiable();
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>())).Verifiable();
actionFilter
.Setup(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()))
.Callback<ActionExecutedContext>(c =>
{
// Handle the exception so the test doesn't throw.
Assert.False(c.ExceptionHandled);
c.ExceptionHandled = true;
c.Result = result.Object;
})
.Verifiable();
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>())).Verifiable();
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>())).Verifiable();
var invoker = CreateInvoker(
new IFilterMetadata[] { actionFilter.Object, resultFilter.Object },
exception: Exception);
// Act
await invoker.InvokeAsync();
// Assert
actionFilter.Verify(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()), Times.Once());
actionFilter.Verify(f => f.OnActionExecuted(It.IsAny<ActionExecutedContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
resultFilter.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
result.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesResultFilter()
{
@ -1653,43 +1216,6 @@ namespace Microsoft.AspNetCore.Mvc
Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter()
{
// Arrange
var expected = Mock.Of<IActionResult>();
ResourceExecutedContext context = null;
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
context = await next();
})
.Verifiable();
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>((c) =>
{
c.Result = expected;
});
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object });
// Act
await invoker.InvokeAsync();
// Assert
Assert.Same(expected, context.Result);
resourceFilter.Verify(
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter()
{
@ -1795,84 +1321,6 @@ namespace Microsoft.AspNetCore.Mvc
Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter()
{
// Arrange
var expected = new DataMisalignedException();
ResourceExecutedContext context = null;
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
context = await next();
context.ExceptionHandled = true;
})
.Verifiable();
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
actionFilter
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
.Callback<ActionExecutingContext>((c) =>
{
throw expected;
});
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object });
// Act
await invoker.InvokeAsync();
// Assert
Assert.Same(expected, context.Exception);
Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
resourceFilter.Verify(
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter()
{
// Arrange
var expected = new DataMisalignedException();
ResourceExecutedContext context = null;
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
context = await next();
context.ExceptionHandled = true;
})
.Verifiable();
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
exceptionFilter
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
.Callback<ExceptionContext>((c) =>
{
throw expected;
});
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, exception: Exception);
// Act
await invoker.InvokeAsync();
// Assert
Assert.Same(expected, context.Exception);
Assert.Same(expected, context.ExceptionDispatchInfo.SourceException);
resourceFilter.Verify(
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Once());
}
[Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter()
{
@ -2082,7 +1530,6 @@ namespace Microsoft.AspNetCore.Mvc
var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker(
@ -2092,7 +1539,6 @@ namespace Microsoft.AspNetCore.Mvc
resourceFilter2.Object, // This filter will short circuit
resourceFilter3.Object, // This shouldn't run - it will throw if it does
exceptionFilter.Object, // This shouldn't run - it will throw if it does
actionFilter.Object, // This shouldn't run - it will throw if it does
resultFilter.Object // This shouldn't run - it will throw if it does
},
// The action won't run
@ -2132,7 +1578,6 @@ namespace Microsoft.AspNetCore.Mvc
var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker(
@ -2142,7 +1587,6 @@ namespace Microsoft.AspNetCore.Mvc
resourceFilter2.Object, // This filter will short circuit
resourceFilter3.Object, // This shouldn't run - it will throw if it does
exceptionFilter.Object, // This shouldn't run - it will throw if it does
actionFilter.Object, // This shouldn't run - it will throw if it does
resultFilter.Object // This shouldn't run - it will throw if it does
},
// The action won't run
@ -2184,7 +1628,6 @@ namespace Microsoft.AspNetCore.Mvc
});
var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker(
@ -2193,7 +1636,6 @@ namespace Microsoft.AspNetCore.Mvc
resourceFilter1.Object, // This filter should see the result retured from resourceFilter2
resourceFilter2.Object,
resourceFilter3.Object, // This shouldn't run - it will throw if it does
actionFilter.Object, // This shouldn't run - it will throw if it does
resultFilter.Object // This shouldn't run - it will throw if it does
},
// The action won't run
@ -2268,49 +1710,5 @@ namespace Microsoft.AspNetCore.Mvc
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Never());
}
[Fact]
public async Task InvokeAction_ExceptionBubbling_AsyncActionFilter_To_ResourceFilter()
{
// Arrange
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
resourceFilter
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>(async (c, next) =>
{
var context = await next();
Assert.Same(Exception, context.Exception);
context.ExceptionHandled = true;
});
var actionFilter1 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter1
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
await next();
});
var actionFilter2 = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
actionFilter2
.Setup(f => f.OnActionExecutionAsync(It.IsAny<ActionExecutingContext>(), It.IsAny<ActionExecutionDelegate>()))
.Returns<ActionExecutingContext, ActionExecutionDelegate>(async (c, next) =>
{
await next();
});
var invoker = CreateInvoker(
new IFilterMetadata[]
{
resourceFilter.Object,
actionFilter1.Object,
actionFilter2.Object,
},
// The action won't run
exception: Exception);
// Act & Assert
await invoker.InvokeAsync();
}
}
}