Move convention extensions from RazorPagesOptions to PageConventionCollectionsExtensions

Fixes #6462
This commit is contained in:
Pranav K 2017-06-29 16:36:26 -07:00
parent 660b7df0e2
commit bc86ea4e47
18 changed files with 660 additions and 429 deletions

View File

@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// <summary>
/// Allows customization of the of the <see cref="PageApplicationModel"/>.
/// </summary>
public interface IPageApplicationModelConvention
public interface IPageApplicationModelConvention : IPageConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="PageApplicationModel"/>.

View File

@ -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
{
/// <summary>
/// Common interface for route and application model conventions that apply to Razor Pages.
/// </summary>
public interface IPageConvention
{
}
}

View File

@ -6,7 +6,7 @@ namespace Microsoft.AspNetCore.Mvc.ApplicationModels
/// <summary>
/// Allows customization of the of the <see cref="PageRouteModel"/>.
/// </summary>
public interface IPageRouteModelConvention
public interface IPageRouteModelConvention : IPageConvention
{
/// <summary>
/// Called to apply the convention to the <see cref="PageRouteModel"/>.

View File

@ -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<IPageConvention>
{
/// <summary>
/// Creates and adds an <see cref="IPageApplicationModelConvention"/> that invokes an action on the
/// <see cref="PageApplicationModel"/> for the page with the speciifed name.
/// </summary>
/// <param name="pageName">The name of the page e.g. <c>/Users/List</c></param>
/// <param name="action">The <see cref="Action"/>.</param>
/// <returns>The added <see cref="IPageApplicationModelConvention"/>.</returns>
public IPageApplicationModelConvention AddPageApplicationModelConvention(
string pageName,
Action<PageApplicationModel> action)
{
EnsureValidPageName(pageName);
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
return Add(new PageApplicationModelConvention(pageName, action));
}
/// <summary>
/// Creates and adds an <see cref="IPageApplicationModelConvention"/> that invokes an action on
/// <see cref="PageApplicationModel"/> instances for all page under the specified folder.
/// </summary>
/// <param name="folderPath">The path of the folder relative to the Razor Pages root. e.g. <c>/Users/</c></param>
/// <param name="action">The <see cref="Action"/>.</param>
/// <returns>The added <see cref="IPageApplicationModelConvention"/>.</returns>
public IPageApplicationModelConvention AddFolderApplicationModelConvention(string folderPath, Action<PageApplicationModel> action)
{
EnsureValidFolderPath(folderPath);
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
return Add(new FolderApplicationModelConvention(folderPath, action));
}
/// <summary>
/// Creates and adds an <see cref="IPageRouteModelConvention"/> that invokes an action on the
/// <see cref="PageRouteModel"/> for the page with the speciifed name.
/// </summary>
/// <param name="pageName">The name of the page e.g. <c>/Users/List</c></param>
/// <param name="action">The <see cref="Action"/>.</param>
/// <returns>The added <see cref="IPageRouteModelConvention"/>.</returns>
public IPageRouteModelConvention AddPageRouteModelConvention(string pageName, Action<PageRouteModel> action)
{
EnsureValidPageName(pageName);
if (action == null)
{
throw new ArgumentNullException(nameof(action));
}
return Add(new PageRouteModelConvention(pageName, action));
}
/// <summary>
/// Creates and adds an <see cref="IPageRouteModelConvention"/> that invokes an action on
/// <see cref="PageRouteModel"/> instances for all page under the specified folder.
/// </summary>
/// <param name="folderPath">The path of the folder relative to the Razor Pages root. e.g. <c>/Users/</c></param>
/// <param name="action">The <see cref="Action"/>.</param>
/// <returns>The added <see cref="IPageApplicationModelConvention"/>.</returns>
public IPageRouteModelConvention AddFolderRouteModelConvention(string folderPath, Action<PageRouteModel> 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>(TConvention convention) where TConvention: IPageConvention
{
base.Add(convention);
return convention;
}
private class PageRouteModelConvention : IPageRouteModelConvention
{
private readonly string _path;
private readonly Action<PageRouteModel> _action;
public PageRouteModelConvention(string path, Action<PageRouteModel> 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<PageRouteModel> _action;
public FolderRouteModelConvention(string folderPath, Action<PageRouteModel> 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<PageApplicationModel> _action;
public PageApplicationModelConvention(string path, Action<PageApplicationModel> 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<PageApplicationModel> _action;
public FolderApplicationModelConvention(string folderPath, Action<PageApplicationModel> 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] == '/';
}
}
}

View File

@ -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<RazorPagesOptions>(options => options.RootDirectory = rootDirectory);

View File

@ -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
{
/// <summary>
/// Extensions for <see cref="PageConventionCollection"/>.
/// </summary>
public static class PageConventionCollectionExtensions
{
/// <summary>
/// Configures the specified <paramref name="factory"/> to apply filters to all Razor Pages.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="factory">The factory to create filters.</param>
/// <returns></returns>
public static IPageApplicationModelConvention ConfigureFilter(
this PageConventionCollection conventions,
Func<PageApplicationModel, IFilterMetadata> 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)));
}
/// <summary>
/// Configures the specified <paramref name="filter"/> to apply to all Razor Pages.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="filter">The <see cref="IFilterMetadata"/> to add.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to the page with the specified name.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="pageName">The page name.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified folder.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to the page with the specified name.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="pageName">The page name.</param>
/// <param name="policy">The authorization policy.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> to the page with the specified name.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="pageName">The page name.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
public static PageConventionCollection AuthorizePage(this PageConventionCollection conventions, string pageName) =>
AuthorizePage(conventions, pageName, policy: string.Empty);
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to all pages under the specified folder.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
/// <param name="policy">The authorization policy.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> to all pages under the specified folder.
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
public static PageConventionCollection AuthorizeFolder(this PageConventionCollection conventions, string folderPath) =>
AuthorizeFolder(conventions, folderPath, policy: string.Empty);
/// <summary>
/// Adds the specified <paramref name="route"/> to the page at the specified <paramref name="pageName"/>.
/// <para>
/// The page can be routed via <paramref name="route"/> in addition to the default set of path based routes.
/// All links generated for this page will use the specified route.
/// </para>
/// </summary>
/// <param name="conventions">The <see cref="PageConventionCollection"/>.</param>
/// <param name="pageName">The page name.</param>
/// <param name="route">The route to associate with the page.</param>
/// <returns>The <see cref="PageConventionCollection"/>.</returns>
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;
}
}
}

