Move convention extensions from RazorPagesOptions to PageConventionCollectionsExtensions
Fixes #6462
This commit is contained in:
parent
660b7df0e2
commit
bc86ea4e47
|
|
@ -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"/>.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
|
@ -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"/>.
|
||||
|
|
|
|||
|
|
@ -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] == '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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] == '/';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
@ -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 '/'.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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");
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue