// 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.Buffers; using System.Collections.Generic; using System.ComponentModel; using System.Diagnostics; using System.Globalization; using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Formatters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Options; using Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.Internal { public class ControllerActionInvokerTest { // Intentionally choosing an uncommon exception type. private readonly Exception _actionException = new DivideByZeroException(); private readonly ContentResult _result = new ContentResult() { Content = "Hello, world!" }; private readonly TestController _controller = new TestController(); [Fact] public async Task InvokeAction_DoesNotInvokeExceptionFilter_WhenActionDoesNotThrow() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnException(It.IsAny())) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: false); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnException(It.IsAny()), Times.Never()); } [Fact] public async Task InvokeAction_DoesNotAsyncInvokeExceptionFilter_WhenActionDoesNotThrow() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnExceptionAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: false); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Never()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_WhenActionThrows() { // Arrange Exception exception = null; IActionResult resultFromAction = null; var expected = new Mock(MockBehavior.Strict); expected .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnException(It.IsAny())) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(context => { exception = context.Exception; resultFromAction = context.Result; // Handle the exception context.Result = expected.Object; }) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); Assert.Same(_actionException, exception); Assert.Null(resultFromAction); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_WhenActionThrows() { // Arrange Exception exception = null; IActionResult resultFromAction = null; var expected = new Mock(MockBehavior.Strict); expected .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { exception = context.Exception; resultFromAction = context.Result; // Handle the exception context.Result = expected.Object; }) .Returns((context) => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter2.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Once()); Assert.Same(_actionException, exception); Assert.Null(resultFromAction); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull_WithoutResult() { // Arrange var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(context => { filter2.ToString(); context.Exception = null; }) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnException(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionNull_WithResult() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(context => { context.Result = result.Object; context.Exception = null; }) .Verifiable(); // Result filters are never used when an exception bubbles up to exception filters. var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnException(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionHandled_WithoutResult() { // Arrange var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(context => { context.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnException(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_ShortCircuit_ExceptionHandled_WithResult() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(context => { context.Result = result.Object; context.ExceptionHandled = true; }) .Verifiable(); // Result filters are never used when an exception bubbles up to exception filters. var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnException(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull_WithoutResult() { // Arrange var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { context.Exception = null; }) .Returns((context) => Task.FromResult(true)) .Verifiable(); var filterMetadata = new IFilterMetadata[] { filter1.Object, filter2.Object }; var invoker = CreateInvoker(filterMetadata, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionNull_WithResult() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { context.Exception = null; context.Result = result.Object; }) .Returns((context) => Task.FromResult(true)) .Verifiable(); // Result filters are never used when an exception bubbles up to exception filters. var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled_WithoutResult() { // Arrange var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { context.ExceptionHandled = true; }) .Returns((context) => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_ShortCircuit_ExceptionHandled_WithResult() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { context.ExceptionHandled = true; context.Result = result.Object; }) .Returns((context) => Task.FromResult(true)) .Verifiable(); // Result filters are never used when an exception bubbles up to exception filters. var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter2.Verify( f => f.OnExceptionAsync(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncExceptionFilter_SettingResultDoesNotShortCircuit() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnExceptionAsync(It.IsAny())) .Callback(context => { context.Result = result.Object; }) .Returns((context) => Task.FromResult(true)) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(c => { }) // Does nothing, we just want to verify that it was called. .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnExceptionAsync(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_UnhandledExceptionIsThrown() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnException(It.IsAny())) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: true); // Act await Assert.ThrowsAsync(_actionException.GetType(), async () => await invoker.InvokeAsync()); // Assert filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_ResultIsExecuted_WithoutResultFilters() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnException(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { filter.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnException(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesExceptionFilter_SettingResultDoesNotShortCircuit() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnException(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnException(It.IsAny())) .Callback(c => { }) // Does nothing, we just want to verify that it was called. .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { filter1.Object, filter2.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnException(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnException(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAuthorizationFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnAuthorization(It.IsAny())).Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncAuthorizationFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnAuthorizationAsync(It.IsAny())) .Returns(context => Task.FromResult(true)) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnAuthorizationAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAuthorizationFilter_ShortCircuit() { // Arrange var challenge = new Mock(MockBehavior.Strict); challenge .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnAuthorization(It.IsAny())) .Callback(c => Task.FromResult(true)) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnAuthorization(It.IsAny())) .Callback(c => c.Result = challenge.Object) .Verifiable(); var filter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object, filter3.Object }); // Act await invoker.InvokeAsync(); // Assert challenge.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] public async Task InvokeAction_InvokesAsyncAuthorizationFilter_ShortCircuit() { // Arrange var challenge = new Mock(MockBehavior.Strict); challenge .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnAuthorizationAsync(It.IsAny())) .Returns((context) => { return Task.FromResult(true); }) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnAuthorizationAsync(It.IsAny())) .Returns((context) => { context.Result = challenge.Object; return Task.FromResult(true); }); var filter3 = new Mock(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()), Times.Once()); filter1.Verify( f => f.OnAuthorizationAsync(It.IsAny()), Times.Once()); Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] public async Task InvokeAction_ExceptionInAuthorizationFilter_CannotBeHandledByOtherFilters() { // Arrange var expected = new InvalidCastException(); var exceptionFilter = new Mock(MockBehavior.Strict); exceptionFilter .Setup(f => f.OnException(It.IsAny())) .Callback(context => { // Mark as handled context.Result = new EmptyResult(); }) .Verifiable(); var authorizationFilter1 = new Mock(MockBehavior.Strict); authorizationFilter1 .Setup(f => f.OnAuthorization(It.IsAny())) .Callback(c => { throw expected; }) .Verifiable(); // None of these filters should run var authorizationFilter2 = new Mock(MockBehavior.Strict); var resourceFilter = new Mock(MockBehavior.Strict); var actionFilter = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { exceptionFilter.Object, authorizationFilter1.Object, authorizationFilter2.Object, resourceFilter.Object, actionFilter.Object, resultFilter.Object, }); // Act var thrown = await Assert.ThrowsAsync(invoker.InvokeAsync); // Assert Assert.Same(expected, thrown); exceptionFilter.Verify(f => f.OnException(It.IsAny()), Times.Never()); authorizationFilter1.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAuthorizationFilter_ChallengeNotSeenByResultFilters() { // Arrange var challenge = new Mock(MockBehavior.Strict); challenge .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var authorizationFilter = new Mock(MockBehavior.Strict); authorizationFilter .Setup(f => f.OnAuthorization(It.IsAny())) .Callback(c => c.Result = challenge.Object) .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resultFilter.Object }); // Act await invoker.InvokeAsync(); // Assert authorizationFilter.Verify(f => f.OnAuthorization(It.IsAny()), Times.Once()); challenge.Verify(c => c.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesActionFilter() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); Assert.Same(_result, result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (context, next) => { var resultContext = await next(); result = resultContext.Result; }) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); Assert.Same(_result, result); } [Fact] public async Task InvokeAction_InvokesActionFilter_ShortCircuit() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); ActionExecutedContext context = null; var actionFilter1 = new Mock(MockBehavior.Strict); actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var actionFilter2 = new Mock(MockBehavior.Strict); actionFilter2 .Setup(f => f.OnActionExecuting(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); var actionFilter3 = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { actionFilter1.Object, actionFilter2.Object, actionFilter3.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); actionFilter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.Same(context.Result, result.Object); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); ActionExecutedContext context = null; var actionFilter1 = new Mock(MockBehavior.Strict); actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var actionFilter2 = new Mock(MockBehavior.Strict); actionFilter2 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Notice we're not calling next c.Result = result.Object; return Task.FromResult(true); }) .Verifiable(); var actionFilter3 = new Mock(MockBehavior.Strict); var resultFilter1 = new Mock(MockBehavior.Strict); resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter1.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var resultFilter2 = new Mock(MockBehavior.Strict); resultFilter2.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter2.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { actionFilter1.Object, actionFilter2.Object, actionFilter3.Object, resultFilter1.Object, resultFilter2.Object, }); // Act await invoker.InvokeAsync(); // Assert result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); actionFilter2.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.Same(context.Result, result.Object); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithoutResult() { // Arrange ActionExecutedContext context = null; var actionFilter1 = new Mock(MockBehavior.Strict); actionFilter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var actionFilter2 = new Mock(MockBehavior.Strict); actionFilter2 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Notice we're not calling next return Task.FromResult(true); }) .Verifiable(); var actionFilter3 = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { actionFilter1.Object, actionFilter2.Object, actionFilter3.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert actionFilter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); actionFilter2.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_ShortCircuit_WithResult_CallNext() { // Arrange var actionFilter = new Mock(MockBehavior.Strict); actionFilter .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { c.Result = new EmptyResult(); await next(); }) .Verifiable(); var message = "If an IAsyncActionFilter provides a result value by setting the Result property of " + "ActionExecutingContext to a non-null value, then it cannot call the next filter by invoking " + "ActionExecutionDelegate."; var invoker = CreateInvoker(actionFilter.Object); // Act & Assert await ExceptionAssert.ThrowsAsync( async () => await invoker.InvokeAsync(), message); } [Fact] public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByAction() { // Arrange ActionExecutedContext context = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception so the test doesn't throw. Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(filter.Object, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); Assert.Same(_actionException, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesActionFilter_WithExceptionThrownByActionFilter() { // Arrange var exception = new DataMisalignedException(); ActionExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception so the test doesn't throw. Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnActionExecuting(It.IsAny())) .Callback(c => { throw exception; }) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Never()); Assert.Same(exception, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncActionFilter_WithExceptionThrownByActionFilter() { // Arrange var exception = new DataMisalignedException(); ActionExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); // Handle the exception so the test doesn't throw. Assert.False(context.ExceptionHandled); context.ExceptionHandled = true; }) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter2 .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { throw exception; }) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify( f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); filter2.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); Assert.Same(exception, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesActionFilter_HandleException() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var actionFilter = new Mock(MockBehavior.Strict); actionFilter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); actionFilter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => { // Handle the exception so the test doesn't throw. Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; c.Result = result.Object; }) .Verifiable(); var resultFilter = new Mock(MockBehavior.Strict); resultFilter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker( new IFilterMetadata[] { actionFilter.Object, resultFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert actionFilter.Verify(f => f.OnActionExecuting(It.IsAny()), Times.Once()); actionFilter.Verify(f => f.OnActionExecuted(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (context, next) => await next()) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter_ShortCircuit_WithCancel() { // Arrange ResultExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => { filter2.ToString(); c.Cancel = true; }) .Verifiable(); var filter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Never()); Assert.True(context.Canceled); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithCancel() { // Arrange ResultExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Not calling next here c.Cancel = true; return Task.FromResult(true); }) .Verifiable(); var filter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); filter2.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.IsType(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel() { // Arrange ResultExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); filter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Not calling next here return Task.FromResult(true); }) .Verifiable(); var filter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { filter1.Object, filter2.Object, filter3.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); filter2.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); Assert.True(context.Canceled); Assert.IsType(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_ShortCircuit_WithoutCancel_CallNext() { // Arrange var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { // Not calling next here c.Cancel = true; await next(); }) .Verifiable(); var message = "If an IAsyncResultFilter cancels execution by setting the Cancel property of " + "ResultExecutingContext to 'true', then it cannot call the next filter by invoking " + "ResultExecutionDelegate."; var invoker = CreateInvoker(filter.Object); // Act & Assert await ExceptionAssert.ThrowsAsync( async () => await invoker.InvokeAsync(), message); } [Fact] public async Task InvokeAction_InvokesResultFilter_ExceptionGoesUnhandled() { // Arrange var exception = new DataMisalignedException(); var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Throws(exception) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); filter.Setup(f => f.OnResultExecuted(It.IsAny())).Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await Assert.ThrowsAsync(exception.GetType(), async () => await invoker.InvokeAsync()); // Assert result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResult() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Throws(exception) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); filter .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert Assert.Same(exception, context.Exception); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResult() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Throws(exception) .Verifiable(); var filter = new Mock(MockBehavior.Strict); filter .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { c.Result = result.Object; context = await next(); // Handle the exception Assert.False(context.ExceptionHandled); context.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(filter.Object); // Act await invoker.InvokeAsync(); // Assert Assert.Same(exception, context.Exception); result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); filter.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResultFilter_WithExceptionThrownByResultFilter() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var resultFilter1 = new Mock(MockBehavior.Strict); resultFilter1.Setup(f => f.OnResultExecuting(It.IsAny())).Verifiable(); resultFilter1 .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var resultFilter2 = new Mock(MockBehavior.Strict); resultFilter2 .Setup(f => f.OnResultExecuting(It.IsAny())) .Throws(exception) .Verifiable(); var resultFilter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Same(exception, context.Exception); resultFilter1.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); resultFilter1.Verify(f => f.OnResultExecuted(It.IsAny()), Times.Once()); resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResultFilter_WithExceptionThrownByResultFilter() { // Arrange ResultExecutedContext context = null; var exception = new DataMisalignedException(); var resultFilter1 = new Mock(MockBehavior.Strict); resultFilter1 .Setup(f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); // Handle the exception Assert.False(context.ExceptionHandled); context.ExceptionHandled = true; }) .Verifiable(); var resultFilter2 = new Mock(MockBehavior.Strict); resultFilter2 .Setup(f => f.OnResultExecuting(It.IsAny())) .Throws(exception) .Verifiable(); var resultFilter3 = new Mock(MockBehavior.Strict); var invoker = CreateInvoker(new IFilterMetadata[] { resultFilter1.Object, resultFilter2.Object, resultFilter3.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Same(exception, context.Exception); resultFilter1.Verify( f => f.OnResultExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); resultFilter2.Verify(f => f.OnResultExecuting(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter() { // Arrange var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { await next(); }) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }); // Act await invoker.InvokeAsync(); // Assert resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesResourceFilter() { // Arrange var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecuting(It.IsAny())) .Verifiable(); resourceFilter .Setup(f => f.OnResourceExecuted(It.IsAny())) .Verifiable(); var invoker = CreateInvoker(resourceFilter.Object); // Act await invoker.InvokeAsync(); // Assert resourceFilter.Verify( f => f.OnResourceExecuted(It.IsAny()), Times.Once()); resourceFilter.Verify( f => f.OnResourceExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromAction() { // Arrange ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var invoker = CreateInvoker(resourceFilter.Object); // Act await invoker.InvokeAsync(); // Assert Assert.Same(_result, context.Result); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromActionFilter() { // Arrange var expected = Mock.Of(); ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var actionFilter = new Mock(MockBehavior.Strict); actionFilter .Setup(f => f.OnActionExecuting(It.IsAny())) .Callback((c) => { c.Result = expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Same(expected, context.Result); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromExceptionFilter() { // Arrange var expected = Mock.Of(); ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var exceptionFilter = new Mock(MockBehavior.Strict); exceptionFilter .Setup(f => f.OnException(It.IsAny())) .Callback((c) => { c.Result = expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert Assert.Same(expected, context.Result); Assert.Null(context.Exception); Assert.Null(context.ExceptionDispatchInfo); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_WithActionResult_FromResultFilter() { // Arrange var expected = Mock.Of(); ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var resultFilter = new Mock(MockBehavior.Loose); resultFilter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback((c) => { c.Result = expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Same(expected, context.Result); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromAction() { // Arrange ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); context.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert Assert.Same(_actionException, context.Exception); Assert.Same(_actionException, context.ExceptionDispatchInfo.SourceException); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_FromActionFilter() { // Arrange var expected = new DataMisalignedException(); ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); context.ExceptionHandled = true; }) .Verifiable(); var actionFilter = new Mock(MockBehavior.Strict); actionFilter .Setup(f => f.OnActionExecuting(It.IsAny())) .Callback((c) => { throw expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, actionFilter.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Same(expected, context.Exception); Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromExceptionFilter() { // Arrange var expected = new DataMisalignedException(); ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); context.ExceptionHandled = true; }) .Verifiable(); var exceptionFilter = new Mock(MockBehavior.Strict); exceptionFilter .Setup(f => f.OnException(It.IsAny())) .Callback((c) => { throw expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, exceptionFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert Assert.Same(expected, context.Exception); Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_HandlesException_FromResultFilter() { // Arrange var expected = new DataMisalignedException(); ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); context.ExceptionHandled = true; }) .Verifiable(); var resultFilter = new Mock(MockBehavior.Loose); resultFilter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback((c) => { throw expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, resultFilter.Object }); // Act await invoker.InvokeAsync(); // Assert Assert.Same(expected, context.Exception); Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_HandleException_BySettingNull() { // Arrange ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); Assert.Same(_actionException, context.Exception); Assert.Same(_actionException, context.ExceptionDispatchInfo.SourceException); context.Exception = null; }) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object }, actionThrows: true); // Act await invoker.InvokeAsync(); // Assert resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_ThrowsUnhandledException() { // Arrange var expected = new DataMisalignedException(); ResourceExecutedContext context = null; var resourceFilter1 = new Mock(MockBehavior.Strict); resourceFilter1 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var resourceFilter2 = new Mock(MockBehavior.Strict); resourceFilter2 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { throw expected; }) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true); // Act var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); // Assert Assert.Same(expected, exception); Assert.Same(expected, context.Exception); Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); } [Fact] public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuting_ThrowsUnhandledException() { // Arrange var expected = new DataMisalignedException(); ResourceExecutedContext context = null; var resourceFilter1 = new Mock(MockBehavior.Strict); resourceFilter1 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var resourceFilter2 = new Mock(MockBehavior.Strict); resourceFilter2 .Setup(f => f.OnResourceExecuting(It.IsAny())) .Callback((c) => { throw expected; }) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true); // Act var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); // Assert Assert.Same(expected, exception); Assert.Same(expected, context.Exception); Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); } [Fact] public async Task InvokeAction_InvokesResourceFilter_OnResourceExecuted_ThrowsUnhandledException() { // Arrange var expected = new DataMisalignedException(); ResourceExecutedContext context = null; var resourceFilter1 = new Mock(MockBehavior.Strict); resourceFilter1 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var resourceFilter2 = new Mock(MockBehavior.Loose); resourceFilter2 .Setup(f => f.OnResourceExecuted(It.IsAny())) .Callback((c) => { throw expected; }) .Verifiable(); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter1.Object, resourceFilter2.Object }, actionThrows: true); // Act var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); // Assert Assert.Same(expected, exception); Assert.Same(expected, context.Exception); Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit() { // Arrange var expected = new Mock(MockBehavior.Strict); expected .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); ResourceExecutedContext context = null; var resourceFilter1 = new Mock(MockBehavior.Strict); resourceFilter1 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var resourceFilter2 = new Mock(MockBehavior.Strict); resourceFilter2 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { c.Result = expected.Object; return Task.FromResult(true); }) .Verifiable(); var resourceFilter3 = new Mock(MockBehavior.Strict); var exceptionFilter = new Mock(MockBehavior.Strict); var actionFilter = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 resourceFilter2.Object, // This filter will short circuit resourceFilter3.Object, // This shouldn't run - it will throw if it does exceptionFilter.Object, // This shouldn't run - it will throw if it does actionFilter.Object, // This shouldn't run - it will throw if it does resultFilter.Object // This shouldn't run - it will throw if it does }, // The action won't run actionThrows: true); // Act await invoker.InvokeAsync(); // Assert expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); Assert.Same(expected.Object, context.Result); Assert.True(context.Canceled); Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_ShortCircuit_WithoutResult() { // Arrange ResourceExecutedContext context = null; var resourceFilter1 = new Mock(MockBehavior.Strict); resourceFilter1 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }) .Verifiable(); var resourceFilter2 = new Mock(MockBehavior.Strict); resourceFilter2 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { return Task.FromResult(true); }) .Verifiable(); var resourceFilter3 = new Mock(MockBehavior.Strict); var exceptionFilter = new Mock(MockBehavior.Strict); var actionFilter = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 resourceFilter2.Object, // This filter will short circuit resourceFilter3.Object, // This shouldn't run - it will throw if it does exceptionFilter.Object, // This shouldn't run - it will throw if it does actionFilter.Object, // This shouldn't run - it will throw if it does resultFilter.Object // This shouldn't run - it will throw if it does }, // The action won't run actionThrows: true); // Act await invoker.InvokeAsync(); // Assert Assert.Null(context.Result); Assert.True(context.Canceled); Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] public async Task InvokeAction_InvokesResourceFilter_ShortCircuit() { // Arrange var expected = new Mock(MockBehavior.Strict); expected .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); ResourceExecutedContext context = null; var resourceFilter1 = new Mock(MockBehavior.Strict); resourceFilter1 .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { context = await next(); }); var resourceFilter2 = new Mock(MockBehavior.Strict); resourceFilter2 .Setup(f => f.OnResourceExecuting(It.IsAny())) .Callback((c) => { c.Result = expected.Object; }); var resourceFilter3 = new Mock(MockBehavior.Strict); var actionFilter = new Mock(MockBehavior.Strict); var resultFilter = new Mock(MockBehavior.Strict); var invoker = CreateInvoker( new IFilterMetadata[] { resourceFilter1.Object, // This filter should see the result retured from resourceFilter2 resourceFilter2.Object, resourceFilter3.Object, // This shouldn't run - it will throw if it does actionFilter.Object, // This shouldn't run - it will throw if it does resultFilter.Object // This shouldn't run - it will throw if it does }, // The action won't run actionThrows: true); // Act await invoker.InvokeAsync(); // Assert expected.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); Assert.Same(expected.Object, context.Result); Assert.True(context.Canceled); Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] public async Task InvokeAction_InvokesAsyncResourceFilter_InvalidShortCircuit() { // Arrange var message = "If an IAsyncResourceFilter provides a result value by setting the Result property of " + "ResourceExecutingContext to a non-null value, then it cannot call the next filter by invoking " + "ResourceExecutionDelegate."; ResourceExecutedContext context = null; var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { // This is not valid. c.Result = Mock.Of(); context = await next(); }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, }); // Act var exception = await Assert.ThrowsAsync(invoker.InvokeAsync); // Assert Assert.Equal(message, exception.Message); } [Fact] public async Task InvokeAction_AuthorizationFilter_ChallengePreventsResourceFiltersFromRunning() { // Arrange var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { await next(); }) .Verifiable(); var authorizationFilter = new Mock(MockBehavior.Strict); authorizationFilter .Setup(f => f.OnAuthorization(It.IsAny())) .Callback((c) => { c.Result = _result; }); var invoker = CreateInvoker(new IFilterMetadata[] { authorizationFilter.Object, resourceFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Never()); Assert.False(invoker.ControllerFactory.CreateCalled); } [Fact] public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInControllerContext() { // Arrange var valueProviderFactory2 = Mock.Of(); var resourceFilter = new Mock(); resourceFilter .Setup(f => f.OnResourceExecuting(It.IsAny())) .Callback((resourceExecutingContext) => { resourceExecutingContext.ValueProviderFactories.Add(valueProviderFactory2); }); var valueProviderFactory1 = Mock.Of(); var valueProviderFactories = new List(); valueProviderFactories.Add(valueProviderFactory1); var invoker = CreateInvoker( new IFilterMetadata[] { resourceFilter.Object }, valueProviderFactories: valueProviderFactories); // Act await invoker.InvokeAsync(); // Assert var controllerContext = invoker.ControllerFactory.ControllerContext; Assert.NotNull(controllerContext); Assert.Equal(2, controllerContext.ValueProviderFactories.Count); Assert.Same(valueProviderFactory1, controllerContext.ValueProviderFactories[0]); Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[1]); } [Fact] public async Task DeletingValueProviderFactory_AtResourceFilter_IsNotAvailableInControllerContext() { // Arrange var resourceFilter = new Mock(); resourceFilter .Setup(f => f.OnResourceExecuting(It.IsAny())) .Callback((resourceExecutingContext) => { resourceExecutingContext.ValueProviderFactories.RemoveAt(0); }); var valueProviderFactory1 = Mock.Of(); var valueProviderFactory2 = Mock.Of(); var valueProviderFactories = new List(); valueProviderFactories.Add(valueProviderFactory1); valueProviderFactories.Add(valueProviderFactory2); var invoker = CreateInvoker( new IFilterMetadata[] { resourceFilter.Object }, valueProviderFactories: valueProviderFactories); // Act await invoker.InvokeAsync(); // Assert var controllerContext = invoker.ControllerFactory.ControllerContext; Assert.NotNull(controllerContext); Assert.Equal(1, controllerContext.ValueProviderFactories.Count); Assert.Same(valueProviderFactory2, controllerContext.ValueProviderFactories[0]); } [Fact] public async Task MaxAllowedErrorsIsSet_BeforeCallingAuthorizationFilter() { // Arrange var expected = 147; var filter = new MockAuthorizationFilter(expected); var invoker = CreateInvoker( filter, actionThrows: false, maxAllowedErrorsInModelState: expected); // Act & Assert // The authorization filter asserts if MaxAllowedErrors was set to the right value. await invoker.InvokeAsync(); } [Fact] public async Task InvokeAction_AsyncAction_TaskReturnType() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskAction), actionParameters); // Act await invoker.InvokeAsync(); // Assert Assert.IsType(result); } [Fact] public async Task InvokeAction_AsyncAction_TaskOfValueReturnType() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskValueTypeAction), actionParameters); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal(inputParam1, contentResult.Value); } [Fact] public async Task InvokeAction_AsyncAction_WithAsyncKeywordThrows() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskActionWithException), actionParameters); // Act and Assert await Assert.ThrowsAsync( () => invoker.InvokeAsync()); } [Fact] public async Task InvokeAction_AsyncAction_WithoutAsyncThrows() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskActionWithExceptionWithoutAsync), actionParameters); // Act and Assert await Assert.ThrowsAsync( () => invoker.InvokeAsync()); } public async Task InvokeAction_AsyncAction_WithExceptionsAfterAwait() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.TaskActionThrowAfterAwait), actionParameters); var expectedException = "Argument Exception"; // Act and Assert var ex = await Assert.ThrowsAsync( () => invoker.InvokeAsync()); Assert.Equal(expectedException, ex.Message); } [Fact] public async Task InvokeAction_SyncAction() { // Arrange var inputString = "hello"; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.Echo), new Dictionary() { { "input", inputString } }); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal(inputString, contentResult.Value); } [Fact] public async Task InvokeAction_SyncAction_WithException() { // Arrange var inputString = "hello"; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.EchoWithException), new Dictionary() { { "input", inputString } }); // Act & Assert await Assert.ThrowsAsync( () => invoker.InvokeAsync()); } [Fact] public async Task InvokeAction_SyncMethod_WithArgumentDictionary_DefaultValueAttributeUsed() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.EchoWithDefaultValue), new Dictionary()); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal("hello", contentResult.Value); } [Fact] public async Task InvokeAction_SyncMethod_WithArgumentArray_DefaultValueAttributeIgnored() { // Arrange var inputString = "test"; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.EchoWithDefaultValue), new Dictionary() { { "input", inputString } }); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal(inputString, contentResult.Value); } [Fact] public async Task InvokeAction_SyncMethod_WithArgumentDictionary_DefaultParameterValueUsed() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.EchoWithDefaultValueAndAttribute), new Dictionary()); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal("world", contentResult.Value); } [Fact] public async Task InvokeAction_SyncMethod_WithArgumentDictionary_AnyValue_HasPrecedenceOverDefaults() { // Arrange var inputString = "test"; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.EchoWithDefaultValueAndAttribute), new Dictionary() { { "input", inputString } }); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal(inputString, contentResult.Value); } [Fact] public async Task InvokeAction_AsyncAction_WithCustomTaskReturnTypeThrows() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.TaskActionWithCustomTaskReturnType), actionParameters); var expectedException = string.Format( CultureInfo.CurrentCulture, "The method 'TaskActionWithCustomTaskReturnType' on type '{0}' returned a Task instance even though it is not an asynchronous method.", typeof(TestController)); // Act & Assert var ex = await Assert.ThrowsAsync( () => invoker.InvokeAsync()); Assert.Equal(expectedException, ex.Message); } [Fact] public async Task InvokeAction_AsyncAction_WithCustomTaskOfTReturnTypeThrows() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.TaskActionWithCustomTaskOfTReturnType), actionParameters); var expectedException = string.Format( CultureInfo.CurrentCulture, "The method 'TaskActionWithCustomTaskOfTReturnType' on type '{0}' returned a Task instance even though it is not an asynchronous method.", typeof(TestController)); // Act & Assert var ex = await Assert.ThrowsAsync( () => invoker.InvokeAsync()); Assert.Equal(expectedException, ex.Message); } [Fact] public async Task InvokeAction_AsyncAction_ReturningUnwrappedTask() { // Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", inputParam1 }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(new[] { filter.Object }, nameof(TestController.UnwrappedTask), actionParameters); // Act await invoker.InvokeAsync(); // Assert Assert.IsType(result); } [Fact] public async Task InvokeAction_AsyncActionWithTaskOfObjectReturnType_AndReturningTaskOfActionResult() { // Arrange var actionParameters = new Dictionary { ["value"] = 3 }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.AsyncActionMethodReturningActionResultWithTaskOfObjectAsReturnType), actionParameters); // Act await invoker.InvokeAsync(); // Assert var testResult = Assert.IsType(result); Assert.Equal(3, testResult.Value); } [Fact] public async Task InvokeAction_ActionWithObjectReturnType_AndReturningActionResult() { // Arrange var actionParameters = new Dictionary { ["value"] = 3 }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.ActionMethodReturningActionResultWithObjectAsReturnType), actionParameters); // Act await invoker.InvokeAsync(); // Assert var testResult = Assert.IsType(result); Assert.Equal(3, testResult.Value); } [Fact] public async Task InvokeAction_AsyncMethod_ParametersInRandomOrder() { //Arrange var inputParam1 = 1; var inputParam2 = "Second Parameter"; // Note that the order of parameters is reversed var actionParameters = new Dictionary { { "s", inputParam2 }, { "i", inputParam1 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.TaskValueTypeAction), actionParameters); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal(inputParam1, contentResult.Value); } [Theory] [InlineData(nameof(TestController.AsynActionMethodWithTestActionResult))] [InlineData(nameof(TestController.ActionMethodWithTestActionResult))] public async Task InvokeAction_ReturnTypeAsIActionResult_ReturnsExpected(string methodName) { //Arrange var inputParam = 1; var actionParameters = new Dictionary { { "value", inputParam } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, methodName, actionParameters); // Act await invoker.InvokeAsync(); // Assert var contentResult = Assert.IsType(result); Assert.Equal(inputParam, contentResult.Value); } [Fact] public async Task InvokeAction_AsyncMethod_InvalidParameterValueThrows() { //Arrange var inputParam2 = "Second Parameter"; var actionParameters = new Dictionary { { "i", "Some Invalid Value" }, { "s", inputParam2 } }; IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, nameof(TestController.TaskValueTypeAction), actionParameters); // Act & Assert await Assert.ThrowsAsync( () => invoker.InvokeAsync()); } [Theory] [InlineData(nameof(TestController.ActionMethodWithNullActionResult), typeof(IActionResult))] [InlineData(nameof(TestController.TestActionMethodWithNullActionResult), typeof(TestActionResult))] [InlineData(nameof(TestController.AsyncActionMethodWithNullActionResult), typeof(IActionResult))] [InlineData(nameof(TestController.AsyncActionMethodWithNullTestActionResult), typeof(TestActionResult))] [ReplaceCulture] public async Task InvokeAction_WithNullActionResultThrows(string methodName, Type resultType) { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); filter.Setup(f => f.OnActionExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnActionExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker( new[] { filter.Object }, methodName, new Dictionary()); // Act & Assert await ExceptionAssert.ThrowsAsync( () => invoker.InvokeAsync(), $"Cannot return null from an action method with a return type of '{resultType}'."); } [Fact] public async Task Invoke_UsesDefaultValuesIfNotBound() { // Arrange var actionDescriptor = new ControllerActionDescriptor { ControllerTypeInfo = typeof(TestController).GetTypeInfo(), BoundProperties = new List(), MethodInfo = typeof(TestController).GetTypeInfo() .DeclaredMethods .First(m => m.Name.Equals("ActionMethodWithDefaultValues", StringComparison.Ordinal)), Parameters = new List { new ParameterDescriptor { Name = "value", ParameterType = typeof(int), BindingInfo = new BindingInfo(), } }, FilterDescriptors = new List() }; var context = new Mock(); context.SetupGet(c => c.Items) .Returns(new Dictionary()); context.Setup(c => c.RequestServices.GetService(typeof(ILoggerFactory))) .Returns(new NullLoggerFactory()); var actionContext = new ActionContext(context.Object, new RouteData(), actionDescriptor); var controllerFactory = new Mock(); controllerFactory.Setup(c => c.CreateController(It.IsAny())) .Returns(new TestController()); var metadataProvider = new EmptyModelMetadataProvider(); var parameterBinder = new ParameterBinder( metadataProvider, TestModelBinderFactory.CreateDefault(metadataProvider), new DefaultObjectValidator(metadataProvider, new IModelValidatorProvider[0])); var controllerContext = new ControllerContext(actionContext) { ValueProviderFactories = new IValueProviderFactory[0] }; controllerContext.ModelState.MaxAllowedErrors = 200; var invoker = new ControllerActionInvoker( controllerFactory.Object, parameterBinder, metadataProvider, new NullLoggerFactory().CreateLogger(), new DiagnosticListener("Microsoft.AspNetCore"), controllerContext, new IFilterMetadata[0], ObjectMethodExecutor.Create( actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo)); // Act await invoker.InvokeAsync(); // Assert Assert.Equal(5, context.Object.Items["Result"]); } [Fact] public async Task Invoke_Success_LogsCorrectValues() { // Arrange var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var logger = loggerFactory.CreateLogger(); var displayName = "A.B.C"; var mockActionDescriptor = new Mock(); mockActionDescriptor .SetupGet(ad => ad.DisplayName) .Returns(displayName); var actionDescriptor = mockActionDescriptor.Object; actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( nameof(ControllerActionInvokerTest.ActionMethod)); actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); actionDescriptor.FilterDescriptors = new List(); actionDescriptor.Parameters = new List(); actionDescriptor.BoundProperties = new List(); var filter = Mock.Of(); var invoker = CreateInvoker( new[] { filter }, actionDescriptor, parameterBinder: null, controller: null, logger: logger); // Act await invoker.InvokeAsync(); // Assert Assert.Single(sink.Scopes); Assert.Equal(displayName, sink.Scopes[0].Scope?.ToString()); Assert.Equal(4, sink.Writes.Count); Assert.Equal($"Executing action {displayName}", sink.Writes[0].State?.ToString()); Assert.Equal($"Executing action method {displayName} with arguments ((null)) - ModelState is Valid", sink.Writes[1].State?.ToString()); Assert.Equal($"Executed action method {displayName}, returned result Microsoft.AspNetCore.Mvc.ContentResult.", sink.Writes[2].State?.ToString()); // This message has the execution time embedded, which we don't want to verify. Assert.StartsWith($"Executed action {displayName} ", sink.Writes[3].State?.ToString()); } [Fact] public async Task Invoke_WritesDiagnostic_ActionSelected() { // Arrange var actionDescriptor = new ControllerActionDescriptor() { FilterDescriptors = new List(), Parameters = new List(), BoundProperties = new List(), }; actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( nameof(ControllerActionInvokerTest.ActionMethod)); actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); var listener = new TestDiagnosticListener(); var routeData = new RouteData(); routeData.Values.Add("tag", "value"); var filter = Mock.Of(); var invoker = CreateInvoker( new[] { filter }, actionDescriptor, parameterBinder: null, controller: null, diagnosticListener: listener, routeData: routeData); // Act await invoker.InvokeAsync(); // Assert Assert.NotNull(listener.BeforeAction?.ActionDescriptor); Assert.NotNull(listener.BeforeAction?.HttpContext); var routeValues = listener.BeforeAction?.RouteData?.Values; Assert.NotNull(routeValues); Assert.Equal(1, routeValues.Count); Assert.Contains(routeValues, kvp => kvp.Key == "tag" && string.Equals(kvp.Value, "value")); } [Fact] public async Task Invoke_WritesDiagnostic_ActionInvoked() { // Arrange var actionDescriptor = new ControllerActionDescriptor() { FilterDescriptors = new List(), Parameters = new List(), BoundProperties = new List(), }; actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( nameof(ControllerActionInvokerTest.ActionMethod)); actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); var listener = new TestDiagnosticListener(); var filter = Mock.Of(); var invoker = CreateInvoker( new[] { filter }, actionDescriptor, parameterBinder: null, controller: null, diagnosticListener: listener); // Act await invoker.InvokeAsync(); // Assert Assert.NotNull(listener.AfterAction?.ActionDescriptor); Assert.NotNull(listener.AfterAction?.HttpContext); } [Fact] public async Task InvokeAction_ExceptionBubbling_AsyncActionFilter_To_ResourceFilter() { // Arrange var resourceFilter = new Mock(MockBehavior.Strict); resourceFilter .Setup(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { var context = await next(); Assert.Same(_actionException, context.Exception); context.ExceptionHandled = true; }); var actionFilter1 = new Mock(MockBehavior.Strict); actionFilter1 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { await next(); }); var actionFilter2 = new Mock(MockBehavior.Strict); actionFilter2 .Setup(f => f.OnActionExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { await next(); }); var invoker = CreateInvoker( new IFilterMetadata[] { resourceFilter.Object, actionFilter1.Object, actionFilter2.Object, }, // The action won't run actionThrows: true); // Act & Assert await invoker.InvokeAsync(); } private TestControllerActionInvoker CreateInvoker( IFilterMetadata filter, bool actionThrows = false, int maxAllowedErrorsInModelState = 200, List valueProviderFactories = null) { return CreateInvoker(new[] { filter }, actionThrows, maxAllowedErrorsInModelState, valueProviderFactories); } private TestControllerActionInvoker CreateInvoker( IFilterMetadata[] filters, bool actionThrows = false, int maxAllowedErrorsInModelState = 200, List valueProviderFactories = null) { var actionDescriptor = new ControllerActionDescriptor() { FilterDescriptors = new List(), Parameters = new List(), BoundProperties = new List(), }; if (actionThrows) { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( nameof(ControllerActionInvokerTest.ThrowingActionMethod)); } else { actionDescriptor.MethodInfo = typeof(ControllerActionInvokerTest).GetMethod( nameof(ControllerActionInvokerTest.ActionMethod)); } actionDescriptor.ControllerTypeInfo = typeof(ControllerActionInvokerTest).GetTypeInfo(); return CreateInvoker( filters, actionDescriptor, null, null, maxAllowedErrorsInModelState, valueProviderFactories); } private TestControllerActionInvoker CreateInvoker( IFilterMetadata[] filters, string methodName, IDictionary arguments, int maxAllowedErrorsInModelState = 200) { var actionDescriptor = new ControllerActionDescriptor() { FilterDescriptors = new List(), Parameters = new List(), BoundProperties = new List(), MethodInfo = typeof(TestController).GetMethod(methodName), ControllerTypeInfo = typeof(TestController).GetTypeInfo(), }; foreach (var argument in arguments) { actionDescriptor.Parameters.Add(new ParameterDescriptor { Name = argument.Key, ParameterType = argument.Value.GetType(), }); } var parameterBinder = new TestParameterBinder(arguments); return CreateInvoker(filters, actionDescriptor, _controller, parameterBinder, maxAllowedErrorsInModelState); } private TestControllerActionInvoker CreateInvoker( IFilterMetadata[] filters, ControllerActionDescriptor actionDescriptor, object controller, ParameterBinder parameterBinder = null, int maxAllowedErrorsInModelState = 200, List valueProviderFactories = null, RouteData routeData = null, ILogger logger = null, object diagnosticListener = null) { var httpContext = new DefaultHttpContext(); var options = new MvcOptions(); var mvcOptionsAccessor = new TestOptionsManager(options); var services = new ServiceCollection(); services.AddSingleton(NullLoggerFactory.Instance); services.AddSingleton>(mvcOptionsAccessor); services.AddSingleton(new ObjectResultExecutor( mvcOptionsAccessor, new TestHttpResponseStreamWriterFactory(), NullLoggerFactory.Instance)); services.AddSingleton(new ContentResultExecutor( NullLogger.Instance, new MemoryPoolHttpResponseStreamWriterFactory(ArrayPool.Shared, ArrayPool.Shared))); httpContext.Response.Body = new MemoryStream(); httpContext.RequestServices = services.BuildServiceProvider(); var formatter = new Mock(); formatter .Setup(f => f.CanWriteResult(It.IsAny())) .Returns(true); formatter .Setup(f => f.WriteAsync(It.IsAny())) .Returns(async c => { await c.HttpContext.Response.WriteAsync(c.Object.ToString()); }); options.OutputFormatters.Add(formatter.Object); if (routeData == null) { routeData = new RouteData(); } var actionContext = new ActionContext( httpContext: httpContext, routeData: routeData, actionDescriptor: actionDescriptor); if (parameterBinder == null) { parameterBinder = new TestParameterBinder(new Dictionary()); } if (valueProviderFactories == null) { valueProviderFactories = new List(); } if (logger == null) { logger = new NullLoggerFactory().CreateLogger(); } var diagnosticSource = new DiagnosticListener("Microsoft.AspNetCore"); if (diagnosticListener != null) { diagnosticSource.SubscribeWithAdapter(diagnosticListener); } var invoker = new TestControllerActionInvoker( filters, new MockControllerFactory(controller ?? this), parameterBinder, TestModelMetadataProvider.CreateDefaultProvider(), logger, diagnosticSource, actionContext, valueProviderFactories.AsReadOnly(), maxAllowedErrorsInModelState); return invoker; } public IActionResult ActionMethod() { return _result; } public ObjectResult ThrowingActionMethod() { throw _actionException; } public class Person { public string Name { get; set; } public int Age { get; set; } } private sealed class TestController { public IActionResult ActionMethodWithDefaultValues(int value = 5) { return new TestActionResult { Value = value }; } public TestActionResult ActionMethodWithTestActionResult(int value) { return new TestActionResult { Value = value }; } public async Task AsynActionMethodWithTestActionResult(int value) { return await Task.FromResult(new TestActionResult { Value = value }); } public IActionResult ActionMethodWithNullActionResult() { return null; } public object ActionMethodReturningActionResultWithObjectAsReturnType(int value = 5) { return new TestActionResult { Value = value }; } public async Task AsyncActionMethodReturningActionResultWithTaskOfObjectAsReturnType(int value = 5) { return await Task.FromResult(new TestActionResult { Value = value }); } public TestActionResult TestActionMethodWithNullActionResult() { return null; } public async Task AsyncActionMethodWithNullActionResult() { return await Task.FromResult(null); } public async Task AsyncActionMethodWithNullTestActionResult() { return await Task.FromResult(null); } #pragma warning disable 1998 public async Task TaskAction(int i, string s) { return; } #pragma warning restore 1998 #pragma warning disable 1998 public async Task TaskValueTypeAction(int i, string s) { return i; } #pragma warning restore 1998 #pragma warning disable 1998 public async Task> TaskOfTaskAction(int i, string s) { return TaskValueTypeAction(i, s); } #pragma warning restore 1998 public Task TaskValueTypeActionWithoutAsync(int i, string s) { return TaskValueTypeAction(i, s); } #pragma warning disable 1998 public async Task TaskActionWithException(int i, string s) { throw new NotImplementedException("Not Implemented Exception"); } #pragma warning restore 1998 public Task TaskActionWithExceptionWithoutAsync(int i, string s) { throw new NotImplementedException("Not Implemented Exception"); } public async Task TaskActionThrowAfterAwait(int i, string s) { await Task.Delay(500); throw new ArgumentException("Argument Exception"); } public TaskDerivedType TaskActionWithCustomTaskReturnType(int i, string s) { return new TaskDerivedType(); } public TaskOfTDerivedType TaskActionWithCustomTaskOfTReturnType(int i, string s) { return new TaskOfTDerivedType(1); } /// /// Returns a instead of a . /// public Task UnwrappedTask(int i, string s) { return Task.Factory.StartNew(async () => await Task.Factory.StartNew(() => i)); } public string Echo(string input) { return input; } public string EchoWithException(string input) { throw new NotImplementedException(); } public string EchoWithDefaultValue([DefaultValue("hello")] string input) { return input; } public string EchoWithDefaultValueAndAttribute([DefaultValue("hello")] string input = "world") { return input; } public class TaskDerivedType : Task { public TaskDerivedType() : base(() => Console.WriteLine("In The Constructor")) { } } public class TaskOfTDerivedType : Task { public TaskOfTDerivedType(T input) : base(() => input) { } } } private sealed class TestActionResult : IActionResult { public int Value { get; set; } public Task ExecuteResultAsync(ActionContext context) { context.HttpContext.Items["Result"] = Value; return Task.FromResult(0); } } private class MockControllerFactory : IControllerFactory { private object _controller; public MockControllerFactory(object controller) { _controller = controller; } public bool CreateCalled { get; private set; } public bool ReleaseCalled { get; private set; } public ControllerContext ControllerContext { get; private set; } public object CreateController(ControllerContext context) { ControllerContext = context; CreateCalled = true; return _controller; } public void ReleaseController(ControllerContext context, object controller) { Assert.NotNull(controller); Assert.Same(_controller, controller); ReleaseCalled = true; } public void Verify() { if (CreateCalled && !ReleaseCalled) { Assert.False(true, "ReleaseController should have been called."); } } } private class TestControllerActionInvoker : ControllerActionInvoker { public TestControllerActionInvoker( IFilterMetadata[] filters, MockControllerFactory controllerFactory, ParameterBinder parameterBinder, IModelMetadataProvider modelMetadataProvider, ILogger logger, DiagnosticSource diagnosticSource, ActionContext actionContext, IReadOnlyList valueProviderFactories, int maxAllowedErrorsInModelState) : base( controllerFactory, parameterBinder, modelMetadataProvider, logger, diagnosticSource, CreatControllerContext(actionContext, valueProviderFactories, maxAllowedErrorsInModelState), filters, CreateExecutor((ControllerActionDescriptor)actionContext.ActionDescriptor)) { ControllerFactory = controllerFactory; } public MockControllerFactory ControllerFactory { get; } public async override Task InvokeAsync() { await base.InvokeAsync(); // Make sure that the controller was disposed in every test that creates ones. ControllerFactory.Verify(); } private static ObjectMethodExecutor CreateExecutor(ControllerActionDescriptor actionDescriptor) { return ObjectMethodExecutor.Create(actionDescriptor.MethodInfo, actionDescriptor.ControllerTypeInfo); } private static ControllerContext CreatControllerContext( ActionContext actionContext, IReadOnlyList valueProviderFactories, int maxAllowedErrorsInModelState) { var controllerContext = new ControllerContext(actionContext) { ValueProviderFactories = valueProviderFactories.ToList() }; controllerContext.ModelState.MaxAllowedErrors = maxAllowedErrorsInModelState; return controllerContext; } } private class MockAuthorizationFilter : IAuthorizationFilter { int _expectedMaxAllowedErrors; public MockAuthorizationFilter(int maxAllowedErrors) { _expectedMaxAllowedErrors = maxAllowedErrors; } public void OnAuthorization(AuthorizationFilterContext context) { Assert.NotNull(context.ModelState.MaxAllowedErrors); Assert.Equal(_expectedMaxAllowedErrors, context.ModelState.MaxAllowedErrors); } } private class TestParameterBinder : ParameterBinder { private readonly IDictionary _actionParameters; public TestParameterBinder(IDictionary actionParameters) : base( new EmptyModelMetadataProvider(), TestModelBinderFactory.CreateDefault(), Mock.Of()) { _actionParameters = actionParameters; } public override Task BindModelAsync( ActionContext actionContext, IValueProvider valueProvider, ParameterDescriptor parameter, object value) { if (_actionParameters.TryGetValue(parameter.Name, out var result)) { return Task.FromResult(ModelBindingResult.Success(result)); } return Task.FromResult(ModelBindingResult.Failed()); } } } }