diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs
index 0b75e6a22d..682d55141f 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Internal/PageRouteModelFactory.cs
@@ -60,21 +60,15 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
{
model.RouteValues.Add("page", model.ViewEnginePath);
- if (AttributeRouteModel.IsOverridePattern(routeTemplate))
- {
- throw new InvalidOperationException(string.Format(
- Resources.PageActionDescriptorProvider_RouteTemplateCannotBeOverrideable,
- model.RelativePath));
- }
-
var selectorModel = CreateSelectorModel(pageRoute, routeTemplate);
model.Selectors.Add(selectorModel);
var fileName = Path.GetFileName(model.RelativePath);
- if (string.Equals(IndexFileName, fileName, StringComparison.OrdinalIgnoreCase))
+ if (!AttributeRouteModel.IsOverridePattern(routeTemplate) &&
+ string.Equals(IndexFileName, fileName, StringComparison.OrdinalIgnoreCase))
{
- // For pages ending in /Index.cshtml, we want to allow incoming routing, but
- // force outgoing routes to match to the path sans /Index.
+ // For pages without an override route, and ending in /Index.cshtml, we want to allow
+ // incoming routing, but force outgoing routes to match to the path sans /Index.
selectorModel.AttributeRouteModel.SuppressLinkGeneration = true;
var index = pageRoute.LastIndexOf('/');
diff --git a/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx b/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx
index be0f94f186..90e0ea9300 100644
--- a/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx
+++ b/src/Microsoft.AspNetCore.Mvc.RazorPages/Resources.resx
@@ -117,9 +117,6 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
- The route for the page at '{0}' cannot start with / or ~/. Pages do not support overriding the file path of the page.
-
The '{0}' property of '{1}' must not be null.
diff --git a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
index 856e88cb94..ea36e1bb7c 100644
--- a/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.FunctionalTests/RazorPagesTest.cs
@@ -1233,6 +1233,16 @@ Microsoft.AspNetCore.Mvc.ViewFeatures.ViewDataDictionary`1[AspNetCore.InjectedPa
Assert.Equal(HttpStatusCode.Redirect, response.StatusCode);
}
+ [Fact]
+ public async Task Page_CanOverrideRouteTemplate()
+ {
+ // Arrange & Act
+ var content = await Client.GetStringAsync("like-totally-custom");
+
+ // Assert
+ Assert.Equal("
Hey, it's Mr. totally custom here!
", content.Trim());
+ }
+
private async Task AddAntiforgeryHeaders(HttpRequestMessage request)
{
var getResponse = await Client.GetAsync(request.RequestUri);
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs
index 011681f152..b270ee3389 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/CompiledPageRouteModelProviderTest.cs
@@ -479,23 +479,40 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
[Fact]
- public void OnProvidersExecuting_ThrowsIfRouteTemplateHasOverridePattern()
+ public void OnProvidersExecuting_AllowsRouteTemplatesWithOverridePattern()
{
// Arrange
var descriptors = new[]
{
- CreateVersion_2_0_Descriptor("/Pages/Index.cshtml"),
+ CreateVersion_2_0_Descriptor("/Pages/Index.cshtml", "~/some-other-prefix"),
CreateVersion_2_0_Descriptor("/Pages/Home.cshtml", "/some-prefix"),
};
var provider = CreateProvider(descriptors: descriptors);
var context = new PageRouteModelProviderContext();
- // Act & Assert
- var exception = Assert.Throws(() => provider.OnProvidersExecuting(context));
- Assert.Equal(
- "The route for the page at '/Pages/Home.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
- exception.Message);
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.Collection(
+ context.RouteModels,
+ result =>
+ {
+ Assert.Equal("/Pages/Index.cshtml", result.RelativePath);
+ Assert.Equal("/Index", result.ViewEnginePath);
+ Assert.Collection(
+ result.Selectors,
+ selector => Assert.Equal("some-other-prefix", selector.AttributeRouteModel.Template));
+ },
+ result =>
+ {
+ Assert.Equal("/Pages/Home.cshtml", result.RelativePath);
+ Assert.Equal("/Home", result.ViewEnginePath);
+ Assert.Collection(
+ result.Selectors,
+ selector => Assert.Equal("some-prefix", selector.AttributeRouteModel.Template));
+ });
}
private TestCompiledPageRouteModelProvider CreateProvider(
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs
index 7e8aeeb734..67bb226d06 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/PageRouteModelFactoryTest.cs
@@ -67,6 +67,34 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
});
}
+ [Fact]
+ public void CreateRouteModel_DoesNotAddMultipleSelectorsWhenRouteTemplateIsAbsolute()
+ {
+ // Arrange
+ var relativePath = "/Pages/Users/Profile/Index.cshtml";
+ var options = new RazorPagesOptions();
+ var routeModelFactory = new PageRouteModelFactory(options, NullLogger.Instance);
+
+ // Act
+ var routeModel = routeModelFactory.CreateRouteModel(relativePath, "/my-override");
+
+ // Assert
+ Assert.Equal(relativePath, routeModel.RelativePath);
+ Assert.Equal("/Users/Profile/Index", routeModel.ViewEnginePath);
+
+ Assert.Collection(
+ routeModel.Selectors,
+ selector => Assert.Equal("my-override", selector.AttributeRouteModel.Template));
+
+ Assert.Collection(
+ routeModel.RouteValues,
+ kvp =>
+ {
+ Assert.Equal("page", kvp.Key);
+ Assert.Equal("/Users/Profile/Index", kvp.Value);
+ });
+ }
+
[Fact]
public void CreateAreaRouteModel_AddsSelector()
{
diff --git a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs
index 37fd8c6ac6..8054e21a4f 100644
--- a/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs
+++ b/test/Microsoft.AspNetCore.Mvc.RazorPages.Test/Internal/RazorProjectPageRouteModelProviderTest.cs
@@ -282,7 +282,7 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
}
[Fact]
- public void OnProvidersExecuting_ThrowsIfRouteTemplateHasOverridePattern()
+ public void OnProvidersExecuting_AllowsRouteTemplateWithOverridePattern()
{
// Arrange
var fileProvider = new TestFileProvider();
@@ -296,10 +296,20 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Internal
var provider = new RazorProjectPageRouteModelProvider(project, optionsManager, NullLoggerFactory.Instance);
var context = new PageRouteModelProviderContext();
- // Act & Assert
- var ex = Assert.Throws(() => provider.OnProvidersExecuting(context));
- Assert.Equal("The route for the page at '/Index.cshtml' cannot start with / or ~/. Pages do not support overriding the file path of the page.",
- ex.Message);
+ // Act
+ provider.OnProvidersExecuting(context);
+
+ // Assert
+ Assert.Collection(
+ context.RouteModels,
+ model =>
+ {
+ Assert.Equal("/Index.cshtml", model.RelativePath);
+ Assert.Equal("/Index", model.ViewEnginePath);
+ Assert.Collection(
+ model.Selectors,
+ selector => Assert.Equal("custom-route", selector.AttributeRouteModel.Template));
+ });
}
[Fact]
diff --git a/test/WebSites/RazorPagesWebSite/Pages/CustomPrefix/Custom.cshtml b/test/WebSites/RazorPagesWebSite/Pages/CustomPrefix/Custom.cshtml
new file mode 100644
index 0000000000..ace20dc097
--- /dev/null
+++ b/test/WebSites/RazorPagesWebSite/Pages/CustomPrefix/Custom.cshtml
@@ -0,0 +1,3 @@
+@page "~/like-totally-custom"
+
+Hey, it's Mr. totally custom here!