// Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. using System; using System.Linq; using System.Reflection; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc.Filters; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.AspNetCore.Mvc.Razor; using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure; using Microsoft.AspNetCore.Mvc.RazorPages.Internal; using Microsoft.Extensions.Options; using Xunit; namespace Microsoft.AspNetCore.Mvc.ApplicationModels { public class DefaultPageApplicationModelProviderTest { [Fact] public void OnProvidersExecuting_ThrowsIfPageDoesNotDeriveFromValidBaseType() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(InvalidPageWithWrongBaseClass).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act & Assert var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); // Assert Assert.Equal( $"The type '{typeInfo.FullName}' is not a valid page. A page must inherit from '{typeof(PageBase).FullName}'.", ex.Message); } private class InvalidPageWithWrongBaseClass : RazorPageBase { public override void BeginContext(int position, int length, bool isLiteral) { throw new NotImplementedException(); } public override void EndContext() { throw new NotImplementedException(); } public override void EnsureRenderedBodyOrSections() { throw new NotImplementedException(); } public override Task ExecuteAsync() { throw new NotImplementedException(); } } [Fact] public void OnProvidersExecuting_ThrowsIfModelPropertyDoesNotExistOnPage() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithoutModelProperty).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act & Assert var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); // Assert Assert.Equal( $"The type '{typeInfo.FullName}' is not a valid page. A page must define a public, non-static 'Model' property.", ex.Message); } private class PageWithoutModelProperty : PageBase { public override Task ExecuteAsync() => throw new NotImplementedException(); } [Fact] public void OnProvidersExecuting_ThrowsIfModelPropertyIsNotPublic() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithNonVisibleModel).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act & Assert var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); // Assert Assert.Equal( $"The type '{typeInfo.FullName}' is not a valid page. A page must define a public, non-static 'Model' property.", ex.Message); } private class PageWithNonVisibleModel : PageBase { private object Model => null; public override Task ExecuteAsync() => throw new NotImplementedException(); } [Fact] public void OnProvidersExecuting_ThrowsIfModelPropertyIsStatic() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithStaticModel).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act & Assert var ex = Assert.Throws(() => provider.OnProvidersExecuting(context)); // Assert Assert.Equal( $"The type '{typeInfo.FullName}' is not a valid page. A page must define a public, non-static 'Model' property.", ex.Message); } private class PageWithStaticModel : PageBase { public static object Model => null; public override Task ExecuteAsync() => throw new NotImplementedException(); } [Fact] public void OnProvidersExecuting_DiscoversPropertiesFromPage_IfModelTypeDoesNotHaveAttribute() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithModelWithoutPageModelAttribute).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act provider.OnProvidersExecuting(context); // Assert Assert.NotNull(context.PageApplicationModel); var propertiesOnPage = context.PageApplicationModel.HandlerProperties .Where(p => p.PropertyInfo.DeclaringType.GetTypeInfo() == typeInfo); Assert.Collection( propertiesOnPage.OrderBy(p => p.PropertyName), property => { Assert.Equal(typeInfo.GetProperty(nameof(PageWithModelWithoutPageModelAttribute.Model)), property.PropertyInfo); Assert.Equal(nameof(PageWithModelWithoutPageModelAttribute.Model), property.PropertyName); }, property => { Assert.Equal(typeInfo.GetProperty(nameof(PageWithModelWithoutPageModelAttribute.Property1)), property.PropertyInfo); Assert.Null(property.BindingInfo); Assert.Equal(nameof(PageWithModelWithoutPageModelAttribute.Property1), property.PropertyName); }, property => { Assert.Equal(typeInfo.GetProperty(nameof(PageWithModelWithoutPageModelAttribute.Property2)), property.PropertyInfo); Assert.Equal(nameof(PageWithModelWithoutPageModelAttribute.Property2), property.PropertyName); Assert.NotNull(property.BindingInfo); Assert.Equal(BindingSource.Path, property.BindingInfo.BindingSource); }); } private class PageWithModelWithoutPageModelAttribute : Page { public string Property1 { get; set; } [FromRoute] public object Property2 { get; set; } public ModelWithoutPageModelAttribute Model => null; public override Task ExecuteAsync() => throw new NotImplementedException(); } private class ModelWithoutPageModelAttribute { } [Fact] public void OnProvidersExecuting_DiscoversPropertiesFromPageModel_IfModelHasAttribute() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithModelWithPageModelAttribute).GetTypeInfo(); var modelType = typeof(ModelWithPageModelAttribute); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act provider.OnProvidersExecuting(context); // Assert Assert.NotNull(context.PageApplicationModel); Assert.Collection( context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName), property => { Assert.Equal(modelType.GetProperty(nameof(ModelWithPageModelAttribute.Property)), property.PropertyInfo); Assert.Equal(nameof(ModelWithPageModelAttribute.Property), property.PropertyName); Assert.NotNull(property.BindingInfo); Assert.Equal(BindingSource.Path, property.BindingInfo.BindingSource); }); } private class PageWithModelWithPageModelAttribute : Page { public string Property1 { get; set; } [FromRoute] public object Property2 { get; set; } public ModelWithPageModelAttribute Model => null; public override Task ExecuteAsync() => throw new NotImplementedException(); } [PageModel] private class ModelWithPageModelAttribute { [FromRoute] public string Property { get; set; } } [Fact] public void OnProvidersExecuting_DiscoversProperties_FromAllSubTypesThatDeclaresBindProperty() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(BindPropertyAttributeOnBaseModelPage).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act provider.OnProvidersExecuting(context); // Assert Assert.NotNull(context.PageApplicationModel); Assert.Collection( context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName).Where(p => p.BindingInfo != null), property => { var name = nameof(ModelLevel3.Property2); Assert.Equal(typeof(ModelLevel3).GetProperty(name), property.PropertyInfo); Assert.Equal(name, property.PropertyName); Assert.NotNull(property.BindingInfo); }, property => { var name = nameof(ModelLevel3.Property3); Assert.Equal(typeof(ModelLevel3).GetProperty(name), property.PropertyInfo); Assert.Equal(name, property.PropertyName); Assert.NotNull(property.BindingInfo); }); } private class BindPropertyAttributeOnBaseModelPage : Page { public ModelLevel3 Model => null; public override Task ExecuteAsync() => throw new NotImplementedException(); } private class ModelLevel1 : PageModel { public string Property1 { get; set; } } [BindProperties] private class ModelLevel2 : ModelLevel1 { public string Property2 { get; set; } } private class ModelLevel3 : ModelLevel2 { public string Property3 { get; set; } } [Fact] public void OnProvidersExecuting_DiscoversHandlersFromPage() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithModelWithoutHandlers).GetTypeInfo(); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act provider.OnProvidersExecuting(context); // Assert Assert.NotNull(context.PageApplicationModel); Assert.Collection( context.PageApplicationModel.HandlerMethods.OrderBy(p => p.Name), handler => { var name = nameof(PageWithModelWithoutHandlers.OnGet); Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo); Assert.Equal(name, handler.Name); Assert.Equal("Get", handler.HttpMethod); Assert.Null(handler.HandlerName); }, handler => { var name = nameof(PageWithModelWithoutHandlers.OnPostAsync); Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo); Assert.Equal(name, handler.Name); Assert.Equal("Post", handler.HttpMethod); Assert.Null(handler.HandlerName); }, handler => { var name = nameof(PageWithModelWithoutHandlers.OnPostDeleteCustomerAsync); Assert.Equal(typeInfo.GetMethod(name), handler.MethodInfo); Assert.Equal(name, handler.Name); Assert.Equal("Post", handler.HttpMethod); Assert.Equal("DeleteCustomer", handler.HandlerName); }); } [Fact] public void OnProvidersExecuting_DiscoversPropertiesFromModel() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithModel).GetTypeInfo(); var modelType = typeof(TestPageModel); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act provider.OnProvidersExecuting(context); // Assert Assert.NotNull(context.PageApplicationModel); Assert.Collection( context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName), property => { var name = nameof(TestPageModel.Property1); Assert.Equal(modelType.GetProperty(name), property.PropertyInfo); Assert.Null(property.BindingInfo); Assert.Equal(name, property.PropertyName); }, property => { var name = nameof(TestPageModel.Property2); Assert.Equal(modelType.GetProperty(name), property.PropertyInfo); Assert.Equal(name, property.PropertyName); Assert.NotNull(property.BindingInfo); Assert.Equal(BindingSource.Query, property.BindingInfo.BindingSource); }); } [Fact] public void OnProvidersExecuting_DiscoversBindingInfoFromHandler() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithBindPropertyModel).GetTypeInfo(); var modelType = typeof(ModelWithBindProperty); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act provider.OnProvidersExecuting(context); // Assert Assert.NotNull(context.PageApplicationModel); Assert.Collection( context.PageApplicationModel.HandlerProperties.OrderBy(p => p.PropertyName), property => { Assert.Equal(nameof(ModelWithBindProperty.Property1), property.PropertyName); Assert.NotNull(property.BindingInfo); }, property => { Assert.Equal(nameof(ModelWithBindProperty.Property2), property.PropertyName); Assert.NotNull(property.BindingInfo); Assert.Equal(BindingSource.Path, property.BindingInfo.BindingSource); }); } private class PageWithBindPropertyModel : PageBase { public ModelWithBindProperty Model => null; public override Task ExecuteAsync() => null; } [BindProperties] [PageModel] private class ModelWithBindProperty { public string Property1 { get; set; } [FromRoute] public string Property2 { get; set; } } [Fact] public void OnProvidersExecuting_DiscoversHandlersFromModel() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithModel).GetTypeInfo(); var modelType = typeof(TestPageModel); var descriptor = new PageActionDescriptor(); var context = new PageApplicationModelProviderContext(descriptor, typeInfo); // Act provider.OnProvidersExecuting(context); // Assert Assert.NotNull(context.PageApplicationModel); Assert.Collection( context.PageApplicationModel.HandlerMethods.OrderBy(p => p.Name), handler => { var name = nameof(TestPageModel.OnGetUser); Assert.Equal(modelType.GetMethod(name), handler.MethodInfo); Assert.Equal(name, handler.Name); Assert.Equal("Get", handler.HttpMethod); Assert.Equal("User", handler.HandlerName); }); } // We want to test the 'empty' page has no bound properties, and no handler methods. [Fact] public void OnProvidersExecuting_EmptyPage() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(EmptyPage).GetTypeInfo(); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); // Act provider.OnProvidersExecuting(context); // Assert var pageModel = context.PageApplicationModel; Assert.Empty(pageModel.HandlerProperties.Where(p => p.BindingInfo != null)); Assert.Empty(pageModel.HandlerMethods); Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.HandlerType); Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.ModelType); Assert.Same(typeof(EmptyPage).GetTypeInfo(), pageModel.PageType); } // We want to test the 'empty' page and PageModel has no bound properties, and no handler methods. [Fact] public void OnProvidersExecuting_EmptyPageModel() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(EmptyPageWithPageModel).GetTypeInfo(); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); // Act provider.OnProvidersExecuting(context); // Assert var pageModel = context.PageApplicationModel; Assert.Empty(pageModel.HandlerProperties.Where(p => p.BindingInfo != null)); Assert.Empty(pageModel.HandlerMethods); Assert.Same(typeof(EmptyPageModel).GetTypeInfo(), pageModel.DeclaredModelType); Assert.Same(typeof(EmptyPageModel).GetTypeInfo(), pageModel.ModelType); Assert.Same(typeof(EmptyPageModel).GetTypeInfo(), pageModel.HandlerType); Assert.Same(typeof(EmptyPageWithPageModel).GetTypeInfo(), pageModel.PageType); } private class EmptyPage : Page { // Copied from generated code [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => null; public EmptyPage Model => ViewData.Model; public override Task ExecuteAsync() { throw new NotImplementedException(); } } private class EmptyPageWithPageModel : Page { // Copied from generated code [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.ViewFeatures.IModelExpressionProvider ModelExpressionProvider { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.IUrlHelper Url { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.IViewComponentHelper Component { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.Rendering.IJsonHelper Json { get; private set; } [global::Microsoft.AspNetCore.Mvc.Razor.Internal.RazorInjectAttribute] public global::Microsoft.AspNetCore.Mvc.Rendering.IHtmlHelper Html { get; private set; } public global::Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary ViewData => null; public EmptyPageModel Model => ViewData.Model; public override Task ExecuteAsync() { throw new NotImplementedException(); } } private class EmptyPageModel : PageModel { } [Fact] // If the model has handler methods, we prefer those. public void CreateDescriptor_FindsHandlerMethod_OnModel() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo(); var modelType = typeof(ModelWithHandler); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); // Act provider.OnProvidersExecuting(context); // Assert var pageModel = context.PageApplicationModel; Assert.Contains( pageModel.HandlerProperties, p => p.PropertyInfo == modelType.GetProperty(nameof(ModelWithHandler.BindMe))); Assert.Collection( pageModel.HandlerMethods, p => Assert.Equal(modelType.GetMethod(nameof(ModelWithHandler.OnGet)), p.MethodInfo)); Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), pageModel.HandlerType); Assert.Same(typeof(ModelWithHandler).GetTypeInfo(), pageModel.ModelType); Assert.Same(typeof(PageWithHandlerThatGetsIgnored).GetTypeInfo(), pageModel.PageType); } private class ModelWithHandler : PageModel { [ModelBinder] public int BindMe { get; set; } public void OnGet() { } } private class PageWithHandlerThatGetsIgnored : Page { public ModelWithHandler Model => null; [ModelBinder] public int IgnoreMe { get; set; } public void OnPost() { } public override Task ExecuteAsync() => throw new NotImplementedException(); } [Fact] // If the model does not have the PageModelAttribute, we look at the page instead. public void OnProvidersExecuting_FindsHandlerMethodOnPage_WhenModelIsNotAnnotatedWithPageModelAttribute() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithHandler).GetTypeInfo(); var context = new PageApplicationModelProviderContext(new PageActionDescriptor(), typeInfo); // Act provider.OnProvidersExecuting(context); // Assert var pageModel = context.PageApplicationModel; var propertiesOnPage = pageModel.HandlerProperties .Where(p => p.PropertyInfo.DeclaringType.GetTypeInfo() == typeInfo); Assert.Collection( propertiesOnPage.OrderBy(p => p.PropertyName), p => Assert.Equal(typeInfo.GetProperty(nameof(PageWithHandler.BindMe)), p.PropertyInfo), p => Assert.Equal(typeInfo.GetProperty(nameof(PageWithHandler.Model)), p.PropertyInfo)); Assert.Collection( pageModel.HandlerMethods, p => Assert.Equal(typeInfo.GetMethod(nameof(PageWithHandler.OnGet)), p.MethodInfo)); Assert.Same(typeof(PageWithHandler).GetTypeInfo(), pageModel.HandlerType); Assert.Same(typeof(PocoModel).GetTypeInfo(), pageModel.ModelType); Assert.Same(typeof(PageWithHandler).GetTypeInfo(), pageModel.PageType); } private class PageWithHandler : Page { public PocoModel Model => null; [ModelBinder] public int BindMe { get; set; } public void OnGet() { } public override Task ExecuteAsync() => throw new NotImplementedException(); } private class PocoModel { // Just a plain ol' model, nothing to see here. [ModelBinder] public int IgnoreMe { get; set; } public void OnGet() { } } [Fact] public void PopulateHandlerMethods_DiscoversHandlersFromBaseType() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(InheritsMethods).GetTypeInfo(); var baseType = typeof(TestSetPageModel); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; Assert.Collection( handlerMethods.OrderBy(h => h.MethodInfo.DeclaringType.Name).ThenBy(h => h.MethodInfo.Name), handler => { Assert.Equal(nameof(InheritsMethods.OnGet), handler.MethodInfo.Name); Assert.Equal(typeInfo, handler.MethodInfo.DeclaringType.GetTypeInfo()); }, handler => { Assert.Equal(nameof(TestSetPageModel.OnGet), handler.MethodInfo.Name); Assert.Equal(baseType, handler.MethodInfo.DeclaringType); }, handler => { Assert.Equal(nameof(TestSetPageModel.OnPost), handler.MethodInfo.Name); Assert.Equal(baseType, handler.MethodInfo.DeclaringType); }); } private class TestSetPageModel { public void OnGet() { } public void OnPost() { } } private class InheritsMethods : TestSetPageModel { public new void OnGet() { } } [Fact] public void PopulateHandlerMethods_IgnoresNonPublicMethods() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(ProtectedModel).GetTypeInfo(); var baseType = typeof(TestSetPageModel); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; Assert.Empty(handlerMethods); } private class ProtectedModel { protected void OnGet() { } private void OnPost() { } } [Fact] public void PopulateHandlerMethods_IgnoreGenericTypeParameters() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(GenericClassModel).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; Assert.Empty(handlerMethods); } private class GenericClassModel { public void OnGet() { } } [Fact] public void PopulateHandlerMethods_IgnoresStaticMethods() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageModelWithStaticHandler).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageModelWithStaticHandler.OnGet), BindingFlags.Public | BindingFlags.Instance); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; Assert.Collection( handlerMethods, handler => Assert.Same(expected, handler.MethodInfo)); } private class PageModelWithStaticHandler { public static void OnGet(string name) { } public void OnGet() { } } [Fact] public void PopulateHandlerMethods_IgnoresAbstractMethods() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageModelWithAbstractMethod).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageModelWithAbstractMethod.OnGet), BindingFlags.Public | BindingFlags.Instance); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; Assert.Collection( handlerMethods, handler => Assert.Same(expected, handler.MethodInfo)); } private abstract class PageModelWithAbstractMethod { public abstract void OnPost(string name); public void OnGet() { } } [Fact] public void PopulateHandlerMethods_IgnoresMethodWithNonHandlerAttribute() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithNonHandlerMethod).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageWithNonHandlerMethod.OnGet), BindingFlags.Public | BindingFlags.Instance); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; Assert.Collection( handlerMethods, handler => Assert.Same(expected, handler.MethodInfo)); } private class PageWithNonHandlerMethod { [NonHandler] public void OnPost(string name) { } public void OnGet() { } } // There are more tests for the parsing elsewhere, this is just testing that it's wired // up to the model. [Fact] public void CreateHandlerModel_ParsesMethod() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageModelWithHandlerNames).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; Assert.Collection( handlerMethods.OrderBy(h => h.MethodInfo.Name), handler => { Assert.Same(typeInfo.GetMethod(nameof(PageModelWithHandlerNames.OnPutDeleteAsync)), handler.MethodInfo); Assert.Equal("Put", handler.HttpMethod); Assert.Equal("Delete", handler.HandlerName); }); } private class PageModelWithHandlerNames { public void OnPutDeleteAsync() { } public void Foo() // This isn't a valid handler name. { } } [Fact] public void CreateHandlerMethods_AddsParameterDescriptors() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost)); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; var handler = Assert.Single(handlerMethods); Assert.Collection( handler.Parameters, p => { Assert.NotNull(p.ParameterInfo); Assert.Equal(typeof(string), p.ParameterInfo.ParameterType); Assert.Equal("name", p.ParameterName); }, p => { Assert.NotNull(p.ParameterInfo); Assert.Equal(typeof(int), p.ParameterInfo.ParameterType); Assert.Equal("id", p.ParameterName); Assert.Equal("personId", p.BindingInfo.BinderModelName); }); } [Fact] public void CreateHandlerMethods_WithLegacyValidationBehavior_AddsParameterDescriptors() { // Arrange var provider = new DefaultPageApplicationModelProvider( TestModelMetadataProvider.CreateDefaultProvider(), Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = false }), Options.Create(new RazorPagesOptions())); var typeInfo = typeof(PageWithHandlerParameters).GetTypeInfo(); var expected = typeInfo.GetMethod(nameof(PageWithHandlerParameters.OnPost)); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerMethods(pageModel); // Assert var handlerMethods = pageModel.HandlerMethods; var handler = Assert.Single(handlerMethods); Assert.Collection( handler.Parameters, p => { Assert.NotNull(p.ParameterInfo); Assert.Equal(typeof(string), p.ParameterInfo.ParameterType); Assert.Equal("name", p.ParameterName); }, p => { Assert.NotNull(p.ParameterInfo); Assert.Equal(typeof(int), p.ParameterInfo.ParameterType); Assert.Equal("id", p.ParameterName); Assert.Equal("personId", p.BindingInfo.BinderModelName); }); } private class PageWithHandlerParameters { public void OnPost(string name, [ModelBinder(Name = "personId")] int id) { } } // We're using PropertyHelper from Common to find the properties here, which implements // out standard set of semantics for properties that the framework interacts with. // // One of the desirable consequences of that is we only find 'visible' properties. We're not // retesting all of the details of PropertyHelper here, just the visibility part as a quick check // that we're using PropertyHelper as expected. [Fact] public void PopulateHandlerProperties_UsesPropertyHelpers_ToFindProperties() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(HidesAProperty).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerProperties(pageModel); // Assert var properties = pageModel.HandlerProperties; Assert.Collection( properties, p => { Assert.Equal(typeof(HidesAProperty).GetTypeInfo(), p.PropertyInfo.DeclaringType.GetTypeInfo()); }); } private class HasAHiddenProperty { [BindProperty] public int Property { get; set; } } private class HidesAProperty : HasAHiddenProperty { [BindProperty] public new int Property { get; set; } } [Fact] public void PopulateHandlerProperties_SupportsGet_OnProperty() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(ModelSupportsGetOnProperty).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, new object[0]); // Act provider.PopulateHandlerProperties(pageModel); // Assert var properties = pageModel.HandlerProperties; Assert.Collection( properties.OrderBy(p => p.PropertyName), p => { Assert.Equal(typeInfo.GetProperty(nameof(ModelSupportsGetOnProperty.Property)), p.PropertyInfo); Assert.NotNull(p.BindingInfo.RequestPredicate); Assert.True(p.BindingInfo.RequestPredicate(new ActionContext { HttpContext = new DefaultHttpContext { Request = { Method ="GET", } } })); }); } private class ModelSupportsGetOnProperty { [BindProperty(SupportsGet = true)] public int Property { get; set; } } [Theory] [InlineData("Foo")] [InlineData("On")] [InlineData("OnAsync")] [InlineData("Async")] public void TryParseHandler_ParsesHandlerNames_InvalidData(string methodName) { // Act var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler); // Assert Assert.False(result); Assert.Null(httpMethod); Assert.Null(handler); } [Theory] [InlineData("OnG", "G", null)] [InlineData("OnGAsync", "G", null)] [InlineData("OnPOST", "P", "OST")] [InlineData("OnPOSTAsync", "P", "OST")] [InlineData("OnDeleteFoo", "Delete", "Foo")] [InlineData("OnDeleteFooAsync", "Delete", "Foo")] [InlineData("OnMadeupLongHandlerName", "Madeup", "LongHandlerName")] [InlineData("OnMadeupLongHandlerNameAsync", "Madeup", "LongHandlerName")] public void TryParseHandler_ParsesHandlerNames_ValidData(string methodName, string expectedHttpMethod, string expectedHandler) { // Arrange // Act var result = DefaultPageApplicationModelProvider.TryParseHandlerMethod(methodName, out var httpMethod, out var handler); // Assert Assert.True(result); Assert.Equal(expectedHttpMethod, httpMethod); Assert.Equal(expectedHandler, handler); } private class PageWithModelWithoutHandlers : Page { public ModelWithoutHandler Model { get; } public override Task ExecuteAsync() => throw new NotImplementedException(); public void OnGet() { } public void OnPostAsync() { } public void OnPostDeleteCustomerAsync() { } public class ModelWithoutHandler { } } private class PageWithModel : Page { public TestPageModel Model { get; } public override Task ExecuteAsync() => throw new NotImplementedException(); } [PageModel] private class TestPageModel { public string Property1 { get; set; } [FromQuery] public string Property2 { get; set; } public void OnGetUser() { } } [Fact] public void PopulateFilters_With21CompatBehavior_DoesNotAddDisallowOptionsRequestsPageFilter() { // Arrange var provider = new DefaultPageApplicationModelProvider( TestModelMetadataProvider.CreateDefaultProvider(), Options.Create(new MvcOptions()), Options.Create(new RazorPagesOptions())); var typeInfo = typeof(object).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); // Act provider.PopulateFilters(pageModel); // Assert Assert.Empty(pageModel.Filters); } [Fact] public void PopulateFilters_AddsDisallowOptionsRequestsPageFilter() { // Arrange var provider = CreateProvider(); var typeInfo = typeof(object).GetTypeInfo(); var pageModel = new PageApplicationModel(new PageActionDescriptor(), typeInfo, typeInfo.GetCustomAttributes(inherit: true)); // Act provider.PopulateFilters(pageModel); // Assert Assert.Collection( pageModel.Filters, filter => Assert.IsType(filter)); } [Fact] public void PopulateFilters_AddsIFilterMetadataAttributesToModel() { // Arrange var provider = CreateProvider(); 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(filter), filter => Assert.IsType(filter)); } [PageModel] [Serializable] [TypeFilter(typeof(object))] private class FilterModel { } [Fact] public void PopulateFilters_AddsPageHandlerPageFilter_IfPageImplementsIAsyncPageFilter() { // Arrange var provider = CreateProvider(); 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(filter), filter => Assert.IsType(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 = CreateProvider(); 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(filter), filter => Assert.IsType(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 = CreateProvider(); 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(filter), filter => Assert.IsType(filter), filter => Assert.IsType(filter)); } [ServiceFilter(typeof(IServiceProvider))] private class DerivedFromPageModel : PageModel { } private static DefaultPageApplicationModelProvider CreateProvider() { return new DefaultPageApplicationModelProvider( TestModelMetadataProvider.CreateDefaultProvider(), Options.Create(new MvcOptions { AllowValidatingTopLevelNodes = true }), Options.Create(new RazorPagesOptions { AllowDefaultHandlingForOptionsRequests = true })); } } }