Provide a way within the Page/PageModel to run code before any handler runs

Fixes #6606
This commit is contained in:
Pranav K 2017-09-21 12:33:14 -07:00
parent 236ef5d1d1
commit 197ef139d6
16 changed files with 912 additions and 32 deletions

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public int Order { get; set; } = int.MinValue;
/// <inheritdoc />
public async Task OnActionExecutionAsync(
public Task OnActionExecutionAsync(
ActionExecutingContext context,
ActionExecutionDelegate next)
{
@ -40,23 +40,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal
nameof(ActionExecutingContext)));
}
IAsyncActionFilter asyncActionFilter;
IActionFilter actionFilter;
if ((asyncActionFilter = controller as IAsyncActionFilter) != null)
if (controller is IAsyncActionFilter asyncActionFilter)
{
await asyncActionFilter.OnActionExecutionAsync(context, next);
return asyncActionFilter.OnActionExecutionAsync(context, next);
}
else if ((actionFilter = controller as IActionFilter) != null)
else if (controller is IActionFilter actionFilter)
{
actionFilter.OnActionExecuting(context);
if (context.Result == null)
{
actionFilter.OnActionExecuted(await next());
}
return ExecuteActionFilter(context, next, actionFilter);
}
else
{
await next();
return next();
}
}
private static async Task ExecuteActionFilter(
ActionExecutingContext context,
ActionExecutionDelegate next,
IActionFilter actionFilter)
{
actionFilter.OnActionExecuting(context);
if (context.Result == null)
{
actionFilter.OnActionExecuted(await next());
}
}
}

View File

@ -18,7 +18,7 @@ namespace Microsoft.AspNetCore.Mvc.Internal
public int Order { get; set; } = int.MinValue;
/// <inheritdoc />
public async Task OnResultExecutionAsync(
public Task OnResultExecutionAsync(
ResultExecutingContext context,
ResultExecutionDelegate next)
{
@ -40,23 +40,29 @@ namespace Microsoft.AspNetCore.Mvc.Internal
nameof(ResultExecutingContext)));
}
IAsyncResultFilter asyncResultFilter;
IResultFilter resultFilter;
if ((asyncResultFilter = controller as IAsyncResultFilter) != null)
if (controller is IAsyncResultFilter asyncResultFilter)
{
await asyncResultFilter.OnResultExecutionAsync(context, next);
return asyncResultFilter.OnResultExecutionAsync(context, next);
}
else if ((resultFilter = controller as IResultFilter) != null)
else if (controller is IResultFilter resultFilter)
{
resultFilter.OnResultExecuting(context);
if (!context.Cancel)
{
resultFilter.OnResultExecuted(await next());
}
return ExecuteResultFilter(context, next, resultFilter);
}
else
{
await next();
return next();
}
}
private static async Task ExecuteResultFilter(
ResultExecutingContext context,
ResultExecutionDelegate next,
IResultFilter resultFilter)
{
resultFilter.OnResultExecuting(context);
if (!context.Cancel)
{
resultFilter.OnResultExecuted(await next());
}
}
}

View File

@ -21,7 +21,7 @@ namespace Microsoft.AspNetCore.Mvc.Filters
void OnPageHandlerExecuting(PageHandlerExecutingContext context);
/// <summary>
/// Called after the handler method executes, before the action result.
/// Called after the handler method executes, before the action method is invoked.
/// </summary>
/// <param name="context">The <see cref="PageHandlerExecutedContext"/>.</param>
void OnPageHandlerExecuted(PageHandlerExecutedContext context);

View File

@ -15,6 +15,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public class DefaultPageApplicationModelProvider : IPageApplicationModelProvider
{
private const string ModelPropertyName = "Model";
private readonly PageHandlerPageFilter _pageHandlerPageFilter = new PageHandlerPageFilter();
private readonly PageHandlerResultFilter _pageHandlerResultFilter = new PageHandlerResultFilter();
/// <inheritdoc />
public int Order => -1000;
@ -142,6 +144,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
pageModel.Filters.Add(filter);
}
}
if (typeof(IAsyncPageFilter).IsAssignableFrom(pageModel.HandlerType) ||
typeof(IPageFilter).IsAssignableFrom(pageModel.HandlerType))
{
pageModel.Filters.Add(_pageHandlerPageFilter);
}
if (typeof(IAsyncResultFilter).IsAssignableFrom(pageModel.HandlerType) ||
typeof(IResultFilter).IsAssignableFrom(pageModel.HandlerType))
{
pageModel.Filters.Add(_pageHandlerResultFilter);
}
}
/// <summary>

