Introduce IAlwaysRunResultFilter \ IAsyncAlwaysRunResultFilter (#7120)

* Introduce IAlwaysRunResultFilter \ IAsyncAlwaysRunResultFilter

Fixes #7105
This commit is contained in:
Pranav K 2017-12-22 10:52:52 -08:00 committed by GitHub
parent 455cf2e0c3
commit 350c4ec4f6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 795 additions and 256 deletions

View File

@ -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"/>.

View File

@ -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
{
}
}

View File

@ -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
{
}
}

View File

@ -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>

View File

@ -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>

View File

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

View File

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

View File

@ -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
{
}

View File

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

View File

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