diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs index 87e2a239c4..5e194b0d28 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcCoreBuilderExtensions.cs @@ -92,8 +92,6 @@ namespace Microsoft.Extensions.DependencyInjection internal static void AddServices(IServiceCollection services) { // Options - services.TryAddEnumerable( - ServiceDescriptor.Transient, RazorPagesOptionsSetup>()); services.TryAddEnumerable( ServiceDescriptor.Transient, RazorPagesRazorViewEngineOptionsSetup>()); @@ -105,6 +103,8 @@ namespace Microsoft.Extensions.DependencyInjection ServiceDescriptor.Singleton()); services.TryAddEnumerable( ServiceDescriptor.Singleton()); + services.TryAddEnumerable( + ServiceDescriptor.Singleton()); services.TryAddEnumerable( ServiceDescriptor.Singleton()); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageFilterApplicationModelProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageFilterApplicationModelProvider.cs new file mode 100644 index 0000000000..24ba99ff26 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageFilterApplicationModelProvider.cs @@ -0,0 +1,41 @@ +// 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.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageFilterApplicationModelProvider : IPageApplicationModelProvider + { + /// This order ensures that runs after + /// and . + /// + public int Order => -1000 + 10; + + public void OnProvidersExecuted(PageApplicationModelProviderContext context) + { + // Do nothing + } + + public void OnProvidersExecuting(PageApplicationModelProviderContext context) + { + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + + for (var i = 0; i < context.Results.Count; i++) + { + var pageApplicationModel = context.Results[i]; + + // Support for [TempData] on properties + pageApplicationModel.Filters.Add(new PageSaveTempDataPropertyFilterFactory()); + + // Always require an antiforgery token on post + pageApplicationModel.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); + } + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesOptionsSetup.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesOptionsSetup.cs deleted file mode 100644 index dde0e9bf01..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/RazorPagesOptionsSetup.cs +++ /dev/null @@ -1,29 +0,0 @@ -// 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.ViewFeatures.Internal; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; - -namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal -{ - public class RazorPagesOptionsSetup : IConfigureOptions - { - public void Configure(RazorPagesOptions options) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - // Support for [TempData] on properties - options.ConfigureFilter(page => new PageSaveTempDataPropertyFilterFactory()); - - // Always require an antiforgery token on post - options.ConfigureFilter(new AutoValidateAntiforgeryTokenAttribute()); - - options.RootDirectory = "/Pages"; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs index 5207e7bbf0..5fd0321190 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs @@ -12,7 +12,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// public class RazorPagesOptions { - private string _root = "/"; + private string _root = "/Pages"; /// /// Gets a list of instances that will be applied to @@ -22,6 +22,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages /// /// Application relative path used as the root of discovery for Razor Page files. + /// Defaults to the /Pages directory under application root. /// public string RootDirectory { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs index 7f1a26502d..95069bc9de 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Infrastructure/PageActionDescriptorProviderTest.cs @@ -186,8 +186,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure // Arrange var options = new MvcOptions(); var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel()); + var filterProvider = new PageFilterApplicationModelProvider(); var provider = new PageActionDescriptorProvider( - new[] { applicationModelProvider }, + new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider }, GetAccessor(options), GetRazorPagesOptions()); var context = new ActionDescriptorProviderContext(); @@ -222,8 +223,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure options.Filters.Add(filter1); options.Filters.Add(filter2); var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel()); + var filterProvider = new PageFilterApplicationModelProvider(); var provider = new PageActionDescriptorProvider( - new[] { applicationModelProvider }, + new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider }, GetAccessor(options), GetRazorPagesOptions()); var context = new ActionDescriptorProviderContext(); @@ -275,8 +277,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure var razorOptions = GetRazorPagesOptions(); razorOptions.Value.Conventions.Add(convention.Object); var applicationModelProvider = new TestPageApplicationModelProvider(CreateModel()); + var filterProvider = new PageFilterApplicationModelProvider(); var provider = new PageActionDescriptorProvider( - new[] { applicationModelProvider }, + new IPageApplicationModelProvider[] { applicationModelProvider, filterProvider }, GetAccessor(options), razorOptions); var context = new ActionDescriptorProviderContext(); @@ -337,7 +340,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure private static IOptions GetRazorPagesOptions() { - return new OptionsManager(new[] { new RazorPagesOptionsSetup() }); + return new TestOptionsManager(); } private static RazorProjectItem GetProjectItem(string basePath, string path, string content) @@ -359,7 +362,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure _models = models ?? Array.Empty(); } - public int Order => 0; + public int Order => -1000; public void OnProvidersExecuted(PageApplicationModelProviderContext context) { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageApplicationModelProviderTest.cs index 5fcec76fa2..62cda6fe5a 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageApplicationModelProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageApplicationModelProviderTest.cs @@ -31,16 +31,51 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal result => { Assert.Equal("/Pages/About.cshtml", result.RelativePath); - Assert.Equal("/Pages/About", result.ViewEnginePath); + Assert.Equal("/About", result.ViewEnginePath); Assert.Collection(result.Selectors, - selector => Assert.Equal("Pages/About", selector.AttributeRouteModel.Template)); + selector => Assert.Equal("About", selector.AttributeRouteModel.Template)); }, result => { Assert.Equal("/Pages/Home.cshtml", result.RelativePath); - Assert.Equal("/Pages/Home", result.ViewEnginePath); + Assert.Equal("/Home", result.ViewEnginePath); Assert.Collection(result.Selectors, - selector => Assert.Equal("Pages/Home/some-prefix", selector.AttributeRouteModel.Template)); + selector => Assert.Equal("Home/some-prefix", selector.AttributeRouteModel.Template)); + }); + } + + [Fact] + public void OnProvidersExecuting_AddsMultipleSelectorsForIndexPage_WithIndexAtRoot() + { + // Arrange + var info = new[] + { + new CompiledPageInfo("/Pages/Index.cshtml", typeof(object), routePrefix: string.Empty), + new CompiledPageInfo("/Pages/Admin/Index.cshtml", typeof(object), "some-template"), + }; + var provider = new TestCompiledPageApplicationModelProvider(info, new RazorPagesOptions { RootDirectory = "/" }); + var context = new PageApplicationModelProviderContext(); + + // Act + provider.OnProvidersExecuting(context); + + // Assert + Assert.Collection(context.Results, + result => + { + Assert.Equal("/Pages/Index.cshtml", result.RelativePath); + Assert.Equal("/Pages/Index", result.ViewEnginePath); + Assert.Collection(result.Selectors, + selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template), + selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template)); + }, + result => + { + Assert.Equal("/Pages/Admin/Index.cshtml", result.RelativePath); + Assert.Equal("/Pages/Admin/Index", result.ViewEnginePath); + Assert.Collection(result.Selectors, + selector => Assert.Equal("Pages/Admin/Index/some-template", selector.AttributeRouteModel.Template), + selector => Assert.Equal("Pages/Admin/some-template", selector.AttributeRouteModel.Template)); }); } @@ -64,18 +99,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal result => { Assert.Equal("/Pages/Index.cshtml", result.RelativePath); - Assert.Equal("/Pages/Index", result.ViewEnginePath); + Assert.Equal("/Index", result.ViewEnginePath); Assert.Collection(result.Selectors, - selector => Assert.Equal("Pages/Index", selector.AttributeRouteModel.Template), - selector => Assert.Equal("Pages", selector.AttributeRouteModel.Template)); + selector => Assert.Equal("Index", selector.AttributeRouteModel.Template), + selector => Assert.Equal("", selector.AttributeRouteModel.Template)); }, result => { Assert.Equal("/Pages/Admin/Index.cshtml", result.RelativePath); - Assert.Equal("/Pages/Admin/Index", result.ViewEnginePath); + Assert.Equal("/Admin/Index", result.ViewEnginePath); Assert.Collection(result.Selectors, - selector => Assert.Equal("Pages/Admin/Index/some-template", selector.AttributeRouteModel.Template), - selector => Assert.Equal("Pages/Admin/some-template", selector.AttributeRouteModel.Template)); + selector => Assert.Equal("Admin/Index/some-template", selector.AttributeRouteModel.Template), + selector => Assert.Equal("Admin/some-template", selector.AttributeRouteModel.Template)); }); } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs index 6ffc1d7039..ecf5f140e7 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionDescriptorChangeProviderTest.cs @@ -26,7 +26,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal var changeToken = changeProvider.GetChangeToken(); // Assert - fileProvider.Verify(f => f.Watch("/**/*.cshtml")); + fileProvider.Verify(f => f.Watch("/Pages/**/*.cshtml")); } [Theory] diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs index 8349080cdd..85dd34a504 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageActionInvokerProviderTest.cs @@ -182,7 +182,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal loader.Object, CreateActionDescriptorCollection(descriptor), razorPageFactoryProvider: razorPageFactoryProvider.Object, - razorProject: defaultRazorProject); + razorProject: defaultRazorProject, + razorPagesOptions: new RazorPagesOptions { RootDirectory = "/" }); var context = new ActionInvokerProviderContext(new ActionContext() { @@ -346,7 +347,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal pageProvider: null, modelProvider: null, razorPageFactoryProvider: razorPageFactoryProvider, - razorProject: razorProject); + razorProject: razorProject, + razorPagesOptions: new RazorPagesOptions { RootDirectory = "/" }); var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor); @@ -458,7 +460,8 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal pageProvider: null, modelProvider: null, razorPageFactoryProvider: pageFactory.Object, - razorProject: razorProject); + razorProject: razorProject, + razorPagesOptions: new RazorPagesOptions { RootDirectory = "/" }); var compiledDescriptor = CreateCompiledPageActionDescriptor(descriptor); diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageFilterApplicationModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageFilterApplicationModelProviderTest.cs new file mode 100644 index 0000000000..ddb169f2b0 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageFilterApplicationModelProviderTest.cs @@ -0,0 +1,41 @@ +// 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 Microsoft.AspNetCore.Mvc.ApplicationModels; +using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal +{ + public class PageFilterApplicationModelProviderTest + { + [Fact] + public void OnProvidersExecuting_AddsFiltersToModels() + { + // Arrange + var applicationModel1 = new PageApplicationModel("/Home.cshtml", "/Home.cshtml"); + var applicationModel2 = new PageApplicationModel("/About.cshtml", "/About.cshtml"); + var modelProvider = new PageFilterApplicationModelProvider(); + var context = new PageApplicationModelProviderContext + { + Results = + { + applicationModel1, + applicationModel2, + } + }; + + // Act + modelProvider.OnProvidersExecuting(context); + + // Assert + Assert.Collection(applicationModel1.Filters, + filter => Assert.IsType(filter), + filter => Assert.IsType(filter)); + + Assert.Collection(applicationModel2.Filters, + filter => Assert.IsType(filter), + filter => Assert.IsType(filter)); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesOptionsSetupTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesOptionsSetupTest.cs deleted file mode 100644 index 80980b3c14..0000000000 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorPagesOptionsSetupTest.cs +++ /dev/null @@ -1,47 +0,0 @@ -// 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 Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.ViewFeatures.Internal; -using Xunit; - -namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal -{ - public class RazorPagesOptionsSetupTest - { - [Fact] - public void Configure_AddsGlobalFilters() - { - // Arrange - var options = new RazorPagesOptions(); - var setup = new RazorPagesOptionsSetup(); - var applicationModel = new PageApplicationModel("/Home.cshtml", "/Home.cshtml"); - - // Act - setup.Configure(options); - foreach (var convention in options.Conventions) - { - convention.Apply(applicationModel); - } - - // Assert - Assert.Collection(applicationModel.Filters, - filter => Assert.IsType(filter), - filter => Assert.IsType(filter)); - } - - [Fact] - public void Configure_SetsRazorPagesRoot() - { - // Arrange - var options = new RazorPagesOptions(); - var setup = new RazorPagesOptionsSetup(); - - // Act - setup.Configure(options); - - // Assert - Assert.Equal("/Pages", options.RootDirectory); - } - } -} diff --git a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs index 62f693343e..4e6fbeb7fb 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/MvcServiceCollectionExtensionsTest.cs @@ -364,13 +364,6 @@ namespace Microsoft.AspNetCore.Mvc typeof(RazorPagesRazorViewEngineOptionsSetup), } }, - { - typeof(IConfigureOptions), - new[] - { - typeof(RazorPagesOptionsSetup), - } - }, { typeof(IActionConstraintProvider), new Type[] @@ -433,6 +426,7 @@ namespace Microsoft.AspNetCore.Mvc { typeof(CompiledPageApplicationModelProvider), typeof(RazorProjectPageApplicationModelProvider), + typeof(PageFilterApplicationModelProvider), } }, };