View File

@ -0,0 +1,82 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageHandlerPageFilter : IAsyncPageFilter, IOrderedFilter
{
/// <remarks>
/// Filters on handlers run furthest from the action.
/// </remarks>t
public int Order => int.MinValue;
public Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
var handlerInstance = context.HandlerInstance;
if (handlerInstance == null)
{
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(context.HandlerInstance),
nameof(PageHandlerExecutedContext)));
}
if (handlerInstance is IAsyncPageFilter asyncPageFilter)
{
return asyncPageFilter.OnPageHandlerExecutionAsync(context, next);
}
else if (handlerInstance is IPageFilter pageFilter)
{
return ExecuteSyncFilter(context, next, pageFilter);
}
else
{
return next();
}
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (context.HandlerInstance is IAsyncPageFilter asyncPageFilter)
{
return asyncPageFilter.OnPageHandlerSelectionAsync(context);
}
else if (context.HandlerInstance is IPageFilter pageFilter)
{
pageFilter.OnPageHandlerSelected(context);
}
return Task.CompletedTask;
}
private static async Task ExecuteSyncFilter(
PageHandlerExecutingContext context,
PageHandlerExecutionDelegate next,
IPageFilter pageFilter)
{
pageFilter.OnPageHandlerExecuting(context);
if (context.Result == null)
{
pageFilter.OnPageHandlerExecuted(await next());
}
}
}
}

View File

@ -0,0 +1,63 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Mvc.Filters;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageHandlerResultFilter : IAsyncResultFilter, IOrderedFilter
{
/// <remarks>
/// Filters on handlers run furthest from the action.
/// </remarks>
public int Order => int.MinValue;
public Task OnResultExecutionAsync(ResultExecutingContext context, ResultExecutionDelegate next)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
var handler = context.Controller;
if (handler == null)
{
throw new InvalidOperationException(Resources.FormatPropertyOfTypeCannotBeNull(
nameof(context.Controller),
nameof(ResultExecutingContext)));
}
if (handler is IAsyncResultFilter asyncResultFilter)
{
return asyncResultFilter.OnResultExecutionAsync(context, next);
}
else if (handler is IResultFilter resultFilter)
{
return ExecuteSyncFilter(context, next, resultFilter);
}
else
{
return next();
}
}
private static async Task ExecuteSyncFilter(
ResultExecutingContext context,
ResultExecutionDelegate next,
IResultFilter resultFilter)
{
resultFilter.OnResultExecuting(context);
if (!context.Cancel)
{
resultFilter.OnResultExecuted(await next());
}
}
}
}

View File

@ -9,6 +9,7 @@ using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.ModelBinding.Internal;
using Microsoft.AspNetCore.Mvc.ModelBinding.Validation;
@ -21,8 +22,8 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Mvc.RazorPages
{
[PageModelAttribute]
public abstract class PageModel
[PageModel]
public abstract class PageModel : IAsyncPageFilter, IPageFilter
{
private IModelMetadataProvider _metadataProvider;
private IModelBinderFactory _modelBinderFactory;
@ -1601,5 +1602,74 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
model: model);
return ModelState.IsValid;
}
#region IAsyncPageFilter \ IPageFilter
/// <summary>
/// Called after a handler method has been selected, but before model binding occurs.
/// </summary>
/// <param name="context">The <see cref="PageHandlerSelectedContext"/>.</param>
public virtual void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
}
/// <summary>
/// Called before the handler method executes, after model binding is complete.
/// </summary>
/// <param name="context">The <see cref="PageHandlerExecutingContext"/>.</param>
public virtual void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
}
/// <summary>
/// Called after the handler method executes, before the action method is invoked.
/// </summary>
/// <param name="context">The <see cref="PageHandlerExecutedContext"/>.</param>
public virtual void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
}
/// <summary>
/// Called asynchronously after the handler method has been selected, but before model binding occurs.
/// </summary>
/// <param name="context">The <see cref="PageHandlerSelectedContext"/>.</param>
/// <returns>A <see cref="Task"/> that on completion indicates the filter has executed.</returns>
public virtual Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
OnPageHandlerSelected(context);
return Task.CompletedTask;
}
/// <summary>
/// Called asynchronously before the handler method is invoked, after model binding is complete.
/// </summary>
/// <param name="context">The <see cref="PageHandlerExecutingContext"/>.</param>
/// <param name="next">
/// The <see cref="PageHandlerExecutionDelegate"/>. Invoked to execute the next page filter or the handler method itself.
/// </param>
/// <returns>A <see cref="Task"/> that on completion indicates the filter has executed.</returns>
public virtual async Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (next == null)
{
throw new ArgumentNullException(nameof(next));
}
OnPageHandlerExecuting(context);
if (context.Result == null)
{
OnPageHandlerExecuted(await next());
}
}
#endregion
}
}
}

