diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
index a14df1b6a8..fa8d0f142a 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/DependencyInjection/RazorPagesOptionsExtensions.cs
@@ -59,30 +59,30 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Adds a to the page with the specified path.
+ /// Adds a to the page with the specified name.
///
/// The to configure.
- /// The path of the Razor Page.
+ /// The page name.
/// The .
- public static RazorPagesOptions AllowAnonymousToPage(this RazorPagesOptions options, string path)
+ public static RazorPagesOptions AllowAnonymousToPage(this RazorPagesOptions options, string pageName)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
- if (string.IsNullOrEmpty(path))
+ if (string.IsNullOrEmpty(pageName))
{
- throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
}
var anonymousFilter = new AllowAnonymousFilter();
- options.Conventions.Add(new PageConvention(path, model => model.Filters.Add(anonymousFilter)));
+ options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(anonymousFilter)));
return options;
}
///
- /// Adds a to all pages under the specified path.
+ /// Adds a to all pages under the specified folder.
///
/// The to configure.
/// The folder path.
@@ -105,40 +105,40 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Adds a with the specified policy to the page with the specified path.
+ /// Adds a with the specified policy to the page with the specified name.
///
/// The to configure.
- /// The path of the Razor Page.
+ /// The page name.
/// The authorization policy.
/// The .
- public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string path, string policy)
+ public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string pageName, string policy)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
- if (string.IsNullOrEmpty(path))
+ if (string.IsNullOrEmpty(pageName))
{
- throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(path));
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
}
var authorizeFilter = new AuthorizeFilter(policy);
- options.Conventions.Add(new PageConvention(path, model => model.Filters.Add(authorizeFilter)));
+ options.Conventions.Add(new PageConvention(pageName, model => model.Filters.Add(authorizeFilter)));
return options;
}
///
- /// Adds a to the page with the specified path.
+ /// Adds a to the page with the specified name.
///
/// The to configure.
- /// The path of the Razor Page.
+ /// The page name.
/// The .
- public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string path) =>
- AuthorizePage(options, path, policy: string.Empty);
+ public static RazorPagesOptions AuthorizePage(this RazorPagesOptions options, string pageName) =>
+ AuthorizePage(options, pageName, policy: string.Empty);
///
- /// Adds a with the specified policy to all pages under the specified path.
+ /// Adds a with the specified policy to all pages under the specified folder.
///
/// The to configure.
/// The folder path.
@@ -162,7 +162,7 @@ namespace Microsoft.Extensions.DependencyInjection
}
///
- /// Adds a to all pages under the specified path.
+ /// Adds a to all pages under the specified folder.
///
/// The to configure.
/// The folder path.
@@ -170,6 +170,54 @@ namespace Microsoft.Extensions.DependencyInjection
public static RazorPagesOptions AuthorizeFolder(this RazorPagesOptions options, string folderPath) =>
AuthorizeFolder(options, folderPath, policy: string.Empty);
+ ///
+ /// Adds the specified to the page at the specified .
+ ///
+ /// The page can be routed via in addition to the default set of path based routes.
+ /// All links generated for this page will use the specified route.
+ ///
+ ///
+ /// The .
+ /// The page name.
+ /// The route to associate with the page.
+ /// The .
+ public static RazorPagesOptions AddPageRoute(this RazorPagesOptions options, string pageName, string route)
+ {
+ if (options == null)
+ {
+ throw new ArgumentNullException(nameof(options));
+ }
+
+ if (string.IsNullOrEmpty(pageName))
+ {
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(pageName));
+ }
+
+ if (string.IsNullOrEmpty(route))
+ {
+ throw new ArgumentException(Resources.ArgumentCannotBeNullOrEmpty, nameof(route));
+ }
+
+ options.Conventions.Add(new PageConvention(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 PageConvention : IPageApplicationModelConvention
{
private readonly string _path;
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
index ac3bdc29b1..196563e4cd 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
@@ -1024,6 +1024,19 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore._InjectedP
Assert.StartsWith(expected, response.Trim());
}
+ [Fact]
+ public async Task PagesCanByRoutedViaRoute_AddedViaAddPageRoute()
+ {
+ // Arrange
+ var expected = "Hello, test!";
+
+ // Act
+ var response = await Client.GetStringAsync("/Different-Route/test");
+
+ // Assert
+ Assert.StartsWith(expected, response.Trim());
+ }
+
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
index fb09882688..9741a0456f 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/DependencyInjection/RazorPagesOptionsExtensionsTest.cs
@@ -242,6 +242,80 @@ namespace Microsoft.Extensions.DependencyInjection
});
}
+ [Fact]
+ public void AddPageRoute_AddsRouteToSelector()
+ {
+ // Arrange
+ var options = new RazorPagesOptions();
+ var models = new[]
+ {
+ new PageApplicationModel("/Pages/Index.cshtml", "/Index.cshtml")
+ {
+ Selectors =
+ {
+ CreateSelectorModel("Index", suppressLinkGeneration: true),
+ CreateSelectorModel(""),
+ }
+ },
+ new PageApplicationModel("/Pages/About.cshtml", "/About.cshtml")
+ {
+ Selectors =
+ {
+ CreateSelectorModel("About"),
+ }
+ }
+ };
+
+ // Act
+ options.AddPageRoute("/Index.cshtml", "Different-Route");
+ ApplyConventions(options, models);
+
+ // Assert
+ Assert.Collection(models,
+ model =>
+ {
+ Assert.Equal("/Index.cshtml", model.ViewEnginePath);
+ Assert.Collection(model.Selectors,
+ selector =>
+ {
+ Assert.Equal("Index", selector.AttributeRouteModel.Template);
+ Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
+ },
+ selector =>
+ {
+ Assert.Equal("", selector.AttributeRouteModel.Template);
+ Assert.True(selector.AttributeRouteModel.SuppressLinkGeneration);
+ },
+ selector =>
+ {
+ Assert.Equal("Different-Route", selector.AttributeRouteModel.Template);
+ Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
+ });
+ },
+ model =>
+ {
+ Assert.Equal("/About.cshtml", model.ViewEnginePath);
+ Assert.Collection(model.Selectors,
+ selector =>
+ {
+ Assert.Equal("About", selector.AttributeRouteModel.Template);
+ Assert.False(selector.AttributeRouteModel.SuppressLinkGeneration);
+ });
+ });
+ }
+
+ private static SelectorModel CreateSelectorModel(string template, bool suppressLinkGeneration = false)
+ {
+ return new SelectorModel
+ {
+ AttributeRouteModel = new AttributeRouteModel
+ {
+ Template = template,
+ SuppressLinkGeneration = suppressLinkGeneration
+ },
+ };
+ }
+
private static void ApplyConventions(RazorPagesOptions options, PageApplicationModel[] models)
{
foreach (var convention in options.Conventions)
diff --git a/test/WebSites/RazorPagesWebSite/Startup.cs b/test/WebSites/RazorPagesWebSite/Startup.cs
index 8a12746fcc..d4c369bfb1 100644
--- a/test/WebSites/RazorPagesWebSite/Startup.cs
+++ b/test/WebSites/RazorPagesWebSite/Startup.cs
@@ -19,6 +19,7 @@ namespace RazorPagesWebSite
options.AuthorizePage("/HelloWorldWithAuth");
options.AuthorizeFolder("/Pages/Admin");
options.AllowAnonymousToPage("/Pages/Admin/Login");
+ options.AddPageRoute("/HelloWorldWithRoute", "Different-Route/{text}");
})
.WithRazorPagesAtContentRoot();
}