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 ControllerActionInvokerCacheEntry _cacheEntry;
private readonly ControllerContext _controllerContext; private readonly ControllerContext _controllerContext;
private object _controller;
private Dictionary<string, object> _arguments; private Dictionary<string, object> _arguments;
private ExceptionContext _exceptionContext;
private ActionExecutingContext _actionExecutingContext; private ActionExecutingContext _actionExecutingContext;
private ActionExecutedContext _actionExecutedContext; private ActionExecutedContext _actionExecutedContext;
private ResultExecutingContext _resultExecutingContext;
private ResultExecutedContext _resultExecutedContext;
internal ControllerActionInvoker( internal ControllerActionInvoker(
ILogger logger, ILogger logger,
DiagnosticSource diagnosticSource, DiagnosticSource diagnosticSource,
@ -52,9 +46,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
protected override void ReleaseResources() 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) 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: case State.ActionBegin:
{ {
var controllerContext = _controllerContext; var controllerContext = _controllerContext;
_cursor.Reset(); _cursor.Reset();
_controller = _cacheEntry.ControllerFactory(controllerContext); _instance = _cacheEntry.ControllerFactory(controllerContext);
_arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase); _arguments = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
@ -293,7 +86,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
if (_actionExecutingContext == null) if (_actionExecutingContext == null)
{ {
_actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller); _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance);
} }
state = current.FilterAsync; state = current.FilterAsync;
@ -303,7 +96,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
if (_actionExecutingContext == null) if (_actionExecutingContext == null)
{ {
_actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _controller); _actionExecutingContext = new ActionExecutingContext(_controllerContext, _filters, _arguments, _instance);
} }
state = current.Filter; state = current.Filter;
@ -350,7 +143,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_actionExecutedContext = new ActionExecutedContext( _actionExecutedContext = new ActionExecutedContext(
_controllerContext, _controllerContext,
_filters, _filters,
_controller) _instance)
{ {
Canceled = true, Canceled = true,
Result = _actionExecutingContext.Result, Result = _actionExecutingContext.Result,
@ -384,7 +177,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_actionExecutedContext = new ActionExecutedContext( _actionExecutedContext = new ActionExecutedContext(
_actionExecutingContext, _actionExecutingContext,
_filters, _filters,
_controller) _instance)
{ {
Canceled = true, Canceled = true,
Result = _actionExecutingContext.Result, Result = _actionExecutingContext.Result,
@ -439,7 +232,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
if (_actionExecutedContext == null) if (_actionExecutedContext == null)
{ {
_actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller) _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance)
{ {
Result = _result, Result = _result,
}; };
@ -457,206 +250,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
_result = actionExecutedContext.Result; _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; isCompleted = true;
return TaskCache.CompletedTask; 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() private async Task InvokeNextActionFilterAsync()
{ {
try try
@ -703,7 +274,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
catch (Exception exception) catch (Exception exception)
{ {
_actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _controller) _actionExecutedContext = new ActionExecutedContext(_controllerContext, _filters, _instance)
{ {
ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception), ExceptionDispatchInfo = ExceptionDispatchInfo.Capture(exception),
}; };
@ -737,7 +308,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
var controllerContext = _controllerContext; var controllerContext = _controllerContext;
var executor = _cacheEntry.ActionMethodExecutor; var executor = _cacheEntry.ActionMethodExecutor;
var controller = _controller; var controller = _instance;
var arguments = _arguments; var arguments = _arguments;
var orderedArguments = PrepareArguments(arguments, executor); var orderedArguments = PrepareArguments(arguments, executor);
@ -842,58 +413,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
return typeof(IActionResult).IsAssignableFrom(resultType); 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 /// <remarks><see cref="ResourceInvoker.InvokeFilterPipelineAsync"/> for details on what the
/// variables in this method represent.</remarks> /// variables in this method represent.</remarks>
protected override async Task InvokeInnerFilterAsync() protected override async Task InvokeInnerFilterAsync()
{ {
var next = State.ResourceInsideBegin; var next = State.ActionBegin;
var scope = Scope.Resource; var scope = Scope.Invoker;
var state = (object)null; var state = (object)null;
var isCompleted = false; 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) private static void Rethrow(ActionExecutedContext context)
{ {
if (context == null) 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() private Task BindArgumentsAsync()
{ {
// Perf: Avoid allocating async state machines where possible. We only need the state // 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); Debug.Assert(_cacheEntry.ControllerBinderDelegate != null);
return _cacheEntry.ControllerBinderDelegate(_controllerContext, _controller, _arguments); return _cacheEntry.ControllerBinderDelegate(_controllerContext, _instance, _arguments);
} }
private static object[] PrepareArguments( private static object[] PrepareArguments(
@ -1016,25 +495,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
private enum Scope private enum Scope
{ {
Resource, Invoker,
Exception,
Action, Action,
Result,
} }
private enum State private enum State
{ {
ResourceInsideBegin,
ExceptionBegin,
ExceptionNext,
ExceptionAsyncBegin,
ExceptionAsyncResume,
ExceptionAsyncEnd,
ExceptionSyncBegin,
ExceptionSyncEnd,
ExceptionInside,
ExceptionHandled,
ExceptionEnd,
ActionBegin, ActionBegin,
ActionNext, ActionNext,
ActionAsyncBegin, ActionAsyncBegin,
@ -1043,15 +509,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ActionSyncEnd, ActionSyncEnd,
ActionInside, ActionInside,
ActionEnd, 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 AuthorizationFilterContext _authorizationContext;
private ResourceExecutingContext _resourceExecutingContext; private ResourceExecutingContext _resourceExecutingContext;
private ResourceExecutedContext _resourceExecutedContext; 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. // 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/ // https://blogs.msdn.microsoft.com/ericlippert/2008/05/14/mutating-readonly-structs/
protected FilterCursor _cursor; protected FilterCursor _cursor;
protected IActionResult _result; protected IActionResult _result;
protected object _instance;
public ResourceInvoker( public ResourceInvoker(
DiagnosticSource diagnosticSource, DiagnosticSource diagnosticSource,
@ -313,18 +317,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
state = current.Filter; state = current.Filter;
goto case State.ResourceSyncBegin; 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 else
{ {
// There are no resource filters - so jump right to 'inside'. // All resource filters are currently on the stack - now execute the 'inside'.
Debug.Assert(scope == Scope.Invoker); goto case State.ResourceInside;
next = State.InvokeEnd;
return InvokeInnerFilterAsync();
} }
} }
@ -447,11 +443,419 @@ namespace Microsoft.AspNetCore.Mvc.Internal
} }
case State.ResourceInside: case State.ResourceInside:
{
goto case State.ExceptionBegin;
}
case State.ExceptionBegin:
{ {
next = State.ResourceOutside; _cursor.Reset();
return InvokeInnerFilterAsync(); 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; var result = _result;
@ -538,6 +942,74 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Debug.Assert(_resourceExecutedContext != null); 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) private static void Rethrow(ResourceExecutedContext context)
{ {
if (context == null) 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 private enum Scope
{ {
Invoker, Invoker,
Resource, Resource,
Exception,
Result,
} }
private enum State private enum State
@ -585,8 +1105,28 @@ namespace Microsoft.AspNetCore.Mvc.Internal
ResourceSyncEnd, ResourceSyncEnd,
ResourceShortCircuit, ResourceShortCircuit,
ResourceInside, ResourceInside,
ResourceOutside, ResourceInsideEnd,
ResourceEnd, ResourceEnd,
ExceptionBegin,
ExceptionNext,
ExceptionAsyncBegin,
ExceptionAsyncResume,
ExceptionAsyncEnd,
ExceptionSyncBegin,
ExceptionSyncEnd,
ExceptionInside,
ExceptionHandled,
ExceptionEnd,
ActionBegin,
ActionEnd,
ResultBegin,
ResultNext,
ResultAsyncBegin,
ResultAsyncEnd,
ResultSyncBegin,
ResultSyncEnd,
ResultInside,
ResultEnd,
InvokeEnd, InvokeEnd,
} }
} }

View File

@ -32,6 +32,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
public class ControllerActionInvokerTest : CommonResourceInvokerTest public class ControllerActionInvokerTest : CommonResourceInvokerTest
{ {
#region Diagnostics
[Fact] [Fact]
public async Task Invoke_Success_LogsCorrectValues() public async Task Invoke_Success_LogsCorrectValues()
{ {
@ -142,6 +144,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.NotNull(listener.AfterAction?.HttpContext); Assert.NotNull(listener.AfterAction?.HttpContext);
} }
#endregion
#region Controller Context
[Fact] [Fact]
public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInControllerContext() public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInControllerContext()
@ -204,6 +209,599 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[0]); 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] [Fact]
public async Task InvokeAction_AsyncAction_TaskReturnType() public async Task InvokeAction_AsyncAction_TaskReturnType()
{ {
@ -794,6 +1392,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
Assert.Equal(5, context.Object.Items["Result"]); Assert.Equal(5, context.Object.Items["Result"]);
} }
#endregion
protected override ResourceInvoker CreateInvoker( protected override ResourceInvoker CreateInvoker(
IFilterMetadata[] filters, IFilterMetadata[] filters,
Exception exception = null, Exception exception = null,

View File

@ -3,20 +3,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Controllers;
using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Testing; using Microsoft.AspNetCore.Testing;
using Microsoft.Extensions.Logging;
using Moq; using Moq;
using Xunit; using Xunit;
@ -701,7 +692,6 @@ namespace Microsoft.AspNetCore.Mvc
// None of these filters should run // None of these filters should run
var authorizationFilter2 = new Mock<IAuthorizationFilter>(MockBehavior.Strict); var authorizationFilter2 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
var resourceFilter = new Mock<IResourceFilter>(MockBehavior.Strict); var resourceFilter = new Mock<IResourceFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict); var resultFilter = new Mock<IResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker(new IFilterMetadata[] var invoker = CreateInvoker(new IFilterMetadata[]
@ -710,7 +700,6 @@ namespace Microsoft.AspNetCore.Mvc
authorizationFilter1.Object, authorizationFilter1.Object,
authorizationFilter2.Object, authorizationFilter2.Object,
resourceFilter.Object, resourceFilter.Object,
actionFilter.Object,
resultFilter.Object, resultFilter.Object,
}); });
@ -751,432 +740,6 @@ namespace Microsoft.AspNetCore.Mvc
challenge.Verify(c => c.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once()); 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] [Fact]
public async Task InvokeAction_InvokesResultFilter() public async Task InvokeAction_InvokesResultFilter()
{ {
@ -1653,43 +1216,6 @@ namespace Microsoft.AspNetCore.Mvc
Times.Once()); 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] [Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter() public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter()
{ {
@ -1795,84 +1321,6 @@ namespace Microsoft.AspNetCore.Mvc
Times.Once()); 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] [Fact]
public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter() public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter()
{ {
@ -2082,7 +1530,6 @@ namespace Microsoft.AspNetCore.Mvc
var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict); var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict); var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict); var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker( var invoker = CreateInvoker(
@ -2092,7 +1539,6 @@ namespace Microsoft.AspNetCore.Mvc
resourceFilter2.Object, // This filter will short circuit resourceFilter2.Object, // This filter will short circuit
resourceFilter3.Object, // This shouldn't run - it will throw if it does resourceFilter3.Object, // This shouldn't run - it will throw if it does
exceptionFilter.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 resultFilter.Object // This shouldn't run - it will throw if it does
}, },
// The action won't run // The action won't run
@ -2132,7 +1578,6 @@ namespace Microsoft.AspNetCore.Mvc
var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict); var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict); var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict); var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker( var invoker = CreateInvoker(
@ -2142,7 +1587,6 @@ namespace Microsoft.AspNetCore.Mvc
resourceFilter2.Object, // This filter will short circuit resourceFilter2.Object, // This filter will short circuit
resourceFilter3.Object, // This shouldn't run - it will throw if it does resourceFilter3.Object, // This shouldn't run - it will throw if it does
exceptionFilter.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 resultFilter.Object // This shouldn't run - it will throw if it does
}, },
// The action won't run // The action won't run
@ -2184,7 +1628,6 @@ namespace Microsoft.AspNetCore.Mvc
}); });
var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict); var resourceFilter3 = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
var actionFilter = new Mock<IAsyncActionFilter>(MockBehavior.Strict);
var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict); var resultFilter = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
var invoker = CreateInvoker( var invoker = CreateInvoker(
@ -2193,7 +1636,6 @@ namespace Microsoft.AspNetCore.Mvc
resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 resourceFilter1.Object, // This filter should see the result retured from resourceFilter2
resourceFilter2.Object, resourceFilter2.Object,
resourceFilter3.Object, // This shouldn't run - it will throw if it does 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 resultFilter.Object // This shouldn't run - it will throw if it does
}, },
// The action won't run // The action won't run
@ -2268,49 +1710,5 @@ namespace Microsoft.AspNetCore.Mvc
f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()), f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()),
Times.Never()); 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();
}
} }
} }