View File

@ -1209,6 +1209,29 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.Equal(expected, response.Trim());
}
[Fact]
public async Task PageHandlerFilterOnPageModelIsExecuted()
{
// Arrange
var expected = "Hello from OnPageHandlerExecuting";
// Act
var response = await Client.GetStringAsync("/ModelAsFilter?message=Hello+world");
// Assert
Assert.Equal(expected, response.Trim());
}
[Fact]
public async Task ResultFilterOnPageModelIsExecuted()
{
// Act
var response = await Client.GetAsync("/ModelAsFilter/TestResultFilter");
// Assert
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
}
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);

View File

@ -7,7 +7,6 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.AspNetCore.Mvc.Razor;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
@ -27,7 +26,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
autorizationProvider.OnProvidersExecuting(context);
// Assert
Assert.Empty(context.PageApplicationModel.Filters);
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f));
}
private class PageWithAuthorizeHandlers : Page
@ -59,6 +60,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<AuthorizeFilter>(f));
}
@ -94,7 +96,12 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
autorizationProvider.OnProvidersExecuting(context);
// Assert
var authorizeFilter = Assert.IsType<AuthorizeFilter>(Assert.Single(context.PageApplicationModel.Filters));
AuthorizeFilter authorizeFilter = null;
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f),
f => authorizeFilter = Assert.IsType<AuthorizeFilter>(f));
// Basic + Basic2 + Derived authorize
Assert.Equal(3, authorizeFilter.Policy.Requirements.Count);
}
@ -133,6 +140,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
// Assert
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f),
f => Assert.IsType<AllowAnonymousFilter>(f));
}

View File

@ -7,6 +7,7 @@ using System.Reflection;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ApplicationModels;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
@ -1032,5 +1033,115 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
public void OnGetUser() { }
}
[Fact]
public void PopulateFilters_AddsIFilterMetadataAttributesToModel()
{
// Arrange
var provider = new DefaultPageApplicationModelProvider();
var typeInfo = typeof(FilterModel).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true));
// Act
provider.PopulateFilters(pageModel);
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<TypeFilterAttribute>(filter));
}
[PageModel]
[Serializable]
[TypeFilter(typeof(object))]
private class FilterModel
{
}
[Fact]
public void PopulateFilters_AddsPageHandlerPageFilter_IfPageImplementsIAsyncPageFilter()
{
// Arrange
var provider = new DefaultPageApplicationModelProvider();
var typeInfo = typeof(ModelImplementingAsyncPageFilter).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true));
// Act
provider.PopulateFilters(pageModel);
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<PageHandlerPageFilter>(filter));
}
private class ModelImplementingAsyncPageFilter : IAsyncPageFilter
{
public Task OnPageHandlerExecutionAsync(PageHandlerExecutingContext context, PageHandlerExecutionDelegate next)
{
throw new NotImplementedException();
}
public Task OnPageHandlerSelectionAsync(PageHandlerSelectedContext context)
{
throw new NotImplementedException();
}
}
[Fact]
public void PopulateFilters_AddsPageHandlerPageFilter_IfPageImplementsIPageFilter()
{
// Arrange
var provider = new DefaultPageApplicationModelProvider();
var typeInfo = typeof(ModelImplementingPageFilter).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true));
// Act
provider.PopulateFilters(pageModel);
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<PageHandlerPageFilter>(filter));
}
private class ModelImplementingPageFilter : IPageFilter
{
public void OnPageHandlerExecuted(PageHandlerExecutedContext context)
{
throw new NotImplementedException();
}
public void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
throw new NotImplementedException();
}
public void OnPageHandlerSelected(PageHandlerSelectedContext context)
{
throw new NotImplementedException();
}
}
[Fact]
public void PopulateFilters_AddsPageHandlerPageFilter_ForModelDerivingFromTypeImplementingPageFilter()
{
// Arrange
var provider = new DefaultPageApplicationModelProvider();
var typeInfo = typeof(DerivedFromPageModel).GetTypeInfo();
var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true));
// Act
provider.PopulateFilters(pageModel);
// Assert
Assert.Collection(
pageModel.Filters,
filter => Assert.IsType<ServiceFilterAttribute>(filter),
filter => Assert.IsType<PageHandlerPageFilter>(filter));
}
[ServiceFilter(typeof(IServiceProvider))]
private class DerivedFromPageModel : PageModel { }
}
}

View File

@ -0,0 +1,181 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
using Microsoft.AspNetCore.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageHandlerPageFilterTest
{
[Fact]
public async Task OnPageHandlerExecutionAsync_ExecutesAsyncFilters()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new Mock<PageModel>();
var pageHandlerExecutingContext = new PageHandlerExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new Dictionary<string, object>(),
model.Object);
var pageHandlerExecutedContext = new PageHandlerExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
model.Object);
PageHandlerExecutionDelegate next = () => Task.FromResult(pageHandlerExecutedContext);
var modelAsFilter = model.As<IAsyncPageFilter>();
modelAsFilter
.Setup(f => f.OnPageHandlerExecutionAsync(pageHandlerExecutingContext, next))
.Returns(Task.CompletedTask)
.Verifiable();
var pageHandlerPageFilter = new PageHandlerPageFilter();
// Act
await pageHandlerPageFilter.OnPageHandlerExecutionAsync(pageHandlerExecutingContext, next);
// Assert
modelAsFilter.Verify();
}
[Fact]
public async Task OnPageHandlerExecutionAsync_ExecutesSyncFilters()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new Mock<object>();
var modelAsFilter = model.As<IPageFilter>();
modelAsFilter
.Setup(f => f.OnPageHandlerExecuting(It.IsAny<PageHandlerExecutingContext>()))
.Verifiable();
modelAsFilter
.Setup(f => f.OnPageHandlerExecuted(It.IsAny<PageHandlerExecutedContext>()))
.Verifiable();
var pageHandlerExecutingContext = new PageHandlerExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new Dictionary<string, object>(),
model.Object);
var pageHandlerExecutedContext = new PageHandlerExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
model.Object);
PageHandlerExecutionDelegate next = () => Task.FromResult(pageHandlerExecutedContext);
var pageHandlerPageFilter = new PageHandlerPageFilter();
// Act
await pageHandlerPageFilter.OnPageHandlerExecutionAsync(pageHandlerExecutingContext, next);
// Assert
modelAsFilter.Verify();
}
[Fact]
public async Task OnPageHandlerExecutionAsync_DoesNotInvokeHandlerExecuted_IfResultIsSet()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new Mock<object>();
var modelAsFilter = model.As<IPageFilter>();
modelAsFilter
.Setup(f => f.OnPageHandlerExecuting(It.IsAny<PageHandlerExecutingContext>()))
.Callback((PageHandlerExecutingContext context) => context.Result = new PageResult())
.Verifiable();
modelAsFilter
.Setup(f => f.OnPageHandlerExecuted(It.IsAny<PageHandlerExecutedContext>()))
.Throws(new Exception("Shouldn't be called"));
var pageHandlerExecutingContext = new PageHandlerExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new Dictionary<string, object>(),
model.Object);
var pageHandlerExecutedContext = new PageHandlerExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
model.Object);
PageHandlerExecutionDelegate next = () => Task.FromResult(pageHandlerExecutedContext);
var pageHandlerPageFilter = new PageHandlerPageFilter();
// Act
await pageHandlerPageFilter.OnPageHandlerExecutionAsync(pageHandlerExecutingContext, next);
// Assert
modelAsFilter.Verify();
}
[Fact]
public async Task OnPageHandlerExecutionAsync_InvokesNextDelegateIfHandlerDoesNotImplementFilter()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new object();
var pageHandlerExecutingContext = new PageHandlerExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new Dictionary<string, object>(),
model);
var pageHandlerExecutedContext = new PageHandlerExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
model);
var invoked = false;
PageHandlerExecutionDelegate next = () =>
{
invoked = true;
return Task.FromResult(pageHandlerExecutedContext);
};
var pageHandlerPageFilter = new PageHandlerPageFilter();
// Act
await pageHandlerPageFilter.OnPageHandlerExecutionAsync(pageHandlerExecutingContext, next);
// Assert
Assert.True(invoked);
}
}
}

View File

@ -0,0 +1,176 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Routing;
using Moq;
using Xunit;
namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
public class PageHandlerResultFilterTest
{
[Fact]
public async Task OnResultExecutionAsync_ExecutesAsyncFilters()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new Mock<PageModel>();
var modelAsFilter = model.As<IAsyncResultFilter>();
modelAsFilter
.Setup(f => f.OnResultExecutionAsync(It.IsAny<ResultExecutingContext>(), It.IsAny<ResultExecutionDelegate>()))
.Returns(Task.CompletedTask)
.Verifiable();
var resultExecutingContext = new ResultExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new PageResult(),
model.Object);
var resultExecutedContext = new ResultExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
resultExecutingContext.Result,
model.Object);
ResultExecutionDelegate next = () => Task.FromResult(resultExecutedContext);
var pageHandlerResultFilter = new PageHandlerResultFilter();
// Act
await pageHandlerResultFilter.OnResultExecutionAsync(resultExecutingContext, next);
// Assert
modelAsFilter.Verify();
}
[Fact]
public async Task OnResultExecutionAsyn_ExecutesSyncFilters()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new Mock<object>();
var modelAsFilter = model.As<IResultFilter>();
modelAsFilter
.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()))
.Verifiable();
modelAsFilter
.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()))
.Verifiable();
var resultExecutingContext = new ResultExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new PageResult(),
model.Object);
var resultExecutedContext = new ResultExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
resultExecutingContext.Result,
model.Object);
ResultExecutionDelegate next = () => Task.FromResult(resultExecutedContext);
var pageHandlerResultFilter = new PageHandlerResultFilter();
// Act
await pageHandlerResultFilter.OnResultExecutionAsync(resultExecutingContext, next);
// Assert
modelAsFilter.Verify();
}
[Fact]
public async Task OnPageHandlerExecutionAsync_DoesNotInvokeResultExecuted_IfCancelled()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new Mock<object>();
var modelAsFilter = model.As<IResultFilter>();
modelAsFilter
.Setup(f => f.OnResultExecuting(It.IsAny<ResultExecutingContext>()))
.Callback((ResultExecutingContext context) => context.Cancel = true)
.Verifiable();
modelAsFilter
.Setup(f => f.OnResultExecuted(It.IsAny<ResultExecutedContext>()))
.Throws(new Exception("Shouldn't be called"));
var resultExecutingContext = new ResultExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new PageResult(),
model.Object);
var resultExecutedContext = new ResultExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
resultExecutingContext.Result,
model.Object);
ResultExecutionDelegate next = () => Task.FromResult(resultExecutedContext);
var pageHandlerResultFilter = new PageHandlerResultFilter();
// Act
await pageHandlerResultFilter.OnResultExecutionAsync(resultExecutingContext, next);
// Assert
modelAsFilter.Verify();
}
[Fact]
public async Task OnPageHandlerExecutionAsync_InvokesNextDelegateIfHandlerDoesNotImplementFilter()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var model = new object();
var resultExecutingContext = new ResultExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new PageResult(),
model);
var resultExecutedContext = new ResultExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
resultExecutingContext.Result,
model);
var invoked = false;
ResultExecutionDelegate next = () =>
{
invoked = true;
return Task.FromResult(resultExecutedContext);
};
var pageHandlerResultFilter = new PageHandlerResultFilter();
// Act
await pageHandlerResultFilter.OnResultExecutionAsync(resultExecutingContext, next);
// Assert
Assert.True(invoked);
}
}
}

View File

