diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs index 2ffe77c41b..deb53ee82d 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageApplicationModelConvention.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels /// /// Allows customization of the of the . /// - public interface IPageApplicationModelConvention + public interface IPageApplicationModelConvention : IPageConvention { /// /// Called to apply the convention to the . diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs new file mode 100644 index 0000000000..3b23349536 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageConvention.cs @@ -0,0 +1,12 @@ +// 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. + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + /// + /// Common interface for route and application model conventions that apply to Razor Pages. + /// + public interface IPageConvention + { + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs index cfa332a465..7d54b8bcd7 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/IPageRouteModelConvention.cs @@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels /// /// Allows customization of the of the . /// - public interface IPageRouteModelConvention + public interface IPageRouteModelConvention : IPageConvention { /// /// Called to apply the convention to the . diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs new file mode 100644 index 0000000000..51c36fcad0 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/ApplicationModels/PageConventionCollection.cs @@ -0,0 +1,222 @@ +// 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.ObjectModel; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class PageConventionCollection : Collection + { + /// + /// Creates and adds an that invokes an action on the + /// for the page with the speciifed name. + /// + /// The name of the page e.g. /Users/List + /// The . + /// The added . + public IPageApplicationModelConvention AddPageApplicationModelConvention( + string pageName, + Action action) + { + EnsureValidPageName(pageName); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new PageApplicationModelConvention(pageName, action)); + } + + /// + /// Creates and adds an that invokes an action on + /// instances for all page under the specified folder. + /// + /// The path of the folder relative to the Razor Pages root. e.g. /Users/ + /// The . + /// The added . + public IPageApplicationModelConvention AddFolderApplicationModelConvention(string folderPath, Action action) + { + EnsureValidFolderPath(folderPath); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new FolderApplicationModelConvention(folderPath, action)); + } + + /// + /// Creates and adds an that invokes an action on the + /// for the page with the speciifed name. + /// + /// The name of the page e.g. /Users/List + /// The . + /// The added . + public IPageRouteModelConvention AddPageRouteModelConvention(string pageName, Action action) + { + EnsureValidPageName(pageName); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new PageRouteModelConvention(pageName, action)); + } + + /// + /// Creates and adds an that invokes an action on + /// instances for all page under the specified folder. + /// + /// The path of the folder relative to the Razor Pages root. e.g. /Users/ + /// The . + /// The added . + public IPageRouteModelConvention AddFolderRouteModelConvention(string folderPath, Action action) + { + EnsureValidFolderPath(folderPath); + + if (action == null) + { + throw new ArgumentNullException(nameof(action)); + } + + return Add(new FolderRouteModelConvention(folderPath, action)); + } + + // Internal for unit testing + internal static void EnsureValidPageName(string pageName) + { + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + if (pageName[0] != '/' || pageName.EndsWith(".cshtml", StringComparison.OrdinalIgnoreCase)) + { + throw new ArgumentException(Resources.FormatInvalidValidPageName(pageName), nameof(pageName)); + } + } + + // Internal for unit testing + internal static void EnsureValidFolderPath(string folderPath) + { + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + if (folderPath[0] != '/') + { + throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(folderPath)); + } + } + + private TConvention Add(TConvention convention) where TConvention: IPageConvention + { + base.Add(convention); + return convention; + } + + private class PageRouteModelConvention : IPageRouteModelConvention + { + private readonly string _path; + private readonly Action _action; + + public PageRouteModelConvention(string path, Action action) + { + _path = path; + _action = action; + } + + public void Apply(PageRouteModel model) + { + if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase)) + { + _action(model); + } + } + } + + private class FolderRouteModelConvention : IPageRouteModelConvention + { + private readonly string _folderPath; + private readonly Action _action; + + public FolderRouteModelConvention(string folderPath, Action action) + { + _folderPath = folderPath.TrimEnd('/'); + _action = action; + } + + public void Apply(PageRouteModel model) + { + var viewEnginePath = model.ViewEnginePath; + + if (PathBelongsToFolder(_folderPath, viewEnginePath)) + { + _action(model); + } + } + } + + private class PageApplicationModelConvention : IPageApplicationModelConvention + { + private readonly string _path; + private readonly Action _action; + + public PageApplicationModelConvention(string path, Action action) + { + _path = path; + _action = action; + } + + public void Apply(PageApplicationModel model) + { + if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase)) + { + _action(model); + } + } + } + + private class FolderApplicationModelConvention : IPageApplicationModelConvention + { + private readonly string _folderPath; + private readonly Action _action; + + public FolderApplicationModelConvention(string folderPath, Action action) + { + _folderPath = folderPath.TrimEnd('/'); + _action = action; + } + + public void Apply(PageApplicationModel model) + { + var viewEnginePath = model.ViewEnginePath; + + if (PathBelongsToFolder(_folderPath, viewEnginePath)) + { + _action(model); + } + } + } + + // Internal for unit testing + internal static bool PathBelongsToFolder(string folderPath, string viewEnginePath) + { + if (folderPath == "/") + { + // Root directory covers everything. + return true; + } + + return viewEnginePath.Length > folderPath.Length && + viewEnginePath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase) && + viewEnginePath[folderPath.Length] == '/'; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs index 0655c3bb7d..6340d87005 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/MvcRazorPagesMvcBuilderExtensions.cs @@ -57,7 +57,7 @@ namespace Microsoft.Extensions.DependencyInjection if (rootDirectory[0] != '/') { - throw new ArgumentException(Resources.PathMustBeAnAppRelativePath, nameof(rootDirectory)); + throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(rootDirectory)); } builder.Services.Configure(options => options.RootDirectory = rootDirectory); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs new file mode 100644 index 0000000000..b04072f217 --- /dev/null +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/PageConventionCollectionExtensions.cs @@ -0,0 +1,222 @@ +// 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.Authorization; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Microsoft.Extensions.DependencyInjection +{ + /// + /// Extensions for . + /// + public static class PageConventionCollectionExtensions + { + /// + /// Configures the specified to apply filters to all Razor Pages. + /// + /// The to configure. + /// The factory to create filters. + /// + public static IPageApplicationModelConvention ConfigureFilter( + this PageConventionCollection conventions, + Func factory) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (factory == null) + { + throw new ArgumentNullException(nameof(factory)); + } + + return conventions.AddFolderApplicationModelConvention("/", model => model.Filters.Add(factory(model))); + } + + /// + /// Configures the specified to apply to all Razor Pages. + /// + /// The to configure. + /// The to add. + /// The . + public static PageConventionCollection ConfigureFilter(this PageConventionCollection conventions, IFilterMetadata filter) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (filter == null) + { + throw new ArgumentNullException(nameof(filter)); + } + + conventions.AddFolderApplicationModelConvention("/", model => model.Filters.Add(filter)); + return conventions; + } + + /// + /// Adds a to the page with the specified name. + /// + /// The to configure. + /// The page name. + /// The . + public static PageConventionCollection AllowAnonymousToPage(this PageConventionCollection conventions, string pageName) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + conventions.AddPageApplicationModelConvention(pageName, model => model.Filters.Add(anonymousFilter)); + return conventions; + } + + /// + /// Adds a to all pages under the specified folder. + /// + /// The to configure. + /// The folder path. + /// The . + public static PageConventionCollection AllowAnonymousToFolder(this PageConventionCollection conventions, string folderPath) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + var anonymousFilter = new AllowAnonymousFilter(); + conventions.AddFolderApplicationModelConvention(folderPath, model => model.Filters.Add(anonymousFilter)); + return conventions; + } + + /// + /// Adds a with the specified policy to the page with the specified name. + /// + /// The to configure. + /// The page name. + /// The authorization policy. + /// The . + public static PageConventionCollection AuthorizePage(this PageConventionCollection conventions, string pageName, string policy) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + var authorizeFilter = new AuthorizeFilter(policy); + conventions.AddPageApplicationModelConvention(pageName, model => model.Filters.Add(authorizeFilter)); + return conventions; + } + + /// + /// Adds a to the page with the specified name. + /// + /// The to configure. + /// The page name. + /// The . + public static PageConventionCollection AuthorizePage(this PageConventionCollection conventions, string pageName) => + AuthorizePage(conventions, pageName, policy: string.Empty); + + /// + /// Adds a with the specified policy to all pages under the specified folder. + /// + /// The to configure. + /// The folder path. + /// The authorization policy. + /// The . + public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath, string policy) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(folderPath)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); + } + + var authorizeFilter = new AuthorizeFilter(policy); + conventions.AddFolderApplicationModelConvention(folderPath, model => model.Filters.Add(authorizeFilter)); + return conventions; + } + + /// + /// Adds a to all pages under the specified folder. + /// + /// The to configure. + /// The folder path. + /// The . + public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath) => + AuthorizeFolder(conventions, folderPath, policy: string.Empty); + + /// + /// Adds the specified to the page at the specified . + /// + /// The page can be routed via in addition to the default set of path based routes. + /// All links generated for this page will use the specified route. + /// + /// + /// The . + /// The page name. + /// The route to associate with the page. + /// The . + public static PageConventionCollection AddPageRoute(this PageConventionCollection conventions, string pageName, string route) + { + if (conventions == null) + { + throw new ArgumentNullException(nameof(conventions)); + } + + if (string.IsNullOrEmpty(pageName)) + { + throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); + } + + if (route == null) + { + throw new ArgumentNullException(nameof(route)); + } + + conventions.AddPageRouteModelConvention(pageName, model => + { + // Use the route specified in MapPageRoute for outbound routing. + foreach (var selector in model.Selectors) + { + selector.AttributeRouteModel.SuppressLinkGeneration = true; + } + + model.Selectors.Add(new SelectorModel + { + AttributeRouteModel = new AttributeRouteModel + { + Template = route, + } + }); + }); + + return conventions; + } + } +} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs deleted file mode 100644 index bbc9582f9e..0000000000 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs +++ /dev/null @@ -1,318 +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.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Authorization; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Microsoft.Extensions.DependencyInjection -{ - /// - /// Extensions for . - /// - public static class RazorPagesOptionsExtensions - { - /// - /// Configures the specified to apply filters to all Razor Pages. - /// - /// The to configure. - /// The factory to create filters. - /// - public static RazorPagesOptions ConfigureFilter(this RazorPagesOptions options, Func factory) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (factory == null) - { - throw new ArgumentNullException(nameof(factory)); - } - - options.ApplicationModelConventions.Add(new FolderApplicationModelConvention("/", model => model.Filters.Add(factory(model)))); - return options; - } - - /// - /// Configures the specified to apply to all Razor Pages. - /// - /// The to configure. - /// The to add. - /// The . - public static RazorPagesOptions ConfigureFilter(this RazorPagesOptions options, IFilterMetadata filter) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (filter == null) - { - throw new ArgumentNullException(nameof(filter)); - } - - options.ApplicationModelConventions.Add(new FolderApplicationModelConvention("/", model => model.Filters.Add(filter))); - return options; - } - - /// - /// Adds a to the page with the specified name. - /// - /// The to configure. - /// The page name. - /// The . - public static RazorPagesOptions AllowAnonymousToPage(this RazorPagesOptions options, string pageName) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (string.IsNullOrEmpty(pageName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); - } - - var anonymousFilter = new AllowAnonymousFilter(); - options.ApplicationModelConventions.Add(new PageApplicationModelConvention(pageName, model => model.Filters.Add(anonymousFilter))); - return options; - } - - /// - /// Adds a to all pages under the specified folder. - /// - /// The to configure. - /// The folder path. - /// The . - public static RazorPagesOptions AllowAnonymousToFolder(this RazorPagesOptions options, string folderPath) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (string.IsNullOrEmpty(folderPath)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); - } - - var anonymousFilter = new AllowAnonymousFilter(); - options.ApplicationModelConventions.Add(new FolderApplicationModelConvention(folderPath, model => model.Filters.Add(anonymousFilter))); - return options; - } - - /// - /// Adds a with the specified policy to the page with the specified name. - /// - /// The to configure. - /// The page name. - /// The authorization policy. - /// The . - public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string pageName, string policy) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (string.IsNullOrEmpty(pageName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); - } - - var authorizeFilter = new AuthorizeFilter(policy); - options.ApplicationModelConventions.Add(new PageApplicationModelConvention(pageName, model => model.Filters.Add(authorizeFilter))); - return options; - } - - /// - /// Adds a to the page with the specified name. - /// - /// The to configure. - /// The page name. - /// The . - public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string pageName) => - AuthorizePage(options, pageName, policy: string.Empty); - - /// - /// Adds a with the specified policy to all pages under the specified folder. - /// - /// The to configure. - /// The folder path. - /// The authorization policy. - /// The . - public static RazorPagesOptions AuthorizeFolder(this RazorPagesOptions options, string folderPath, string policy) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (string.IsNullOrEmpty(folderPath)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(folderPath)); - } - - var authorizeFilter = new AuthorizeFilter(policy); - options.ApplicationModelConventions.Add(new FolderApplicationModelConvention(folderPath, model => model.Filters.Add(authorizeFilter))); - return options; - } - - /// - /// Adds a to all pages under the specified folder. - /// - /// The to configure. - /// The folder path. - /// The . - public static RazorPagesOptions AuthorizeFolder(this RazorPagesOptions options, string folderPath) => - AuthorizeFolder(options, folderPath, policy: string.Empty); - - /// - /// Adds the specified to the page at the specified . - /// - /// The page can be routed via in addition to the default set of path based routes. - /// All links generated for this page will use the specified route. - /// - /// - /// The . - /// The page name. - /// The route to associate with the page. - /// The . - public static RazorPagesOptions AddPageRoute(this RazorPagesOptions options, string pageName, string route) - { - if (options == null) - { - throw new ArgumentNullException(nameof(options)); - } - - if (string.IsNullOrEmpty(pageName)) - { - throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - - options.RouteModelConventions.Add(new PageRouteModelConvention(pageName, model => - { - // Use the route specified in MapPageRoute for outbound routing. - foreach (var selector in model.Selectors) - { - selector.AttributeRouteModel.SuppressLinkGeneration = true; - } - - model.Selectors.Add(new SelectorModel - { - AttributeRouteModel = new AttributeRouteModel - { - Template = route, - } - }); - })); - - return options; - } - - private class PageRouteModelConvention : IPageRouteModelConvention - { - private readonly string _path; - private readonly Action _action; - - public PageRouteModelConvention(string path, Action action) - { - _path = path; - _action = action; - } - - public void Apply(PageRouteModel model) - { - if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase)) - { - _action(model); - } - } - } - - private class FolderRouteModelConvention : IPageRouteModelConvention - { - private readonly string _folderPath; - private readonly Action _action; - - public FolderRouteModelConvention(string folderPath, Action action) - { - _folderPath = folderPath.TrimEnd('/'); - _action = action; - } - - public void Apply(PageRouteModel model) - { - var viewEnginePath = model.ViewEnginePath; - - if (PathBelongsToFolder(_folderPath, viewEnginePath)) - { - _action(model); - } - } - } - - private class PageApplicationModelConvention : IPageApplicationModelConvention - { - private readonly string _path; - private readonly Action _action; - - public PageApplicationModelConvention(string path, Action action) - { - _path = path; - _action = action; - } - - public void Apply(PageApplicationModel model) - { - if (string.Equals(model.ViewEnginePath, _path, StringComparison.OrdinalIgnoreCase)) - { - _action(model); - } - } - } - - private class FolderApplicationModelConvention : IPageApplicationModelConvention - { - private readonly string _folderPath; - private readonly Action _action; - - public FolderApplicationModelConvention(string folderPath, Action action) - { - _folderPath = folderPath.TrimEnd('/'); - _action = action; - } - - public void Apply(PageApplicationModel model) - { - var viewEnginePath = model.ViewEnginePath; - - if (PathBelongsToFolder(_folderPath, viewEnginePath)) - { - _action(model); - } - } - } - - private static bool PathBelongsToFolder(string folderPath, string viewEnginePath) - { - if (folderPath == "/") - { - // Root directory covers everything. - return true; - } - - return viewEnginePath.Length > folderPath.Length && - viewEnginePath.StartsWith(folderPath, StringComparison.OrdinalIgnoreCase) && - viewEnginePath[folderPath.Length] == '/'; - } - } -} diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs index 0c4970b5cf..5c02a67644 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Infrastructure/PageActionDescriptorProvider.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { private readonly IPageRouteModelProvider[] _routeModelProviders; private readonly MvcOptions _mvcOptions; - private readonly RazorPagesOptions _pagesOptions; + private readonly IPageRouteModelConvention[] _conventions; public PageActionDescriptorProvider( IEnumerable pageRouteModelProviders, @@ -25,7 +25,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure { _routeModelProviders = pageRouteModelProviders.OrderBy(p => p.Order).ToArray(); _mvcOptions = mvcOptionsAccessor.Value; - _pagesOptions = pagesOptionsAccessor.Value; + + _conventions = pagesOptionsAccessor.Value.Conventions + .OfType() + .ToArray(); } public int Order { get; set; } = -900; // Run after the default MVC provider, but before others. @@ -63,9 +66,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure private void AddActionDescriptors(IList actions, PageRouteModel model) { - for (var i = 0; i < _pagesOptions.RouteModelConventions.Count; i++) + for (var i = 0; i < _conventions.Length; i++) { - _pagesOptions.RouteModelConventions[i].Apply(model); + _conventions[i].Apply(model); } foreach (var selector in model.Selectors) diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs index f0c588e932..bfce828103 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/DefaultPageLoader.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { private readonly IPageApplicationModelProvider[] _applicationModelProviders; private readonly IViewCompilerProvider _viewCompilerProvider; - private readonly RazorPagesOptions _options; + private readonly IPageApplicationModelConvention[] _conventions; public DefaultPageLoader( IEnumerable applicationModelProviders, @@ -25,7 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { _applicationModelProviders = applicationModelProviders.ToArray(); _viewCompilerProvider = viewCompilerProvider; - _options = pageOptions.Value; + _conventions = pageOptions.Value.Conventions + .OfType() + .ToArray(); } private IViewCompiler Compiler => _viewCompilerProvider.GetCompiler(); @@ -52,9 +54,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal _applicationModelProviders[i].OnProvidersExecuted(context); } - for (var i = 0; i < _options.ApplicationModelConventions.Count; i++) + for (var i = 0; i < _conventions.Length; i++) { - _options.ApplicationModelConventions[i].Apply(context.PageApplicationModel); + _conventions[i].Apply(context.PageApplicationModel); } return CompiledPageActionDescriptorBuilder.Build(context.PageApplicationModel); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs index 4e9aa8a0d3..c2cd73be9f 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Properties/Resources.Designer.cs @@ -109,18 +109,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages => string.Format(CultureInfo.CurrentCulture, GetString("AmbiguousHandler"), p0, p1); /// - /// Path must be an application relative path that starts with a forward slash '/'. + /// Path must be a root relative path that starts with a forward slash '/'. /// - internal static string PathMustBeAnAppRelativePath + internal static string PathMustBeRootRelativePath { - get => GetString("PathMustBeAnAppRelativePath"); + get => GetString("PathMustBeRootRelativePath"); } /// - /// Path must be an application relative path that starts with a forward slash '/'. + /// Path must be a root relative path that starts with a forward slash '/'. /// - internal static string FormatPathMustBeAnAppRelativePath() - => GetString("PathMustBeAnAppRelativePath"); + internal static string FormatPathMustBeRootRelativePath() + => GetString("PathMustBeRootRelativePath"); /// /// If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. @@ -164,6 +164,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages internal static string FormatInvalidPageType_NoModelProperty(object p0, object p1) => string.Format(CultureInfo.CurrentCulture, GetString("InvalidPageType_NoModelProperty"), p0, p1); + /// + /// '{0}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g "/Users/Edit". + /// + internal static string InvalidValidPageName + { + get => GetString("InvalidValidPageName"); + } + + /// + /// '{0}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g "/Users/Edit". + /// + internal static string FormatInvalidValidPageName(object p0) + => string.Format(CultureInfo.CurrentCulture, GetString("InvalidValidPageName"), p0); + private static string GetString(string name, params string[] formatterNames) { var value = _resourceManager.GetString(name); diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs index e562414a7a..8bb833d4fc 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/RazorPagesOptions.cs @@ -4,8 +4,6 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Mvc.ApplicationModels; -using Microsoft.AspNetCore.Mvc.Filters; -using Microsoft.AspNetCore.Mvc.RazorPages.Internal; namespace Microsoft.AspNetCore.Mvc.RazorPages { @@ -17,16 +15,10 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages private string _root = "/Pages"; /// - /// Gets a list of instances that will be applied to - /// the when discovering Razor Pages. + /// Gets a collection of instances that are applied during + /// route and page model construction. /// - public IList RouteModelConventions { get; } = new List(); - - /// - /// Gets a list of instances that will be applied to - /// the when discovering Razor Pages. - /// - public IList ApplicationModelConventions { get; } = new List(); + public PageConventionCollection Conventions { get; } = new PageConventionCollection(); /// /// Application relative path used as the root of discovery for Razor Page files. @@ -44,7 +36,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages if (value[0] != '/') { - throw new ArgumentException(Resources.PathMustBeAnAppRelativePath, nameof(value)); + throw new ArgumentException(Resources.PathMustBeRootRelativePath, nameof(value)); } _root = value; diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx b/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx index 6b80508128..be0f94f186 100644 --- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx +++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx @@ -138,8 +138,8 @@ Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1} - - Path must be an application relative path that starts with a forward slash '/'. + + Path must be a root relative path that starts with a forward slash '/'. If an {0} provides a result value by setting the {1} property of {2} to a non-null value, then it cannot call the next filter by invoking {3}. @@ -150,4 +150,7 @@ The type '{0}' is not a valid page. A page must define a public, non-static '{1}' property. + + '{0}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g "/Users/Edit". + \ No newline at end of file diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageConventionCollectionTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageConventionCollectionTest.cs new file mode 100644 index 0000000000..ee6b4603a1 --- /dev/null +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/ApplicationModels/PageConventionCollectionTest.cs @@ -0,0 +1,76 @@ +// 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.Testing; +using Xunit; + +namespace Microsoft.AspNetCore.Mvc.ApplicationModels +{ + public class PageConventionCollectionTest + { + [Theory] + [InlineData(null)] + [InlineData("")] + public void EnsureValidPageName_ThrowsIfPageNameIsNullOrEmpty(string pageName) + { + // Act & Assert + var ex = ExceptionAssert.ThrowsArgument( + () => PageConventionCollection.EnsureValidPageName(pageName), + "pageName", + "Value cannot be null or empty."); + } + + [Theory] + [InlineData("path-without-slash")] + [InlineData(@"c:\myapp\path-without-slash")] + public void EnsureValidPageName_ThrowsIfPageNameDoesNotStartWithLeadingSlash(string pageName) + { + // Arrange + var expected = $"'{pageName}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g \"/Users/Edit\"."; + // Act & Assert + var ex = ExceptionAssert.ThrowsArgument( + () => PageConventionCollection.EnsureValidPageName(pageName), + "pageName", + expected); + } + + [Fact] + public void EnsureValidPageName_ThrowsIfPageNameHasExtension() + { + // Arrange + var pageName = "/Page.cshtml"; + var expected = $"'{pageName}' is not a valid page name. A page name is path relative to the Razor Pages root directory that starts with a leading forward slash ('/') and does not contain the file extension e.g \"/Users/Edit\"."; + // Act & Assert + var ex = ExceptionAssert.ThrowsArgument( + () => PageConventionCollection.EnsureValidPageName(pageName), + "pageName", + expected); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + public void EnsureValidFolderPath_ThrowsIfPathIsNullOrEmpty(string folderPath) + { + // Arrange + // Act & Assert + var ex = ExceptionAssert.ThrowsArgument( + () => PageConventionCollection.EnsureValidFolderPath(folderPath), + "folderPath", + "Value cannot be null or empty."); + } + + [Theory] + [InlineData("path-without-slash")] + [InlineData(@"c:\myapp\path-without-slash")] + public void EnsureValidFolderPath_ThrowsIfPageNameDoesNotStartWithLeadingSlash(string folderPath) + { + // Arrange + // Act & Assert + var ex = ExceptionAssert.ThrowsArgument( + () => PageConventionCollection.EnsureValidFolderPath(folderPath), + "folderPath", + "Path must be a root relative path that starts with a forward slash '/'."); + } + } +} diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs index 0d57132c92..4b209a45fd 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/MvcRazorPagesMvcBuilderExtensionsTest.cs @@ -14,7 +14,7 @@ namespace Microsoft.Extensions.DependencyInjection public class MvcRazorPagesMvcBuilderExtensionsTest { [Fact] - public void AddRazorPagesOptions_AddsApplicationModelConventions() + public void AddRazorPagesOptions_AddsConventions() { // Arrange var services = new ServiceCollection().AddOptions(); @@ -23,18 +23,20 @@ namespace Microsoft.Extensions.DependencyInjection var builder = new MvcBuilder(services, new ApplicationPartManager()); builder.AddRazorPagesOptions(options => { - options.ApplicationModelConventions.Add(applicationModelConvention); - options.RouteModelConventions.Add(routeModelConvention); + options.Conventions.Add(applicationModelConvention); + options.Conventions.Add(routeModelConvention); }); var serviceProvider = services.BuildServiceProvider(); var accessor = serviceProvider.GetRequiredService>(); // Act & Assert - var conventions = accessor.Value.ApplicationModelConventions; + var conventions = accessor.Value.Conventions; // Assert - Assert.Collection(conventions, - convention => Assert.Same(applicationModelConvention, convention)); + Assert.Collection( + conventions, + convention => Assert.Same(applicationModelConvention, convention), + convention => Assert.Same(routeModelConvention, convention)); } } } diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/PageConventionCollectionExtensionsTest.cs similarity index 76% rename from test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs rename to test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/PageConventionCollectionExtensionsTest.cs index 99d45ad3b0..b6ea5e394e 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/PageConventionCollectionExtensionsTest.cs @@ -1,6 +1,7 @@ // 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.Linq; using System.Reflection; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc.ApplicationModels; @@ -12,24 +13,24 @@ using Xunit; namespace Microsoft.Extensions.DependencyInjection { - public class RazorPagesOptionsExtensionsTest + public class PageConventionCollectionExtensionsTest { [Fact] public void AddFilter_AddsFiltersToAllPages() { // Arrange var filter = Mock.Of(); - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { - CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), - CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), - CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + CreateApplicationModel("/Pages/Index.cshtml", "/Index"), + CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"), + CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"), }; // Act - options.ConfigureFilter(filter); - ApplyConventions(options, models); + conventions.ConfigureFilter(filter); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, @@ -42,30 +43,30 @@ namespace Microsoft.Extensions.DependencyInjection public void AuthorizePage_AddsAllowAnonymousFilterToSpecificPage() { // Arrange - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { - CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), - CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), - CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + CreateApplicationModel("/Pages/Index.cshtml", "/Index"), + CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"), + CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"), }; // Act - options.AuthorizeFolder("/Users"); - options.AllowAnonymousToPage("/Users/Contact.cshtml"); - ApplyConventions(options, models); + conventions.AuthorizeFolder("/Users"); + conventions.AllowAnonymousToPage("/Users/Contact"); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, model => Assert.Empty(model.Filters), model => { - Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Account", model.ViewEnginePath); Assert.IsType(Assert.Single(model.Filters)); }, model => { - Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Contact", model.ViewEnginePath); Assert.IsType(model.Filters[0]); Assert.IsType(model.Filters[1]); }); @@ -77,35 +78,35 @@ namespace Microsoft.Extensions.DependencyInjection public void AuthorizePage_AddsAllowAnonymousFilterToPagesUnderFolder(string folderName) { // Arrange - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { - CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), - CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), - CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + CreateApplicationModel("/Pages/Index.cshtml", "/Index"), + CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"), + CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"), }; // Act - options.AuthorizeFolder("/"); - options.AllowAnonymousToFolder("/Users"); - ApplyConventions(options, models); + conventions.AuthorizeFolder("/"); + conventions.AllowAnonymousToFolder("/Users"); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, model => { - Assert.Equal("/Index.cshtml", model.ViewEnginePath); + Assert.Equal("/Index", model.ViewEnginePath); Assert.IsType(Assert.Single(model.Filters)); }, model => { - Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Account", model.ViewEnginePath); Assert.IsType(model.Filters[0]); Assert.IsType(model.Filters[1]); }, model => { - Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Contact", model.ViewEnginePath); Assert.IsType(model.Filters[0]); Assert.IsType(model.Filters[1]); }); @@ -115,24 +116,24 @@ namespace Microsoft.Extensions.DependencyInjection public void AuthorizePage_AddsAuthorizeFilterWithPolicyToSpecificPage() { // Arrange - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { - CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), - CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), - CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + CreateApplicationModel("/Pages/Index.cshtml", "/Index"), + CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"), + CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"), }; // Act - options.AuthorizePage("/Users/Account.cshtml", "Manage-Accounts"); - ApplyConventions(options, models); + conventions.AuthorizePage("/Users/Account", "Manage-Accounts"); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, model => Assert.Empty(model.Filters), model => { - Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Account", model.ViewEnginePath); var authorizeFilter = Assert.IsType(Assert.Single(model.Filters)); var authorizeData = Assert.IsType(Assert.Single(authorizeFilter.AuthorizeData)); Assert.Equal("Manage-Accounts", authorizeData.Policy); @@ -144,24 +145,24 @@ namespace Microsoft.Extensions.DependencyInjection public void AuthorizePage_AddsAuthorizeFilterWithoutPolicyToSpecificPage() { // Arrange - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { - CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), - CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), - CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + CreateApplicationModel("/Pages/Index.cshtml", "/Index"), + CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"), + CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"), }; // Act - options.AuthorizePage("/Users/Account.cshtml"); - ApplyConventions(options, models); + conventions.AuthorizePage("/Users/Account"); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, model => Assert.Empty(model.Filters), model => { - Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Account", model.ViewEnginePath); var authorizeFilter = Assert.IsType(Assert.Single(model.Filters)); var authorizeData = Assert.IsType(Assert.Single(authorizeFilter.AuthorizeData)); Assert.Equal(string.Empty, authorizeData.Policy); @@ -175,31 +176,31 @@ namespace Microsoft.Extensions.DependencyInjection public void AuthorizePage_AddsAuthorizeFilterWithPolicyToPagesUnderFolder(string folderName) { // Arrange - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { - CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), - CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), - CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + CreateApplicationModel("/Pages/Index.cshtml", "/Index"), + CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"), + CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"), }; // Act - options.AuthorizeFolder(folderName, "Manage-Accounts"); - ApplyConventions(options, models); + conventions.AuthorizeFolder(folderName, "Manage-Accounts"); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, model => Assert.Empty(model.Filters), model => { - Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Account", model.ViewEnginePath); var authorizeFilter = Assert.IsType(Assert.Single(model.Filters)); var authorizeData = Assert.IsType(Assert.Single(authorizeFilter.AuthorizeData)); Assert.Equal("Manage-Accounts", authorizeData.Policy); }, model => { - Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Contact", model.ViewEnginePath); var authorizeFilter = Assert.IsType(Assert.Single(model.Filters)); var authorizeData = Assert.IsType(Assert.Single(authorizeFilter.AuthorizeData)); Assert.Equal("Manage-Accounts", authorizeData.Policy); @@ -212,31 +213,31 @@ namespace Microsoft.Extensions.DependencyInjection public void AuthorizePage_AddsAuthorizeFilterWithoutPolicyToPagesUnderFolder(string folderName) { // Arrange - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { CreateApplicationModel("/Pages/Index.cshtml", "/Index.cshtml"), - CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account.cshtml"), - CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact.cshtml"), + CreateApplicationModel("/Pages/Users/Account.cshtml", "/Users/Account"), + CreateApplicationModel("/Pages/Users/Contact.cshtml", "/Users/Contact"), }; // Act - options.AuthorizeFolder(folderName); - ApplyConventions(options, models); + conventions.AuthorizeFolder(folderName); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, model => Assert.Empty(model.Filters), model => { - Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Account", model.ViewEnginePath); var authorizeFilter = Assert.IsType(Assert.Single(model.Filters)); var authorizeData = Assert.IsType(Assert.Single(authorizeFilter.AuthorizeData)); Assert.Equal(string.Empty, authorizeData.Policy); }, model => { - Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath); + Assert.Equal("/Users/Contact", model.ViewEnginePath); var authorizeFilter = Assert.IsType(Assert.Single(model.Filters)); var authorizeData = Assert.IsType(Assert.Single(authorizeFilter.AuthorizeData)); Assert.Equal(string.Empty, authorizeData.Policy); @@ -247,10 +248,10 @@ namespace Microsoft.Extensions.DependencyInjection public void AddPageRoute_AddsRouteToSelector() { // Arrange - var options = new RazorPagesOptions(); + var conventions = new PageConventionCollection(); var models = new[] { - new PageRouteModel("/Pages/Index.cshtml", "/Index.cshtml") + new PageRouteModel("/Pages/Index.cshtml", "/Index") { Selectors = { @@ -258,7 +259,7 @@ namespace Microsoft.Extensions.DependencyInjection CreateSelectorModel(""), } }, - new PageRouteModel("/Pages/About.cshtml", "/About.cshtml") + new PageRouteModel("/Pages/About.cshtml", "/About") { Selectors = { @@ -268,14 +269,14 @@ namespace Microsoft.Extensions.DependencyInjection }; // Act - options.AddPageRoute("/Index.cshtml", "Different-Route"); - ApplyConventions(options, models); + conventions.AddPageRoute("/Index", "Different-Route"); + ApplyConventions(conventions, models); // Assert Assert.Collection(models, model => { - Assert.Equal("/Index.cshtml", model.ViewEnginePath); + Assert.Equal("/Index", model.ViewEnginePath); Assert.Collection(model.Selectors, selector => { @@ -295,7 +296,7 @@ namespace Microsoft.Extensions.DependencyInjection }, model => { - Assert.Equal("/About.cshtml", model.ViewEnginePath); + Assert.Equal("/About", model.ViewEnginePath); Assert.Collection(model.Selectors, selector => { @@ -317,9 +318,9 @@ namespace Microsoft.Extensions.DependencyInjection }; } - private static void ApplyConventions(RazorPagesOptions options, PageRouteModel[] models) + private static void ApplyConventions(PageConventionCollection conventions, PageRouteModel[] models) { - foreach (var convention in options.RouteModelConventions) + foreach (var convention in conventions.OfType()) { foreach (var model in models) { @@ -327,9 +328,9 @@ namespace Microsoft.Extensions.DependencyInjection } } } - private static void ApplyConventions(RazorPagesOptions options, PageApplicationModel[] models) + private static void ApplyConventions(PageConventionCollection conventions, PageApplicationModel[] models) { - foreach (var convention in options.ApplicationModelConventions) + foreach (var convention in conventions.OfType()) { foreach (var model in models) { diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs index 29a7268f97..771fd40055 100644 --- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/DefaultPageLoaderTest.cs @@ -105,7 +105,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal { Assert.Same(model, m); }); - options.Value.ApplicationModelConventions.Add(convention.Object); + options.Value.Conventions.Add(convention.Object); var loader = new DefaultPageLoader( providers, diff --git a/test/WebSites/RazorPagesWebSite/Startup.cs b/test/WebSites/RazorPagesWebSite/Startup.cs index 04e213fdc9..76a0bf7dec 100644 --- a/test/WebSites/RazorPagesWebSite/Startup.cs +++ b/test/WebSites/RazorPagesWebSite/Startup.cs @@ -16,11 +16,11 @@ namespace RazorPagesWebSite .AddCookieTempDataProvider() .AddRazorPagesOptions(options => { - options.AuthorizePage("/HelloWorldWithAuth"); - options.AuthorizeFolder("/Pages/Admin"); - options.AllowAnonymousToPage("/Pages/Admin/Login"); - options.AddPageRoute("/HelloWorldWithRoute", "Different-Route/{text}"); - options.AddPageRoute("/Pages/NotTheRoot", string.Empty); + options.Conventions.AuthorizePage("/HelloWorldWithAuth"); + options.Conventions.AuthorizeFolder("/Pages/Admin"); + options.Conventions.AllowAnonymousToPage("/Pages/Admin/Login"); + options.Conventions.AddPageRoute("/HelloWorldWithRoute", "Different-Route/{text}"); + options.Conventions.AddPageRoute("/Pages/NotTheRoot", string.Empty); }) .WithRazorPagesAtContentRoot(); } diff --git a/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs b/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs index 20af9f043c..040e41597a 100644 --- a/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs +++ b/test/WebSites/RazorPagesWebSite/StartupWithBasePath.cs @@ -16,8 +16,8 @@ namespace RazorPagesWebSite .AddCookieTempDataProvider() .AddRazorPagesOptions(options => { - options.AuthorizePage("/Conventions/Auth"); - options.AuthorizeFolder("/Conventions/AuthFolder"); + options.Conventions.AuthorizePage("/Conventions/Auth"); + options.Conventions.AuthorizeFolder("/Conventions/AuthFolder"); }); }