Introduce IAlwaysRunResultFilter \ IAsyncAlwaysRunResultFilter (#7120)
* Introduce IAlwaysRunResultFilter \ IAsyncAlwaysRunResultFilter Fixes #7105
This commit is contained in:
parent
455cf2e0c3
commit
350c4ec4f6
|
|
@ -71,7 +71,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
|
|||
/// <summary>
|
||||
/// Gets or sets an indication that the <see cref="Exception"/> has been handled.
|
||||
/// </summary>
|
||||
public virtual bool ExceptionHandled { get; set; } = false;
|
||||
public virtual bool ExceptionHandled { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="IActionResult"/>.
|
||||
|
|
|
|||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that surrounds execution of all action results.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The <see cref="IAlwaysRunResultFilter"/> interface declares an <see cref="IResultFilter"/> implementation
|
||||
/// that should run for all action results. <seealso cref="IAsyncAlwaysRunResultFilter"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> instances are not executed in cases where
|
||||
/// an authorization filter or resource filter short-circuits the request to prevent execution of the action.
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> implementations
|
||||
/// are also not executed in cases where an exception filter handles an exception by producing an action result.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IAlwaysRunResultFilter : IResultFilter
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that asynchronously surrounds execution of all action results.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// The <see cref="IAsyncAlwaysRunResultFilter"/> interface declares an <see cref="IAsyncResultFilter"/> implementation
|
||||
/// that should run for all action results. <seealso cref="IAsyncAlwaysRunResultFilter"/>.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> instances are not executed in cases where
|
||||
/// an authorization filter or resource filter short-circuits the request to prevent execution of the action.
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> implementations
|
||||
/// are also not executed in cases where an exception filter handles an exception by producing an action result.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IAsyncAlwaysRunResultFilter : IAsyncResultFilter
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -6,8 +6,24 @@ using System.Threading.Tasks;
|
|||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that asynchronously surrounds execution of the action result.
|
||||
/// A filter that asynchronously surrounds execution of action results successfully returned from an action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> implementations are executed around the action
|
||||
/// result only when the action method (or action filters) complete successfully.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> instances are not executed in cases where
|
||||
/// an authorization filter or resource filter short-circuits the request to prevent execution of the action.
|
||||
/// <see cref="IResultFilter"/>. <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> implementations
|
||||
/// are also not executed in cases where an exception filter handles an exception by producing an action result.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To create a result filter that surrounds the execution of all action results, implement
|
||||
/// either the <see cref="IAlwaysRunResultFilter"/> or the <see cref="IAsyncAlwaysRunResultFilter"/> interface.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IAsyncResultFilter : IFilterMetadata
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -4,8 +4,24 @@
|
|||
namespace Microsoft.AspNetCore.Mvc.Filters
|
||||
{
|
||||
/// <summary>
|
||||
/// A filter that surrounds execution of the action result.
|
||||
/// A filter that surrounds execution of action results successfully returned from an action.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> implementations are executed around the action
|
||||
/// result only when the action method (or action filters) complete successfully.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> instances are not executed in cases where
|
||||
/// an authorization filter or resource filter short-circuits the request to prevent execution of the action.
|
||||
/// <see cref="IResultFilter"/>. <see cref="IResultFilter"/> and <see cref="IAsyncResultFilter"/> implementations
|
||||
/// are also not executed in cases where an exception filter handles an exception by producing an action result.
|
||||
/// </para>
|
||||
/// <para>
|
||||
/// To create a result filter that surrounds the execution of all action results, implement
|
||||
/// either the <see cref="IAlwaysRunResultFilter"/> or the <see cref="IAsyncAlwaysRunResultFilter"/> interface.
|
||||
/// </para>
|
||||
/// </remarks>
|
||||
public interface IResultFilter : IFilterMetadata
|
||||
{
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -41,38 +41,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
IFilterMetadata[] filters,
|
||||
IList<IValueProviderFactory> valueProviderFactories)
|
||||
{
|
||||
_diagnosticSource = diagnosticSource ?? throw new ArgumentNullException(nameof(diagnosticSource));
|
||||
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
_actionContext = actionContext ?? throw new ArgumentNullException(nameof(actionContext));
|
||||
|
||||
if (diagnosticSource == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(diagnosticSource));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
}
|
||||
|
||||
if (actionContext == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(actionContext));
|
||||
}
|
||||
|
||||
if (filters == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(filters));
|
||||
}
|
||||
|
||||
if (valueProviderFactories == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(valueProviderFactories));
|
||||
}
|
||||
|
||||
_diagnosticSource = diagnosticSource;
|
||||
_logger = logger;
|
||||
_actionContext = actionContext;
|
||||
|
||||
_filters = filters;
|
||||
_valueProviderFactories = valueProviderFactories;
|
||||
_filters = filters ?? throw new ArgumentNullException(nameof(filters));
|
||||
_valueProviderFactories = valueProviderFactories ?? throw new ArgumentNullException(nameof(valueProviderFactories));
|
||||
_cursor = new FilterCursor(filters);
|
||||
}
|
||||
|
||||
|
|
@ -289,13 +263,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
Debug.Assert(state != null);
|
||||
Debug.Assert(_authorizationContext != null);
|
||||
Debug.Assert(_authorizationContext.Result != null);
|
||||
|
||||
_logger.AuthorizationFailure((IFilterMetadata)state);
|
||||
|
||||
// If an authorization filter short circuits, the result is the last thing we execute
|
||||
// so just return that task instead of calling back into the state machine.
|
||||
// This is a short-circuit - execute relevant result filters + result and complete this invocation.
|
||||
isCompleted = true;
|
||||
return InvokeResultAsync(_authorizationContext.Result);
|
||||
_result = _authorizationContext.Result;
|
||||
return InvokeAlwaysRunResultFilters();
|
||||
}
|
||||
|
||||
case State.AuthorizationEnd:
|
||||
|
|
@ -477,7 +452,8 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
|
||||
_logger.ResourceFilterShortCircuited((IFilterMetadata)state);
|
||||
|
||||
var task = InvokeResultAsync(_resourceExecutingContext.Result);
|
||||
_result = _resourceExecutingContext.Result;
|
||||
var task = InvokeAlwaysRunResultFilters();
|
||||
if (task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
next = State.ResourceEnd;
|
||||
|
|
@ -662,13 +638,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
_exceptionContext.Result = new EmptyResult();
|
||||
}
|
||||
|
||||
if (scope == Scope.Resource)
|
||||
{
|
||||
Debug.Assert(_exceptionContext.Result != null);
|
||||
_result = _exceptionContext.Result;
|
||||
}
|
||||
_result = _exceptionContext.Result;
|
||||
|
||||
var task = InvokeResultAsync(_exceptionContext.Result);
|
||||
var task = InvokeAlwaysRunResultFilters();
|
||||
if (task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
next = State.ResourceInsideEnd;
|
||||
|
|
@ -701,7 +673,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Debug.Fail("unreachable");
|
||||
}
|
||||
|
||||
goto case State.ResultBegin;
|
||||
var task = InvokeResultFilters();
|
||||
if (task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
next = State.ResourceInsideEnd;
|
||||
return task;
|
||||
}
|
||||
goto case State.ResourceInsideEnd;
|
||||
}
|
||||
|
||||
case State.ActionBegin:
|
||||
|
|
@ -727,217 +705,12 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
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);
|
||||
_logger.BeforeExecutingMethodOnFilter(
|
||||
FilterTypeConstants.ResultFilter,
|
||||
nameof(IAsyncResultFilter.OnResultExecutionAsync),
|
||||
filter);
|
||||
|
||||
var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync);
|
||||
var task = InvokeResultFilters();
|
||||
if (task.Status != TaskStatus.RanToCompletion)
|
||||
{
|
||||
next = State.ResultAsyncEnd;
|
||||
next = State.ResourceInsideEnd;
|
||||
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)
|
||||
{
|
||||
// Short-circuited by not calling next || Short-circuited by setting Cancel == true
|
||||
_logger.ResultFilterShortCircuited(filter);
|
||||
|
||||
_resultExecutedContext = new ResultExecutedContext(
|
||||
_actionContext,
|
||||
_filters,
|
||||
resultExecutingContext.Result,
|
||||
_instance)
|
||||
{
|
||||
Canceled = true,
|
||||
};
|
||||
}
|
||||
|
||||
_diagnosticSource.AfterOnResultExecution(_resultExecutedContext, filter);
|
||||
_logger.AfterExecutingMethodOnFilter(
|
||||
FilterTypeConstants.ResultFilter,
|
||||
nameof(IAsyncResultFilter.OnResultExecutionAsync),
|
||||
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);
|
||||
_logger.BeforeExecutingMethodOnFilter(
|
||||
FilterTypeConstants.ResultFilter,
|
||||
nameof(IResultFilter.OnResultExecuting),
|
||||
filter);
|
||||
|
||||
filter.OnResultExecuting(resultExecutingContext);
|
||||
|
||||
_diagnosticSource.AfterOnResultExecuting(resultExecutingContext, filter);
|
||||
_logger.AfterExecutingMethodOnFilter(
|
||||
FilterTypeConstants.ResultFilter,
|
||||
nameof(IResultFilter.OnResultExecuting),
|
||||
filter);
|
||||
|
||||
if (_resultExecutingContext.Cancel)
|
||||
{
|
||||
// Short-circuited by setting Cancel == true
|
||||
_logger.ResultFilterShortCircuited(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);
|
||||
_logger.BeforeExecutingMethodOnFilter(
|
||||
FilterTypeConstants.ResultFilter,
|
||||
nameof(IResultFilter.OnResultExecuted),
|
||||
filter);
|
||||
|
||||
filter.OnResultExecuted(resultExecutedContext);
|
||||
|
||||
_diagnosticSource.AfterOnResultExecuted(resultExecutedContext, filter);
|
||||
_logger.AfterExecutingMethodOnFilter(
|
||||
FilterTypeConstants.ResultFilter,
|
||||
nameof(IResultFilter.OnResultExecuted),
|
||||
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 Task.CompletedTask;
|
||||
}
|
||||
|
||||
Rethrow(_resultExecutedContext);
|
||||
|
||||
goto case State.ResourceInsideEnd;
|
||||
}
|
||||
|
||||
|
|
@ -1048,7 +821,260 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
}
|
||||
|
||||
private async Task InvokeNextResultFilterAsync()
|
||||
private async Task InvokeAlwaysRunResultFilters()
|
||||
{
|
||||
var next = State.ResultBegin;
|
||||
var scope = Scope.Invoker;
|
||||
var state = (object)null;
|
||||
var isCompleted = false;
|
||||
|
||||
while (!isCompleted)
|
||||
{
|
||||
await ResultNext<IAlwaysRunResultFilter, IAsyncAlwaysRunResultFilter>(ref next, ref scope, ref state, ref isCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InvokeResultFilters()
|
||||
{
|
||||
var next = State.ResultBegin;
|
||||
var scope = Scope.Invoker;
|
||||
var state = (object)null;
|
||||
var isCompleted = false;
|
||||
|
||||
while (!isCompleted)
|
||||
{
|
||||
await ResultNext<IResultFilter, IAsyncResultFilter>(ref next, ref scope, ref state, ref isCompleted);
|
||||
}
|
||||
}
|
||||
|
||||
private Task ResultNext<TFilter, TFilterAsync>(ref State next, ref Scope scope, ref object state, ref bool isCompleted)
|
||||
where TFilter : class, IResultFilter
|
||||
where TFilterAsync : class, IAsyncResultFilter
|
||||
{
|
||||
var resultFilterKind = typeof(TFilter) == typeof(IAlwaysRunResultFilter) ?
|
||||
FilterTypeConstants.AlwaysRunResultFilter :
|
||||
FilterTypeConstants.ResultFilter;
|
||||
|
||||
switch (next)
|
||||
{
|
||||
case State.ResultBegin:
|
||||
{
|
||||
_cursor.Reset();
|
||||
goto case State.ResultNext;
|
||||
}
|
||||
|
||||
case State.ResultNext:
|
||||
{
|
||||
var current = _cursor.GetNextFilter<TFilter, TFilterAsync>();
|
||||
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 = (TFilterAsync)state;
|
||||
var resultExecutingContext = _resultExecutingContext;
|
||||
|
||||
_diagnosticSource.BeforeOnResultExecution(resultExecutingContext, filter);
|
||||
_logger.BeforeExecutingMethodOnFilter(
|
||||
resultFilterKind,
|
||||
nameof(IAsyncResultFilter.OnResultExecutionAsync),
|
||||
filter);
|
||||
|
||||
var task = filter.OnResultExecutionAsync(resultExecutingContext, InvokeNextResultFilterAwaitedAsync<TFilter, TFilterAsync>);
|
||||
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 = (TFilterAsync)state;
|
||||
var resultExecutingContext = _resultExecutingContext;
|
||||
var resultExecutedContext = _resultExecutedContext;
|
||||
|
||||
if (resultExecutedContext == null || resultExecutingContext.Cancel)
|
||||
{
|
||||
// Short-circuited by not calling next || Short-circuited by setting Cancel == true
|
||||
_logger.ResultFilterShortCircuited(filter);
|
||||
|
||||
_resultExecutedContext = new ResultExecutedContext(
|
||||
_actionContext,
|
||||
_filters,
|
||||
resultExecutingContext.Result,
|
||||
_instance)
|
||||
{
|
||||
Canceled = true,
|
||||
};
|
||||
}
|
||||
|
||||
_diagnosticSource.AfterOnResultExecution(_resultExecutedContext, filter);
|
||||
_logger.AfterExecutingMethodOnFilter(
|
||||
resultFilterKind,
|
||||
nameof(IAsyncResultFilter.OnResultExecutionAsync),
|
||||
filter);
|
||||
|
||||
goto case State.ResultEnd;
|
||||
}
|
||||
|
||||
case State.ResultSyncBegin:
|
||||
{
|
||||
Debug.Assert(state != null);
|
||||
Debug.Assert(_resultExecutingContext != null);
|
||||
|
||||
var filter = (TFilter)state;
|
||||
var resultExecutingContext = _resultExecutingContext;
|
||||
|
||||
_diagnosticSource.BeforeOnResultExecuting(resultExecutingContext, filter);
|
||||
_logger.BeforeExecutingMethodOnFilter(
|
||||
resultFilterKind,
|
||||
nameof(IResultFilter.OnResultExecuting),
|
||||
filter);
|
||||
|
||||
filter.OnResultExecuting(resultExecutingContext);
|
||||
|
||||
_diagnosticSource.AfterOnResultExecuting(resultExecutingContext, filter);
|
||||
_logger.AfterExecutingMethodOnFilter(
|
||||
resultFilterKind,
|
||||
nameof(IResultFilter.OnResultExecuting),
|
||||
filter);
|
||||
|
||||
if (_resultExecutingContext.Cancel)
|
||||
{
|
||||
// Short-circuited by setting Cancel == true
|
||||
_logger.ResultFilterShortCircuited(filter);
|
||||
|
||||
_resultExecutedContext = new ResultExecutedContext(
|
||||
resultExecutingContext,
|
||||
_filters,
|
||||
resultExecutingContext.Result,
|
||||
_instance)
|
||||
{
|
||||
Canceled = true,
|
||||
};
|
||||
|
||||
goto case State.ResultEnd;
|
||||
}
|
||||
|
||||
var task = InvokeNextResultFilterAsync<TFilter, TFilterAsync>();
|
||||
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 = (TFilter)state;
|
||||
var resultExecutedContext = _resultExecutedContext;
|
||||
|
||||
_diagnosticSource.BeforeOnResultExecuted(resultExecutedContext, filter);
|
||||
_logger.BeforeExecutingMethodOnFilter(
|
||||
resultFilterKind,
|
||||
nameof(IResultFilter.OnResultExecuted),
|
||||
filter);
|
||||
|
||||
filter.OnResultExecuted(resultExecutedContext);
|
||||
|
||||
_diagnosticSource.AfterOnResultExecuted(resultExecutedContext, filter);
|
||||
_logger.AfterExecutingMethodOnFilter(
|
||||
resultFilterKind,
|
||||
nameof(IResultFilter.OnResultExecuted),
|
||||
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;
|
||||
isCompleted = true;
|
||||
|
||||
if (scope == Scope.Result)
|
||||
{
|
||||
if (_resultExecutedContext == null)
|
||||
{
|
||||
_resultExecutedContext = new ResultExecutedContext(_actionContext, _filters, result, _instance);
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
Rethrow(_resultExecutedContext);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
default:
|
||||
throw new InvalidOperationException(); // Unreachable.
|
||||
}
|
||||
}
|
||||
|
||||
private async Task InvokeNextResultFilterAsync<TFilter, TFilterAsync>()
|
||||
where TFilter : class, IResultFilter
|
||||
where TFilterAsync : class, IAsyncResultFilter
|
||||
{
|
||||
try
|
||||
{
|
||||
|
|
@ -1058,7 +1084,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var isCompleted = false;
|
||||
while (!isCompleted)
|
||||
{
|
||||
await Next(ref next, ref scope, ref state, ref isCompleted);
|
||||
await ResultNext<TFilter, TFilterAsync>(ref next, ref scope, ref state, ref isCompleted);
|
||||
}
|
||||
}
|
||||
catch (Exception exception)
|
||||
|
|
@ -1072,7 +1098,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Debug.Assert(_resultExecutedContext != null);
|
||||
}
|
||||
|
||||
private async Task<ResultExecutedContext> InvokeNextResultFilterAwaitedAsync()
|
||||
private async Task<ResultExecutedContext> InvokeNextResultFilterAwaitedAsync<TFilter, TFilterAsync>()
|
||||
where TFilter : class, IResultFilter
|
||||
where TFilterAsync : class, IAsyncResultFilter
|
||||
{
|
||||
Debug.Assert(_resultExecutingContext != null);
|
||||
if (_resultExecutingContext.Cancel)
|
||||
|
|
@ -1088,7 +1116,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
await InvokeNextResultFilterAsync();
|
||||
await InvokeNextResultFilterAsync<TFilter, TFilterAsync>();
|
||||
|
||||
Debug.Assert(_resultExecutedContext != null);
|
||||
return _resultExecutedContext;
|
||||
|
|
@ -1221,6 +1249,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public const string ActionFilter = "Action Filter";
|
||||
public const string ExceptionFilter = "Exception Filter";
|
||||
public const string ResultFilter = "Result Filter";
|
||||
public const string AlwaysRunResultFilter = "Always Run Result Filter";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ using System.Net;
|
|||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using BasicWebSite.Models;
|
||||
using Newtonsoft.Json;
|
||||
|
|
@ -472,5 +473,23 @@ namespace Microsoft.AspNetCore.Mvc.FunctionalTests
|
|||
// Assert
|
||||
Assert.Equal("true", response);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task AlwaysRunResultFilters_CanRunWhenResourceFiltersShortCircuit()
|
||||
{
|
||||
// Arrange
|
||||
var url = "Filters/AlwaysRunResultFiltersCanRunWhenResourceFilterShortCircuit";
|
||||
var request = new HttpRequestMessage(HttpMethod.Post, url)
|
||||
{
|
||||
Content = new StringContent("Test", Encoding.UTF8, "application/json"),
|
||||
};
|
||||
|
||||
// Act
|
||||
var response = await Client.SendAsync(request);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(422, (int)response.StatusCode);
|
||||
Assert.Equal("Can't process this!", await response.Content.ReadAsStringAsync());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -556,6 +556,24 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
result.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_WithExceptionFilterInTheStack_InvokesResultFilter()
|
||||
{
|
||||
// Arrange
|
||||
var exceptionFilter = new Mock<IExceptionFilter>();
|
||||
var resultFilter = new Mock<IResultFilter>();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new IFilterMetadata[] { exceptionFilter.Object, resultFilter.Object });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
exceptionFilter.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Never());
|
||||
resultFilter.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_InvokesAuthorizationFilter()
|
||||
{
|
||||
|
|
@ -1713,6 +1731,348 @@ namespace Microsoft.AspNetCore.Mvc
|
|||
Times.Never());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AuthorizationFilterShortCircuit_InvokesAlwaysRunResultFilter()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter = new Mock<IAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()))
|
||||
.Callback<ResultExecutingContext>(c => Assert.Same(Result, c.Result))
|
||||
.Verifiable();
|
||||
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()))
|
||||
.Callback<ResultExecutedContext>(c => Assert.Same(Result, c.Result))
|
||||
.Verifiable();
|
||||
|
||||
var authorizationFilter = new Mock<IAsyncAuthorizationFilter>(MockBehavior.Strict);
|
||||
authorizationFilter
|
||||
.Setup(f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()))
|
||||
.Returns<AuthorizationFilterContext>((c) =>
|
||||
{
|
||||
c.Result = Result;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resultFilter.Object, });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AuthorizationFilterShortCircuit_InvokesAsyncAlwaysRunResultFilter()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter = new Mock<IAsyncAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns<ResultExecutingContext, ResultExecutionDelegate>((c, next) =>
|
||||
{
|
||||
Assert.Same(Result, c.Result);
|
||||
return next();
|
||||
})
|
||||
.Verifiable();
|
||||
|
||||
var authorizationFilter = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
||||
authorizationFilter
|
||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
||||
.Callback<AuthorizationFilterContext>((c) =>
|
||||
{
|
||||
c.Result = Result;
|
||||
});
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resultFilter.Object, });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AuthorizationFilterShortCircuit_DoesNotRunResultFilters()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter1 = new Mock<IResultFilter>(MockBehavior.Strict);
|
||||
resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()));
|
||||
resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()));
|
||||
|
||||
var resultFilter2 = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
|
||||
resultFilter2.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()));
|
||||
|
||||
var resultFilter3 = new Mock<IAsyncAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter3.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var authorizationFilter = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
||||
authorizationFilter
|
||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
||||
.Callback<AuthorizationFilterContext>((c) =>
|
||||
{
|
||||
c.Result = Result;
|
||||
});
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resultFilter1.Object, resultFilter2.Object, resultFilter3.Object, });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter1.Verify(
|
||||
f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()),
|
||||
Times.Never());
|
||||
resultFilter1.Verify(
|
||||
f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()),
|
||||
Times.Never());
|
||||
resultFilter2.Verify(
|
||||
f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()),
|
||||
Times.Never());
|
||||
resultFilter3.Verify(
|
||||
f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_ResourceFilterShortCircuit_InvokesAlwaysRunResultFilter()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter = new Mock<IAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()))
|
||||
.Callback<ResultExecutingContext>(c => Assert.Same(Result, c.Result))
|
||||
.Verifiable();
|
||||
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()))
|
||||
.Callback<ResultExecutedContext>(c => Assert.Same(Result, c.Result))
|
||||
.Verifiable();
|
||||
|
||||
var resourceFilter = new Mock<IAsyncResourceFilter>(MockBehavior.Strict);
|
||||
resourceFilter
|
||||
.Setup(f => f.OnResourceExecutionAsync(It.IsAny<ResourceExecutingContext>(), It.IsAny<ResourceExecutionDelegate>()))
|
||||
.Returns<ResourceExecutingContext, ResourceExecutionDelegate>((c, next) =>
|
||||
{
|
||||
c.Result = Result;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object, });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_ResourceFilterShortCircuit_InvokesAsyncAlwaysRunResultFilter()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter = new Mock<IAsyncAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns<ResultExecutingContext, ResultExecutionDelegate>((c, next) =>
|
||||
{
|
||||
Assert.Same(Result, c.Result);
|
||||
return next();
|
||||
})
|
||||
.Verifiable();
|
||||
|
||||
var resourceFilter = new Mock<IResourceFilter>(MockBehavior.Strict);
|
||||
resourceFilter
|
||||
.Setup(f => f.OnResourceExecuting(It.IsAny<ResourceExecutingContext>()))
|
||||
.Callback<ResourceExecutingContext>(c => c.Result = Result);
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object, });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_ResourceFilterShortCircuit_DoesNotRunResultFilters()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter1 = new Mock<IResultFilter>(MockBehavior.Strict);
|
||||
resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()));
|
||||
resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()));
|
||||
|
||||
var resultFilter2 = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
|
||||
resultFilter2.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()));
|
||||
|
||||
var resultFilter3 = new Mock<IAsyncAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter3.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var resourceFilter = new Mock<IResourceFilter>(MockBehavior.Strict);
|
||||
resourceFilter
|
||||
.Setup(f => f.OnResourceExecuting(It.IsAny<ResourceExecutingContext>()))
|
||||
.Callback<ResourceExecutingContext>(c => c.Result = Result);
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter1.Object, resultFilter2.Object, resultFilter3.Object, });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter1.Verify(
|
||||
f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()),
|
||||
Times.Never());
|
||||
resultFilter1.Verify(
|
||||
f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()),
|
||||
Times.Never());
|
||||
resultFilter2.Verify(
|
||||
f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()),
|
||||
Times.Never());
|
||||
resultFilter3.Verify(
|
||||
f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_ExceptionFilterShortCircuit_InvokesAlwaysRunResultFilter()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter = new Mock<IAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()))
|
||||
.Callback<ResultExecutingContext>(c => Assert.Same(Result, c.Result))
|
||||
.Verifiable();
|
||||
resultFilter.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()))
|
||||
.Callback<ResultExecutedContext>(c => Assert.Same(Result, c.Result))
|
||||
.Verifiable();
|
||||
|
||||
var exceptionFilter = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
||||
exceptionFilter
|
||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
||||
.Returns<ExceptionContext>(c =>
|
||||
{
|
||||
c.Result = Result;
|
||||
return Task.CompletedTask;
|
||||
});
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { exceptionFilter.Object, resultFilter.Object, }, Exception);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_ExceptionFilterShortCircuit_InvokesAsyncAlwaysRunResultFilter()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter = new Mock<IAsyncAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns<ResultExecutingContext, ResultExecutionDelegate>((c, next) =>
|
||||
{
|
||||
Assert.Same(Result, c.Result);
|
||||
return next();
|
||||
})
|
||||
.Verifiable();
|
||||
|
||||
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
||||
exceptionFilter
|
||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
||||
.Callback<ExceptionContext>(c => c.Result = Result);
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { exceptionFilter.Object, resultFilter.Object, }, Exception);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter.Verify();
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_ExceptionFilterShortCircuit_DoesNotRunResultFilters()
|
||||
{
|
||||
// Arrange
|
||||
var resultFilter1 = new Mock<IResultFilter>(MockBehavior.Strict);
|
||||
resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()));
|
||||
resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()));
|
||||
|
||||
var resultFilter2 = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
|
||||
resultFilter2.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()));
|
||||
|
||||
var resultFilter3 = new Mock<IAsyncAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter3.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns(Task.CompletedTask);
|
||||
|
||||
var exceptionFilter = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
||||
exceptionFilter
|
||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
||||
.Callback<ExceptionContext>(c => c.Result = Result);
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new IFilterMetadata[] { exceptionFilter.Object, resultFilter1.Object, resultFilter2.Object, resultFilter3.Object, },
|
||||
Exception);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter1.Verify(
|
||||
f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()),
|
||||
Times.Never());
|
||||
resultFilter1.Verify(
|
||||
f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()),
|
||||
Times.Never());
|
||||
resultFilter2.Verify(
|
||||
f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()),
|
||||
Times.Never());
|
||||
resultFilter3.Verify(
|
||||
f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()),
|
||||
Times.Once());
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_AlwaysRunResultFiltersAndRunWithResultFilters()
|
||||
{
|
||||
// Arrange
|
||||
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<IAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter2
|
||||
.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()))
|
||||
.Verifiable();
|
||||
resultFilter2
|
||||
.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()))
|
||||
.Verifiable();
|
||||
|
||||
var resultFilter3 = new Mock<IAsyncResultFilter>(MockBehavior.Strict);
|
||||
resultFilter3.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns<ResultExecutingContext, ResultExecutionDelegate>((c, next) => next())
|
||||
.Verifiable();
|
||||
|
||||
var resultFilter4 = new Mock<IAsyncAlwaysRunResultFilter>(MockBehavior.Strict);
|
||||
resultFilter4.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
|
||||
.Returns<ResultExecutingContext, ResultExecutionDelegate>((c, next) => next())
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(
|
||||
new IFilterMetadata[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object, resultFilter4.Object });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
resultFilter1.Verify();
|
||||
resultFilter2.Verify();
|
||||
resultFilter3.Verify();
|
||||
resultFilter4.Verify();
|
||||
}
|
||||
|
||||
public class TestResult : ActionResult
|
||||
{
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,22 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using BasicWebSite.Models;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
|
||||
namespace BasicWebSite.Controllers
|
||||
{
|
||||
public class FiltersController : Controller
|
||||
{
|
||||
[HttpPost]
|
||||
[Consumes("application/yaml")]
|
||||
[UnprocessableResultFilter]
|
||||
public IActionResult AlwaysRunResultFiltersCanRunWhenResourceFilterShortCircuit([FromBody] Product product) =>
|
||||
throw new Exception("Shouldn't be executed");
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
|
||||
namespace BasicWebSite
|
||||
{
|
||||
public class UnprocessableResultFilter : Attribute, IAlwaysRunResultFilter
|
||||
{
|
||||
public void OnResultExecuted(ResultExecutedContext context)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
if (context.Result is StatusCodeResult statusCodeResult &&
|
||||
statusCodeResult.StatusCode == 415)
|
||||
{
|
||||
context.Result = new ObjectResult("Can't process this!")
|
||||
{
|
||||
StatusCode = 422,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue