// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Reflection; using System.Text.Encodings.Web; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Abstractions; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.ModelBinding.Validation; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.Rendering; using Microsoft.AspNetCore.Mvc.ViewEngines; using Microsoft.AspNetCore.Mvc.ViewFeatures; using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; 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 Moq; using Xunit; namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { public class PageActionInvokerTest : CommonResourceInvokerTest { #region Diagnostics [Fact] public async Task Invoke_Success_LogsCorrectValues() { // Arrange var sink = new TestSink(); var loggerFactory = new TestLoggerFactory(sink, enabled: true); var logger = loggerFactory.CreateLogger(); var actionDescriptor = CreateDescriptorForSimplePage(); var displayName = "/A/B/C"; actionDescriptor.DisplayName = displayName; var invoker = CreateInvoker(filters: null, actionDescriptor: actionDescriptor, 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 handler method OnGetHandler1 with arguments ((null)) - ModelState is Valid", sink.Writes[1].State?.ToString()); Assert.Equal($"Executed handler method OnGetHandler1, returned result {typeof(PageResult).FullName}.", 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 = CreateDescriptorForSimplePage(); var displayName = actionDescriptor.DisplayName; var routeData = new RouteData(); routeData.Values.Add("tag", "value"); var listener = new TestDiagnosticListener(); var invoker = CreateInvoker(filters: null, actionDescriptor: actionDescriptor, listener: 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 = CreateDescriptorForSimplePage(); var displayName = actionDescriptor.DisplayName; var routeData = new RouteData(); routeData.Values.Add("tag", "value"); var listener = new TestDiagnosticListener(); var invoker = CreateInvoker(filters: null, actionDescriptor: actionDescriptor, listener: listener, routeData: routeData); // Act await invoker.InvokeAsync(); // Assert Assert.NotNull(listener.AfterAction?.ActionDescriptor); Assert.NotNull(listener.AfterAction?.HttpContext); } #endregion #region Page Context [Fact] public async Task AddingValueProviderFactory_AtResourceFilter_IsAvailableInPageContext() { // 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 pageContext = Assert.IsType(invoker).PageContext; Assert.NotNull(pageContext); Assert.Equal(2, pageContext.ValueProviderFactories.Count); Assert.Same(valueProviderFactory1, pageContext.ValueProviderFactories[0]); Assert.Same(valueProviderFactory2, pageContext.ValueProviderFactories[1]); } [Fact] public async Task DeletingValueProviderFactory_AtResourceFilter_IsNotAvailableInPageContext() { // 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 pageContext = Assert.IsType(invoker).PageContext; Assert.NotNull(pageContext); Assert.Equal(1, pageContext.ValueProviderFactories.Count); Assert.Same(valueProviderFactory2, pageContext.ValueProviderFactories[0]); } #endregion #region Page vs PageModel [Fact] public async Task InvokeAction_WithSimplePage_FlowsRightValues() { // Arrange object instance = null; IActionResult result = null; var pageFilter = new Mock(MockBehavior.Strict); AllowSelector(pageFilter); pageFilter .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback(c => { instance = c.HandlerInstance; }); pageFilter .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => { Assert.Same(instance, c.HandlerInstance); }); var resultFilter = new Mock(MockBehavior.Strict); resultFilter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => { Assert.Same(instance, c.Controller); result = c.Result; }); resultFilter .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => { Assert.Same(instance, c.Controller); Assert.Same(result, c.Result); }); var filters = new IFilterMetadata[] { pageFilter.Object, resultFilter.Object }; var invoker = CreateInvoker(filters, CreateDescriptorForSimplePage()); // Act await invoker.InvokeAsync(); // Assert var page = Assert.IsType(instance); Assert.IsType>(page.ViewContext.ViewData); Assert.Same(page, page.ViewContext.ViewData.Model); var pageResult = Assert.IsType(result); Assert.Same(page, pageResult.Page); Assert.Same(page, pageResult.Model); Assert.Same(page.ViewContext.ViewData, pageResult.ViewData); } [Fact] public async Task InvokeAction_WithSimplePageWithPocoModel_FlowsRightValues() { // Arrange object instance = null; IActionResult result = null; var pageFilter = new Mock(MockBehavior.Strict); AllowSelector(pageFilter); pageFilter .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback(c => { instance = c.HandlerInstance; }); pageFilter .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => { Assert.Same(instance, c.HandlerInstance); }); var resultFilter = new Mock(MockBehavior.Strict); resultFilter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => { Assert.Same(instance, c.Controller); result = c.Result; }); resultFilter .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => { Assert.Same(instance, c.Controller); Assert.Same(result, c.Result); }); var filters = new IFilterMetadata[] { pageFilter.Object, resultFilter.Object }; var invoker = CreateInvoker(filters, CreateDescriptorForSimplePageWithPocoModel()); // Act await invoker.InvokeAsync(); // Assert var page = Assert.IsType(instance); Assert.IsType>(page.PageContext.ViewData); Assert.Null(page.PageContext.ViewData.Model); var pageResult = Assert.IsType(result); Assert.Same(page, pageResult.Page); Assert.Null(pageResult.Model); Assert.Same(page.ViewContext.ViewData, pageResult.ViewData); } [Fact] public async Task InvokeAction_WithPageModel_FlowsRightValues() { // Arrange object instance = null; IActionResult result = null; var pageFilter = new Mock(MockBehavior.Strict); AllowSelector(pageFilter); pageFilter .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback(c => { instance = c.HandlerInstance; }); pageFilter .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => { Assert.Same(instance, c.HandlerInstance); }); var resultFilter = new Mock(MockBehavior.Strict); resultFilter .Setup(f => f.OnResultExecuting(It.IsAny())) .Callback(c => { Assert.Same(instance, c.Controller); result = c.Result; }); resultFilter .Setup(f => f.OnResultExecuted(It.IsAny())) .Callback(c => { Assert.Same(instance, c.Controller); Assert.Same(result, c.Result); }); var filters = new IFilterMetadata[] { pageFilter.Object, resultFilter.Object }; var invoker = CreateInvoker( filters, CreateDescriptorForPageModelPage(), modelFactory: context => new TestPageModel() { PageContext = context }); // Act await invoker.InvokeAsync(); // Assert var pageModel = Assert.IsType(instance); Assert.IsType>(pageModel.PageContext.ViewData); Assert.Same(pageModel, pageModel.ViewData.Model); var pageResult = Assert.IsType(result); Assert.IsType(pageResult.Page); Assert.Same(pageModel, pageResult.Model); Assert.Same(pageModel.PageContext.ViewData, pageResult.ViewData); } #endregion #region Handler Selection [Fact] public async Task InvokeAction_InvokesPageFilter_CanModifySelectedHandler() { // Arrange HandlerMethodDescriptor handler = null; var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnPageHandlerSelected(It.IsAny())) .Callback(c => { handler = c.HandlerMethod = c.ActionDescriptor.HandlerMethods[1]; }) .Verifiable(); filter1 .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback(c => Assert.Same(handler, c.HandlerMethod)) .Verifiable(); filter1 .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => Assert.Same(handler, c.HandlerMethod)) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnPageHandlerSelected(It.IsAny())) .Callback(c => Assert.Same(handler, c.HandlerMethod)) .Verifiable(); filter2 .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback(c => Assert.Same(handler, c.HandlerMethod)) .Verifiable(); filter2 .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => Assert.Same(handler, c.HandlerMethod)) .Verifiable(); var filters = new IFilterMetadata[] { filter1.Object, filter2.Object }; var invoker = CreateInvoker(filters, actionDescriptor: CreateDescriptorForSimplePage()); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnPageHandlerSelected(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerSelected(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_InvokesAsyncPageFilter_CanModifySelectedHandler() { // Arrange HandlerMethodDescriptor handler = null; var filter1 = new Mock(MockBehavior.Strict); filter1 .Setup(f => f.OnPageHandlerSelectionAsync(It.IsAny())) .Callback(c => { handler = c.HandlerMethod = c.ActionDescriptor.HandlerMethods[1]; }) .Returns(Task.CompletedTask) .Verifiable(); filter1 .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async(c, next) => { Assert.Same(handler, c.HandlerMethod); await next(); }) .Verifiable(); var filter2 = new Mock(MockBehavior.Strict); filter2 .Setup(f => f.OnPageHandlerSelectionAsync(It.IsAny())) .Callback(c => Assert.Same(handler, c.HandlerMethod)) .Returns(Task.CompletedTask) .Verifiable(); filter2 .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { Assert.Same(handler, c.HandlerMethod); await next(); }) .Verifiable(); var filters = new IFilterMetadata[] { filter1.Object, filter2.Object }; var invoker = CreateInvoker(filters, actionDescriptor: CreateDescriptorForSimplePage()); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnPageHandlerSelectionAsync(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerSelectionAsync(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } #endregion #region Page Filters [Fact] public async Task InvokeAction_InvokesPageFilter() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); AllowSelector(filter); filter.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => result = c.Result) .Verifiable(); var invoker = CreateInvoker(filter.Object, result: Result); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); Assert.Same(Result, result); } [Fact] public async Task InvokeAction_InvokesAsyncPageFilter() { // Arrange IActionResult result = null; var filter = new Mock(MockBehavior.Strict); AllowSelector(filter); filter .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (context, next) => { var resultContext = await next(); result = resultContext.Result; }) .Verifiable(); var invoker = CreateInvoker(filter.Object, result: Result); // Act await invoker.InvokeAsync(); // Assert filter.Verify( f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); Assert.Same(Result, result); } [Fact] public async Task InvokeAction_InvokesPageFilter_ShortCircuit() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); PageHandlerExecutedContext context = null; var pageFilter1 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter1); pageFilter1.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); pageFilter1 .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var pageFilter2 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter2); pageFilter2 .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback(c => c.Result = result.Object) .Verifiable(); var pageFilter3 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter3); 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[] { pageFilter1.Object, pageFilter2.Object, pageFilter3.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); pageFilter1.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); pageFilter1.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); pageFilter2.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); pageFilter2.Verify(f => f.OnPageHandlerExecuted(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_InvokesAsyncPageFilter_ShortCircuit_WithResult() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns(Task.FromResult(true)) .Verifiable(); PageHandlerExecutedContext context = null; var pageFilter1 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter1); pageFilter1.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); pageFilter1 .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var pageFilter2 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter2); pageFilter2 .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Notice we're not calling next c.Result = result.Object; return Task.FromResult(true); }) .Verifiable(); var pageFilter3 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter3); 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[] { pageFilter1.Object, pageFilter2.Object, pageFilter3.Object, resultFilter1.Object, resultFilter2.Object, }); // Act await invoker.InvokeAsync(); // Assert result.Verify(r => r.ExecuteResultAsync(It.IsAny()), Times.Once()); pageFilter1.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); pageFilter1.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); pageFilter2.Verify( f => f.OnPageHandlerExecutionAsync(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_InvokesAsyncPageFilter_ShortCircuit_WithoutResult() { // Arrange PageHandlerExecutedContext context = null; var pageFilter1 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter1); pageFilter1.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); pageFilter1 .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => context = c) .Verifiable(); var pageFilter2 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter2); pageFilter2 .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns((c, next) => { // Notice we're not calling next return Task.FromResult(true); }) .Verifiable(); var pageFilter3 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter3); 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[] { pageFilter1.Object, pageFilter2.Object, pageFilter3.Object, resultFilter.Object, }); // Act await invoker.InvokeAsync(); // Assert pageFilter1.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); pageFilter1.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); pageFilter2.Verify( f => f.OnPageHandlerExecutionAsync(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_InvokesAsyncPageFilter_ShortCircuit_WithResult_CallNext() { // Arrange var pageFilter = new Mock(MockBehavior.Strict); AllowSelector(pageFilter); pageFilter .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { c.Result = new EmptyResult(); await next(); }) .Verifiable(); var message = "If an IAsyncPageFilter provides a result value by setting the Result property of " + "PageHandlerExecutingContext to a non-null value, then it cannot call the next filter by invoking " + "PageHandlerExecutionDelegate."; var invoker = CreateInvoker(pageFilter.Object); // Act & Assert await ExceptionAssert.ThrowsAsync( () => invoker.InvokeAsync(), message); } [Fact] public async Task InvokeAction_InvokesPageFilter_WithExceptionThrownByAction() { // Arrange PageHandlerExecutedContext context = null; var filter = new Mock(MockBehavior.Strict); AllowSelector(filter); filter.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); filter .Setup(f => f.OnPageHandlerExecuted(It.IsAny())) .Callback(c => { context = c; // Handle the exception so the test doesn't throw. Assert.Same(Exception, c.Exception); Assert.False(c.ExceptionHandled); c.ExceptionHandled = true; }) .Verifiable(); var invoker = CreateInvoker(filter.Object, exception: Exception); // Act await invoker.InvokeAsync(); // Assert filter.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); filter.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); Assert.Same(Exception, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesPageFilter_WithExceptionThrownByPageFilter() { // Arrange var exception = new DataMisalignedException(); PageHandlerExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); AllowSelector(filter1); filter1.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); filter1 .Setup(f => f.OnPageHandlerExecuted(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); AllowSelector(filter2); filter2 .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback(c => { throw exception; }) .Verifiable(); var invoker = CreateInvoker(new[] { filter1.Object, filter2.Object }); // Act await invoker.InvokeAsync(); // Assert filter1.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); filter1.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerExecuted(It.IsAny()), Times.Never()); Assert.Same(exception, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesAsyncPageFilter_WithExceptionThrownByPageFilter() { // Arrange var exception = new DataMisalignedException(); PageHandlerExecutedContext context = null; var filter1 = new Mock(MockBehavior.Strict); AllowSelector(filter1); filter1 .Setup(f => f.OnPageHandlerExecutionAsync(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); AllowSelector(filter2); filter2.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); filter2 .Setup(f => f.OnPageHandlerExecuted(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.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); filter2.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); Assert.Same(exception, context.Exception); Assert.Null(context.Result); } [Fact] public async Task InvokeAction_InvokesPageFilter_HandleException() { // Arrange var result = new Mock(MockBehavior.Strict); result .Setup(r => r.ExecuteResultAsync(It.IsAny())) .Returns((context) => Task.FromResult(true)) .Verifiable(); var pageFilter = new Mock(MockBehavior.Strict); AllowSelector(pageFilter); pageFilter.Setup(f => f.OnPageHandlerExecuting(It.IsAny())).Verifiable(); pageFilter .Setup(f => f.OnPageHandlerExecuted(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[] { pageFilter.Object, resultFilter.Object }, exception: Exception); // Act await invoker.InvokeAsync(); // Assert pageFilter.Verify(f => f.OnPageHandlerExecuting(It.IsAny()), Times.Once()); pageFilter.Verify(f => f.OnPageHandlerExecuted(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_InvokesAsyncResourceFilter_WithActionResult_FromPageFilter() { // 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 pageFilter = new Mock(MockBehavior.Strict); AllowSelector(pageFilter); pageFilter .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback((c) => { c.Result = expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, pageFilter.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_FromPageFilter() { // 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 pageFilter = new Mock(MockBehavior.Strict); AllowSelector(pageFilter); pageFilter .Setup(f => f.OnPageHandlerExecuting(It.IsAny())) .Callback((c) => { throw expected; }); var invoker = CreateInvoker(new IFilterMetadata[] { resourceFilter.Object, pageFilter.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 }, exception: Exception); // Act await invoker.InvokeAsync(); // Assert Assert.Same(expected, context.Exception); Assert.Same(expected, context.ExceptionDispatchInfo.SourceException); resourceFilter.Verify( f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } [Fact] public async Task InvokeAction_ExceptionBubbling_AsyncPageFilter_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(Exception, context.Exception); context.ExceptionHandled = true; }) .Verifiable(); var pageFilter1 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter1); pageFilter1 .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { await next(); }); var pageFilter2 = new Mock(MockBehavior.Strict); AllowSelector(pageFilter2); pageFilter2 .Setup(f => f.OnPageHandlerExecutionAsync(It.IsAny(), It.IsAny())) .Returns(async (c, next) => { await next(); }); var invoker = CreateInvoker( new IFilterMetadata[] { resourceFilter.Object, pageFilter1.Object, pageFilter2.Object, }, // The action won't run exception: Exception); // Act & Assert await invoker.InvokeAsync(); resourceFilter.Verify(f => f.OnResourceExecutionAsync(It.IsAny(), It.IsAny()), Times.Once()); } #endregion protected override ResourceInvoker CreateInvoker( IFilterMetadata[] filters, Exception exception = null, IActionResult result = null, IList valueProviderFactories = null) { var actionDescriptor = new CompiledPageActionDescriptor { ViewEnginePath = "/Index.cshtml", RelativePath = "/Index.cshtml", HandlerMethods = new List(), HandlerTypeInfo = typeof(TestPage).GetTypeInfo(), ModelTypeInfo = typeof(TestPage).GetTypeInfo(), PageTypeInfo = typeof(TestPage).GetTypeInfo(), BoundProperties = new List(), }; var handlers = new List>>(); if (result != null) { handlers.Add((obj, args) => Task.FromResult(result)); actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor() { HttpMethod = "GET", Parameters = new List(), }); } else if (exception != null) { handlers.Add((obj, args) => Task.FromException(exception)); actionDescriptor.HandlerMethods.Add(new HandlerMethodDescriptor() { HttpMethod = "GET", Parameters = new List(), }); } return CreateInvoker( filters, actionDescriptor, handlers: handlers.ToArray(), valueProviderFactories: valueProviderFactories); } private PageActionInvoker CreateInvoker( IFilterMetadata[] filters, CompiledPageActionDescriptor actionDescriptor, Func modelFactory = null, ITempDataDictionaryFactory tempDataFactory = null, IList valueProviderFactories = null, Func>[] handlers = null, RouteData routeData = null, ILogger logger = null, TestDiagnosticListener listener = null) { var diagnosticListener = new DiagnosticListener("Microsoft.AspNetCore"); if (listener != null) { diagnosticListener.SubscribeWithAdapter(listener); } var httpContext = new DefaultHttpContext(); var services = new ServiceCollection(); services.AddSingleton(); httpContext.RequestServices = services.BuildServiceProvider(); var pageContext = new PageContext() { ActionDescriptor = actionDescriptor, HttpContext = httpContext, RouteData = routeData ?? new RouteData(), ValueProviderFactories = valueProviderFactories?.ToList() ?? new List(), ViewStartFactories = new List>(), }; var viewDataFactory = ViewDataDictionaryFactory.CreateFactory(actionDescriptor.ModelTypeInfo); pageContext.ViewData = viewDataFactory(new EmptyModelMetadataProvider(), pageContext.ModelState); if (tempDataFactory == null) { tempDataFactory = Mock.Of(m => m.GetTempData(It.IsAny()) == Mock.Of()); } Func pageFactory = (context, viewContext) => { var instance = (Page)Activator.CreateInstance(actionDescriptor.PageTypeInfo.AsType()); instance.PageContext = context; instance.ViewContext = viewContext; return instance; }; if (handlers == null) { handlers = new Func>[actionDescriptor.HandlerMethods.Count]; for (var i = 0; i < handlers.Length; i++) { handlers[i] = (obj, args) => Task.FromResult(new PageResult()); } } if (modelFactory == null) { modelFactory = _ => Activator.CreateInstance(actionDescriptor.ModelTypeInfo.AsType()); } var cacheEntry = new PageActionInvokerCacheEntry( actionDescriptor, viewDataFactory, pageFactory, (c, viewContext, page) => { (page as IDisposable)?.Dispose(); }, modelFactory, (c, model) => { (model as IDisposable)?.Dispose(); }, null, handlers, null, new FilterItem[0]); // Always just select the first one. var selector = new Mock(); selector .Setup(s => s.Select(It.IsAny())) .Returns(c => c.ActionDescriptor.HandlerMethods.FirstOrDefault()); var invoker = new PageActionInvoker( selector.Object, diagnosticListener ?? new DiagnosticListener("Microsoft.AspNetCore"), logger ?? NullLogger.Instance, pageContext, filters ?? Array.Empty(), cacheEntry, GetParameterBinder(), tempDataFactory, new HtmlHelperOptions()); return invoker; } private static ParameterBinder GetParameterBinder( IModelBinderFactory factory = null, IObjectModelValidator validator = null) { if (validator == null) { validator = CreateMockValidator(); } if (factory == null) { factory = TestModelBinderFactory.CreateDefault(); } return new ParameterBinder( TestModelMetadataProvider.CreateDefaultProvider(), factory, validator); } private static IObjectModelValidator CreateMockValidator() { var mockValidator = new Mock(MockBehavior.Strict); mockValidator .Setup(o => o.Validate( It.IsAny(), It.IsAny(), It.IsAny(), It.IsAny())); return mockValidator.Object; } private CompiledPageActionDescriptor CreateDescriptorForSimplePage() { return new CompiledPageActionDescriptor() { HandlerTypeInfo = typeof(TestPage).GetTypeInfo(), ModelTypeInfo = typeof(TestPage).GetTypeInfo(), PageTypeInfo = typeof(TestPage).GetTypeInfo(), BoundProperties = new List(), HandlerMethods = new HandlerMethodDescriptor[] { new HandlerMethodDescriptor() { HttpMethod = "GET", MethodInfo = typeof(TestPage).GetTypeInfo().GetMethod(nameof(TestPage.OnGetHandler1)), Parameters = new List(), }, new HandlerMethodDescriptor() { HttpMethod = "GET", MethodInfo = typeof(TestPage).GetTypeInfo().GetMethod(nameof(TestPage.OnGetHandler2)), Parameters = new List(), }, }, }; } private CompiledPageActionDescriptor CreateDescriptorForSimplePageWithPocoModel() { return new CompiledPageActionDescriptor() { HandlerTypeInfo = typeof(TestPage).GetTypeInfo(), ModelTypeInfo = typeof(PocoModel).GetTypeInfo(), PageTypeInfo = typeof(TestPage).GetTypeInfo(), BoundProperties = new List(), HandlerMethods = new HandlerMethodDescriptor[] { new HandlerMethodDescriptor() { HttpMethod = "GET", MethodInfo = typeof(TestPage).GetTypeInfo().GetMethod(nameof(TestPage.OnGetHandler1)), Parameters = new List(), }, new HandlerMethodDescriptor() { HttpMethod = "GET", MethodInfo = typeof(TestPage).GetTypeInfo().GetMethod(nameof(TestPage.OnGetHandler2)), Parameters = new List(), }, }, }; } private CompiledPageActionDescriptor CreateDescriptorForPageModelPage() { return new CompiledPageActionDescriptor() { HandlerTypeInfo = typeof(TestPageModel).GetTypeInfo(), ModelTypeInfo = typeof(TestPageModel).GetTypeInfo(), PageTypeInfo = typeof(TestPage).GetTypeInfo(), BoundProperties = new List(), HandlerMethods = new HandlerMethodDescriptor[] { new HandlerMethodDescriptor() { HttpMethod = "GET", MethodInfo = typeof(PageModel).GetTypeInfo().GetMethod(nameof(TestPageModel.OnGetHandler1)), Parameters = new List(), }, new HandlerMethodDescriptor() { HttpMethod = "GET", MethodInfo = typeof(PageModel).GetTypeInfo().GetMethod(nameof(TestPageModel.OnGetHandler2)), Parameters = new List(), }, }, }; } private void AllowSelector(Mock filter) { filter.Setup(f => f.OnPageHandlerSelected(It.IsAny())); } private void AllowSelector(Mock filter) { filter.Setup(f => f.OnPageHandlerSelectionAsync(It.IsAny())).Returns(Task.CompletedTask); } private class TestPageResultExecutor : PageResultExecutor { private readonly Func _executeAction; public TestPageResultExecutor() : this(null) { } public TestPageResultExecutor(Func executeAction) : base( Mock.Of(), Mock.Of(), Mock.Of(), Mock.Of(), new DiagnosticListener("Microsoft.AspNetCore"), HtmlEncoder.Default) { _executeAction = executeAction; } public override Task ExecuteAsync(PageContext pageContext, PageResult result) { return _executeAction?.Invoke(pageContext) ?? Task.CompletedTask; } } private class PocoModel { } private class TestPage : Page { public void OnGetHandler1() { } public void OnGetHandler2() { } public override Task ExecuteAsync() { throw new NotImplementedException(); } } private class TestPageModel : PageModel { public void OnGetHandler1() { } public void OnGetHandler2() { } } } }