@ -25,7 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
provider.OnProvidersExecuting(context);
// Assert
Assert.Empty(context.PageApplicationModel.Filters);
Assert.Collection(
context.PageApplicationModel.Filters,
f => Assert.IsType<PageHandlerPageFilter>(f));
}
private class PageWithoutResponseCache : Page
@ -59,6 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(
context.PageApplicationModel.Filters,
f => { },
f => Assert.IsType<PageHandlerPageFilter>(f),
f =>
{
var filter = Assert.IsType<ResponseCacheFilter>(f);
@ -104,6 +107,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
Assert.Collection(
context.PageApplicationModel.Filters,
f => { },
f => Assert.IsType<PageHandlerPageFilter>(f),
f =>
{
var filter = Assert.IsType<ResponseCacheFilter>(f);

View File

@ -7,6 +7,7 @@ using System.IO;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.Formatters;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure;
@ -1762,6 +1763,99 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
Assert.Null(pageResult.Page); // This is set by the invoker
}
[Fact]
public async Task AsyncPageHandlerExecutingMethod_InvokeSyncMethods()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var pageHandlerExecutingContext = new PageHandlerExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new Dictionary<string, object>(),
new object());
var pageHandlerExecutedContext = new PageHandlerExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new object());
var testPageModel = new Mock<PageModel> { CallBase = true };
testPageModel.Setup(p => p.OnPageHandlerExecuting(pageHandlerExecutingContext))
.Verifiable();
testPageModel.Setup(p => p.OnPageHandlerExecuted(pageHandlerExecutedContext))
.Verifiable();
// Act
await testPageModel.Object.OnPageHandlerExecutionAsync(
pageHandlerExecutingContext,
() => Task.FromResult(pageHandlerExecutedContext));
testPageModel.Verify();
}
[Fact]
public async Task AsyncPageHandlerExecutingMethod__DoesNotInvokeExecutedMethod_IfResultIsSet()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var pageHandlerExecutingContext = new PageHandlerExecutingContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new Dictionary<string, object>(),
new object());
var pageHandlerExecutedContext = new PageHandlerExecutedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new HandlerMethodDescriptor(),
new object());
var testPageModel = new Mock<PageModel>() { CallBase = true };
testPageModel.Setup(p => p.OnPageHandlerExecuting(pageHandlerExecutingContext))
.Callback((PageHandlerExecutingContext context) => context.Result = new PageResult())
.Verifiable();
testPageModel.Setup(p => p.OnPageHandlerExecuted(pageHandlerExecutedContext))
.Throws(new Exception("Shouldn't be called"));
// Act
await testPageModel.Object.OnPageHandlerExecutionAsync(
pageHandlerExecutingContext,
() => Task.FromResult(pageHandlerExecutedContext));
testPageModel.Verify();
}
[Fact]
public async Task AsyncPageHandlerSelectingMethod_InvokeSyncMethods()
{
// Arrange
var pageContext = new PageContext(new ActionContext(
new DefaultHttpContext(),
new RouteData(),
new PageActionDescriptor(),
new ModelStateDictionary()));
var pageHandlerSelectedContext = new PageHandlerSelectedContext(
pageContext,
Array.Empty<IFilterMetadata>(),
new object());
var testPageModel = new Mock<PageModel> { CallBase = true };
testPageModel.Setup(p => p.OnPageHandlerSelected(pageHandlerSelectedContext))
.Verifiable();
// Act
await testPageModel.Object.OnPageHandlerSelectionAsync(pageHandlerSelectedContext);
testPageModel.Verify();
}
private class ContentPageModel : PageModel
{
public IActionResult Content_WithNoEncoding()

View File

@ -0,0 +1,39 @@
// 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 Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace RazorPagesWebSite
{
public class ModelAsFilter : PageModel, IResultFilter
{
public string Message { get; set; }
public void OnGet(string message)
{
Message = message;
}
public IActionResult OnGetTestResultFilter() => NotFound();
public override void OnPageHandlerExecuting(PageHandlerExecutingContext context)
{
context.HandlerArguments["message"] = "Hello from OnPageHandlerExecuting";
}
public void OnResultExecuted(ResultExecutedContext context)
{
}
public void OnResultExecuting(ResultExecutingContext context)
{
if (context.Result is NotFoundResult)
{
context.Result = Redirect("/Different-Location");
}
}
}
}

View File

@ -0,0 +1,3 @@
@page "{handler?}"
@model RazorPagesWebSite.ModelAsFilter
@Model.Message