View File

@ -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
{
/// <summary>
/// Extensions for <see cref="RazorPagesOptions"/>.
/// </summary>
public static class RazorPagesOptionsExtensions
{
/// <summary>
/// Configures the specified <paramref name="factory"/> to apply filters to all Razor Pages.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="factory">The factory to create filters.</param>
/// <returns></returns>
public static RazorPagesOptions ConfigureFilter(this RazorPagesOptions options, Func<PageApplicationModel, IFilterMetadata> 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;
}
/// <summary>
/// Configures the specified <paramref name="filter"/> to apply to all Razor Pages.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="filter">The <see cref="IFilterMetadata"/> to add.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to the page with the specified name.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="pageName">The page name.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AllowAnonymousFilter"/> to all pages under the specified folder.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to the page with the specified name.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="pageName">The page name.</param>
/// <param name="policy">The authorization policy.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> to the page with the specified name.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="pageName">The page name.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string pageName) =>
AuthorizePage(options, pageName, policy: string.Empty);
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> with the specified policy to all pages under the specified folder.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
/// <param name="policy">The authorization policy.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
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;
}
/// <summary>
/// Adds a <see cref="AuthorizeFilter"/> to all pages under the specified folder.
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/> to configure.</param>
/// <param name="folderPath">The folder path.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
public static RazorPagesOptions AuthorizeFolder(this RazorPagesOptions options, string folderPath) =>
AuthorizeFolder(options, folderPath, policy: string.Empty);
/// <summary>
/// Adds the specified <paramref name="route"/> to the page at the specified <paramref name="pageName"/>.
/// <para>
/// The page can be routed via <paramref name="route"/> in addition to the default set of path based routes.
/// All links generated for this page will use the specified route.
/// </para>
/// </summary>
/// <param name="options">The <see cref="RazorPagesOptions"/>.</param>
/// <param name="pageName">The page name.</param>
/// <param name="route">The route to associate with the page.</param>
/// <returns>The <see cref="RazorPagesOptions"/>.</returns>
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<PageRouteModel> _action;
public PageRouteModelConvention(string path, Action<PageRouteModel> 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<PageRouteModel> _action;
public FolderRouteModelConvention(string folderPath, Action<PageRouteModel> 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<PageApplicationModel> _action;
public PageApplicationModelConvention(string path, Action<PageApplicationModel> 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<PageApplicationModel> _action;
public FolderApplicationModelConvention(string folderPath, Action<PageApplicationModel> 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] == '/';
}
}
}

View File

@ -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<IPageRouteModelProvider> 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<IPageRouteModelConvention>()
.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<ActionDescriptor> 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)

View File

@ -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<IPageApplicationModelProvider> applicationModelProviders,
@ -25,7 +25,9 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
_applicationModelProviders = applicationModelProviders.ToArray();
_viewCompilerProvider = viewCompilerProvider;
_options = pageOptions.Value;
_conventions = pageOptions.Value.Conventions
.OfType<IPageApplicationModelConvention>()
.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);

View File

@ -109,18 +109,18 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages
=> string.Format(CultureInfo.CurrentCulture, GetString("AmbiguousHandler"), p0, p1);
/// <summary>
/// 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 '/'.
/// </summary>
internal static string PathMustBeAnAppRelativePath
internal static string PathMustBeRootRelativePath
{
get => GetString("PathMustBeAnAppRelativePath");
get => GetString("PathMustBeRootRelativePath");
}
/// <summary>
/// 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 '/'.
/// </summary>
internal static string FormatPathMustBeAnAppRelativePath()
=> GetString("PathMustBeAnAppRelativePath");
internal static string FormatPathMustBeRootRelativePath()
=> GetString("PathMustBeRootRelativePath");
/// <summary>
/// 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);
/// <summary>
/// '{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".
/// </summary>
internal static string InvalidValidPageName
{
get => GetString("InvalidValidPageName");
}
/// <summary>
/// '{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".
/// </summary>
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);

View File

@ -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";
/// <summary>
/// Gets a list of <see cref="IPageRouteModelConvention"/> instances that will be applied to
/// the <see cref="PageModel"/> when discovering Razor Pages.
/// Gets a collection of <see cref="IPageConvention"/> instances that are applied during
/// route and page model construction.
/// </summary>
public IList<IPageRouteModelConvention> RouteModelConventions { get; } = new List<IPageRouteModelConvention>();
/// <summary>
/// Gets a list of <see cref="IPageRouteModelConvention"/> instances that will be applied to
/// the <see cref="PageModel"/> when discovering Razor Pages.
/// </summary>
public IList<IPageApplicationModelConvention> ApplicationModelConventions { get; } = new List<IPageApplicationModelConvention>();
public PageConventionCollection Conventions { get; } = new PageConventionCollection();
/// <summary>
/// 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;

View File

@ -138,8 +138,8 @@
<data name="AmbiguousHandler" xml:space="preserve">
<value>Multiple handlers matched. The following handlers matched route data and had all constraints satisfied:{0}{0}{1}</value>
</data>
<data name="PathMustBeAnAppRelativePath" xml:space="preserve">
<value>Path must be an application relative path that starts with a forward slash '/'.</value>
<data name="PathMustBeRootRelativePath" xml:space="preserve">
<value>Path must be a root relative path that starts with a forward slash '/'.</value>
</data>
<data name="AsyncPageFilter_InvalidShortCircuit" xml:space="preserve">
<value>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}.</value>
@ -150,4 +150,7 @@
<data name="InvalidPageType_NoModelProperty" xml:space="preserve">
<value>The type '{0}' is not a valid page. A page must define a public, non-static '{1}' property.</value>
</data>
<data name="InvalidValidPageName" xml:space="preserve">
<value>'{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".</value>
</data>
</root>

View File

@ -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 '/'.");
}
}
}

View File

@ -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<IOptions<RazorPagesOptions>>();
// 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));
}
}
}

View File

@ -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<IFilterMetadata>();
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<AuthorizeFilter>(Assert.Single(model.Filters));
},
model =>
{
Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath);
Assert.Equal("/Users/Contact", model.ViewEnginePath);
Assert.IsType<AuthorizeFilter>(model.Filters[0]);
Assert.IsType<AllowAnonymousFilter>(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<AuthorizeFilter>(Assert.Single(model.Filters));
},
model =>
{
Assert.Equal("/Users/Account.cshtml", model.ViewEnginePath);
Assert.Equal("/Users/Account", model.ViewEnginePath);
Assert.IsType<AuthorizeFilter>(model.Filters[0]);
Assert.IsType<AllowAnonymousFilter>(model.Filters[1]);
},
model =>
{
Assert.Equal("/Users/Contact.cshtml", model.ViewEnginePath);
Assert.Equal("/Users/Contact", model.ViewEnginePath);
Assert.IsType<AuthorizeFilter>(model.Filters[0]);
Assert.IsType<AllowAnonymousFilter>(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<AuthorizeFilter>(Assert.Single(model.Filters));
var authorizeData = Assert.IsType<AuthorizeAttribute>(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<AuthorizeFilter>(Assert.Single(model.Filters));
var authorizeData = Assert.IsType<AuthorizeAttribute>(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<AuthorizeFilter>(Assert.Single(model.Filters));
var authorizeData = Assert.IsType<AuthorizeAttribute>(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<AuthorizeFilter>(Assert.Single(model.Filters));
var authorizeData = Assert.IsType<AuthorizeAttribute>(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<AuthorizeFilter>(Assert.Single(model.Filters));
var authorizeData = Assert.IsType<AuthorizeAttribute>(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<AuthorizeFilter>(Assert.Single(model.Filters));
var authorizeData = Assert.IsType<AuthorizeAttribute>(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<IPageRouteModelConvention>())
{
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<IPageApplicationModelConvention>())
{
foreach (var model in models)
{

View File

@ -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,

View File

@ -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();
}

View File

@ -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");
});
}