[Fixes #5175] Async resource filters' short circuited result getting executed more than once.
This commit is contained in:
parent
7036e2b0f5
commit
ece8f33a65
|
|
@ -357,7 +357,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
goto case State.ExceptionBegin;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
case State.ResourceAsyncBegin:
|
||||
{
|
||||
Debug.Assert(state != null);
|
||||
|
|
@ -392,13 +392,14 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Canceled = true,
|
||||
Result = _resourceExecutingContext.Result,
|
||||
};
|
||||
}
|
||||
|
||||
_diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter);
|
||||
_diagnosticSource.AfterOnResourceExecution(_resourceExecutedContext, filter);
|
||||
|
||||
if (_resourceExecutingContext.Result != null)
|
||||
{
|
||||
goto case State.ResourceShortCircuit;
|
||||
// A filter could complete a Task without setting a result
|
||||
if (_resourceExecutingContext.Result != null)
|
||||
{
|
||||
goto case State.ResourceShortCircuit;
|
||||
}
|
||||
}
|
||||
|
||||
goto case State.ResourceEnd;
|
||||
|
|
@ -1222,7 +1223,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
arguments,
|
||||
controller);
|
||||
logger.ActionMethodExecuting(controllerContext, orderedArguments);
|
||||
|
||||
|
||||
var returnType = executor.MethodReturnType;
|
||||
if (returnType == typeof(void))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -82,31 +82,41 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
// Arrange
|
||||
Exception exception = null;
|
||||
IActionResult result = null;
|
||||
IActionResult resultFromAction = null;
|
||||
var expected = new Mock<IActionResult>(MockBehavior.Strict);
|
||||
expected
|
||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
||||
.Returns(Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
var filter = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
||||
filter
|
||||
var filter1 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
||||
filter1
|
||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
||||
.Verifiable();
|
||||
var filter2 = new Mock<IExceptionFilter>(MockBehavior.Strict);
|
||||
filter2
|
||||
.Setup(f => f.OnException(It.IsAny<ExceptionContext>()))
|
||||
.Callback<ExceptionContext>(context =>
|
||||
{
|
||||
exception = context.Exception;
|
||||
result = context.Result;
|
||||
resultFromAction = context.Result;
|
||||
|
||||
// Handle the exception
|
||||
context.Result = new EmptyResult();
|
||||
context.Result = expected.Object;
|
||||
})
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(filter.Object, actionThrows: true);
|
||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
filter.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Once());
|
||||
expected.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
||||
filter2.Verify(f => f.OnException(It.IsAny<ExceptionContext>()), Times.Once());
|
||||
|
||||
Assert.Same(_actionException, exception);
|
||||
Assert.Null(result);
|
||||
Assert.Null(resultFromAction);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -114,34 +124,45 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
{
|
||||
// Arrange
|
||||
Exception exception = null;
|
||||
IActionResult result = null;
|
||||
IActionResult resultFromAction = null;
|
||||
var expected = new Mock<IActionResult>(MockBehavior.Strict);
|
||||
expected
|
||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
||||
.Returns(Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
var filter = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
||||
filter
|
||||
var filter1 = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
||||
filter1
|
||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
||||
.Returns<ExceptionContext>((context) => Task.FromResult(true))
|
||||
.Verifiable();
|
||||
var filter2 = new Mock<IAsyncExceptionFilter>(MockBehavior.Strict);
|
||||
filter2
|
||||
.Setup(f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()))
|
||||
.Callback<ExceptionContext>(context =>
|
||||
{
|
||||
exception = context.Exception;
|
||||
result = context.Result;
|
||||
resultFromAction = context.Result;
|
||||
|
||||
// Handle the exception
|
||||
context.Result = new EmptyResult();
|
||||
context.Result = expected.Object;
|
||||
})
|
||||
.Returns<ExceptionContext>((context) => Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(filter.Object, actionThrows: true);
|
||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
filter.Verify(
|
||||
expected.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
||||
filter2.Verify(
|
||||
f => f.OnExceptionAsync(It.IsAny<ExceptionContext>()),
|
||||
Times.Once());
|
||||
|
||||
Assert.Same(_actionException, exception);
|
||||
Assert.Null(result);
|
||||
Assert.Null(resultFromAction);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -340,22 +361,33 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public async Task InvokeAction_InvokesAuthorizationFilter_ShortCircuit()
|
||||
{
|
||||
// Arrange
|
||||
var challenge = new Mock<IActionResult>(MockBehavior.Loose).Object;
|
||||
var challenge = new Mock<IActionResult>(MockBehavior.Strict);
|
||||
challenge
|
||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
||||
.Returns(Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
var filter1 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
||||
filter1
|
||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
||||
.Callback<AuthorizationFilterContext>(c => c.Result = challenge)
|
||||
.Callback<AuthorizationFilterContext>(c => Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
var filter2 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
||||
filter2
|
||||
.Setup(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()))
|
||||
.Callback<AuthorizationFilterContext>(c => c.Result = challenge.Object)
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object });
|
||||
var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object, filter3.Object });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
challenge.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
||||
filter1.Verify(f => f.OnAuthorization(It.IsAny<AuthorizationFilterContext>()), Times.Once());
|
||||
Assert.False(invoker.ControllerFactory.CreateCalled);
|
||||
}
|
||||
|
|
@ -364,26 +396,39 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public async Task InvokeAction_InvokesAsyncAuthorizationFilter_ShortCircuit()
|
||||
{
|
||||
// Arrange
|
||||
var challenge = new Mock<IActionResult>(MockBehavior.Loose).Object;
|
||||
var challenge = new Mock<IActionResult>(MockBehavior.Strict);
|
||||
challenge
|
||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
||||
.Returns(Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
var filter1 = new Mock<IAsyncAuthorizationFilter>(MockBehavior.Strict);
|
||||
filter1
|
||||
.Setup(f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()))
|
||||
.Returns<AuthorizationFilterContext>((context) =>
|
||||
{
|
||||
context.Result = challenge;
|
||||
return Task.FromResult(true);
|
||||
})
|
||||
.Verifiable();
|
||||
|
||||
var filter2 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
||||
var filter2 = new Mock<IAsyncAuthorizationFilter>(MockBehavior.Strict);
|
||||
filter2
|
||||
.Setup(f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()))
|
||||
.Returns<AuthorizationFilterContext>((context) =>
|
||||
{
|
||||
context.Result = challenge.Object;
|
||||
return Task.FromResult(true);
|
||||
});
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object });
|
||||
var filter3 = new Mock<IAuthorizationFilter>(MockBehavior.Strict);
|
||||
|
||||
var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object });
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
challenge.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
||||
filter1.Verify(
|
||||
f => f.OnAuthorizationAsync(It.IsAny<AuthorizationFilterContext>()),
|
||||
Times.Once());
|
||||
|
|
@ -392,7 +437,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_ExceptionInAuthorizationFilterCannotBeHandledByOtherFilters()
|
||||
public async Task InvokeAction_ExceptionInAuthorizationFilter_CannotBeHandledByOtherFilters()
|
||||
{
|
||||
// Arrange
|
||||
var expected = new InvalidCastException();
|
||||
|
|
@ -524,7 +569,11 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
public async Task InvokeAction_InvokesActionFilter_ShortCircuit()
|
||||
{
|
||||
// Arrange
|
||||
var result = new EmptyResult();
|
||||
var result = new Mock<IActionResult>(MockBehavior.Strict);
|
||||
result
|
||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
||||
.Returns(Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
ActionExecutedContext context = null;
|
||||
|
||||
|
|
@ -538,7 +587,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
var actionFilter2 = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
actionFilter2
|
||||
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
|
||||
.Callback<ActionExecutingContext>(c => c.Result = result)
|
||||
.Callback<ActionExecutingContext>(c => c.Result = result.Object)
|
||||
.Verifiable();
|
||||
|
||||
var actionFilter3 = new Mock<IActionFilter>(MockBehavior.Strict);
|
||||
|
|
@ -559,23 +608,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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);
|
||||
Assert.Same(context.Result, result.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult()
|
||||
{
|
||||
// Arrange
|
||||
var result = new EmptyResult();
|
||||
var result = new Mock<IActionResult>(MockBehavior.Strict);
|
||||
result
|
||||
.Setup(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()))
|
||||
.Returns(Task.FromResult(true))
|
||||
.Verifiable();
|
||||
|
||||
ActionExecutedContext context = null;
|
||||
|
||||
|
|
@ -592,29 +647,34 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
.Returns<ActionExecutingContext, ActionExecutionDelegate>((c, next) =>
|
||||
{
|
||||
// Notice we're not calling next
|
||||
c.Result = result;
|
||||
c.Result = result.Object;
|
||||
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 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,
|
||||
resultFilter.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());
|
||||
|
||||
|
|
@ -622,11 +682,13 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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());
|
||||
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);
|
||||
Assert.Same(context.Result, result.Object);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -766,10 +828,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
.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; })
|
||||
.Setup(f => f.OnActionExecuting(It.IsAny<ActionExecutingContext>()))
|
||||
.Callback<ActionExecutingContext>(c => { throw exception; })
|
||||
.Verifiable();
|
||||
|
||||
var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object });
|
||||
|
|
@ -782,6 +843,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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);
|
||||
|
|
@ -858,7 +920,9 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
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 }, actionThrows: true);
|
||||
var invoker = CreateInvoker(
|
||||
new IFilterMetadata[] { actionFilter.Object, resultFilter.Object },
|
||||
actionThrows: true);
|
||||
|
||||
// Act
|
||||
await invoker.InvokeAsync();
|
||||
|
|
@ -913,7 +977,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
}
|
||||
|
||||
[Fact]
|
||||
public async Task InvokeAction_InvokesResultFilter_ShortCircuit()
|
||||
public async Task InvokeAction_InvokesResultFilter_ShortCircuit_WithCancel()
|
||||
{
|
||||
// Arrange
|
||||
ResultExecutedContext context = null;
|
||||
|
|
@ -947,6 +1011,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
filter1.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Once());
|
||||
|
||||
filter2.Verify(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()), Times.Once());
|
||||
filter2.Verify(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()), Times.Never());
|
||||
|
||||
Assert.True(context.Canceled);
|
||||
}
|
||||
|
|
@ -1797,6 +1862,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
expected.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
||||
Assert.Same(expected.Object, context.Result);
|
||||
Assert.True(context.Canceled);
|
||||
Assert.False(invoker.ControllerFactory.CreateCalled);
|
||||
|
|
@ -1899,6 +1965,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
await invoker.InvokeAsync();
|
||||
|
||||
// Assert
|
||||
expected.Verify(r => r.ExecuteResultAsync(It.IsAny<ActionContext>()), Times.Once());
|
||||
Assert.Same(expected.Object, context.Result);
|
||||
Assert.True(context.Canceled);
|
||||
Assert.False(invoker.ControllerFactory.CreateCalled);
|
||||
|
|
@ -2751,7 +2818,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
|
|||
Assert.NotNull(listener.AfterAction?.ActionDescriptor);
|
||||
Assert.NotNull(listener.AfterAction?.HttpContext);
|
||||
}
|
||||
|
||||
|
||||
public async Task InvokeAction_ExceptionBubbling_AsyncActionFilter_To_ResourceFilter()
|
||||
{
|
||||
// Arrange
|
||||
|
|
|
|||
Loading…
Reference in New Issue