diff --git a/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs b/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
index 5cb82620c1..6d2bb0a7c9 100644
--- a/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
+++ b/src/Microsoft.AspNetCore.Routing/Properties/Resources.Designer.cs
@@ -249,7 +249,7 @@ namespace Microsoft.AspNetCore.Routing
=> string.Format(CultureInfo.CurrentCulture, GetString("TemplateRoute_InvalidParameterName"), p0);
///
- /// The route template cannot start with a '/' or '~' character.
+ /// The route template cannot start with a '~' character unless followed by a '/'.
///
internal static string TemplateRoute_InvalidRouteTemplate
{
@@ -257,7 +257,7 @@ namespace Microsoft.AspNetCore.Routing
}
///
- /// The route template cannot start with a '/' or '~' character.
+ /// The route template cannot start with a '~' character unless followed by a '/'.
///
internal static string FormatTemplateRoute_InvalidRouteTemplate()
=> GetString("TemplateRoute_InvalidRouteTemplate");
diff --git a/src/Microsoft.AspNetCore.Routing/Resources.resx b/src/Microsoft.AspNetCore.Routing/Resources.resx
index 96ecf1f445..d883906f7c 100644
--- a/src/Microsoft.AspNetCore.Routing/Resources.resx
+++ b/src/Microsoft.AspNetCore.Routing/Resources.resx
@@ -1,17 +1,17 @@
-
@@ -169,7 +169,7 @@
The route parameter name '{0}' is invalid. Route parameter names must be non-empty and cannot contain these characters: '{{', '}}', '/'. The '?' character marks a parameter as optional, and can occur only at the end of the parameter. The '*' character marks a parameter as catch-all, and can occur only at the start of the parameter.
- The route template cannot start with a '/' or '~' character.
+ The route template cannot start with a '~' character unless followed by a '/'.
There is an incomplete parameter in the route template. Check that each '{' character has a matching '}' character.
diff --git a/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs b/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs
index 51f598a486..0168b22a4b 100644
--- a/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs
+++ b/src/Microsoft.AspNetCore.Routing/Template/TemplateParser.cs
@@ -25,12 +25,9 @@ namespace Microsoft.AspNetCore.Routing.Template
routeTemplate = String.Empty;
}
- if (IsInvalidRouteTemplate(routeTemplate))
- {
- throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, nameof(routeTemplate));
- }
+ var trimmedRouteTemplate = TrimPrefix(routeTemplate);
- var context = new TemplateParserContext(routeTemplate);
+ var context = new TemplateParserContext(trimmedRouteTemplate);
var segments = new List();
while (context.Next())
@@ -61,6 +58,23 @@ namespace Microsoft.AspNetCore.Routing.Template
}
}
+ private static string TrimPrefix(string routeTemplate)
+ {
+ if (routeTemplate.StartsWith("~/", StringComparison.Ordinal))
+ {
+ return routeTemplate.Substring(2);
+ }
+ else if (routeTemplate.StartsWith("/", StringComparison.Ordinal))
+ {
+ return routeTemplate.Substring(1);
+ }
+ else if (routeTemplate.StartsWith("~", StringComparison.Ordinal))
+ {
+ throw new ArgumentException(Resources.TemplateRoute_InvalidRouteTemplate, nameof(routeTemplate));
+ }
+ return routeTemplate;
+ }
+
private static bool ParseSegment(TemplateParserContext context, List segments)
{
Debug.Assert(context != null);
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs
index 4d71164ce6..ab06c3f2cc 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs
@@ -691,6 +691,28 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
"/UrlEncode[[v1]]");
}
+ [Fact]
+ public void GetUrlWithLeadingTildeSlash()
+ {
+ RunTest(
+ "~/foo",
+ null,
+ null,
+ new RouteValueDictionary(new { }),
+ "/UrlEncode[[foo]]");
+ }
+
+ [Fact]
+ public void GetUrlWithLeadingSlash()
+ {
+ RunTest(
+ "/foo",
+ null,
+ null,
+ new RouteValueDictionary(new { }),
+ "/UrlEncode[[foo]]");
+ }
+
[Fact]
public void TemplateBinder_KeepsExplicitlySuppliedRouteValues_OnFailedRouetMatch()
{
@@ -721,7 +743,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
#if ROUTE_COLLECTION
- [Fact]
+ [Fact]
public void GetUrlShouldValidateOnlyAcceptedParametersAndUserDefaultValuesForInvalidatedParameters()
{
// Arrange
@@ -755,7 +777,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
Assert.Equal("/app1/UrlConstraints/Validation.mvc/Input5/MissmatchedValidateParameters2/valid1", vpd.VirtualPath);
}
- [Fact]
+ [Fact]
public void GetUrlWithRouteThatHasExtensionWithSubsequentDefaultValueIncludesExtensionButNotDefaultValue()
{
// Arrange
diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs
index 7ffd8f1026..7f9f9b8b40 100644
--- a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs
+++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateParserTests.cs
@@ -655,6 +655,18 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
"Parameter name: routeTemplate");
}
+ [Theory]
+ [InlineData("/foo")]
+ [InlineData("~/foo")]
+ public void ValidTemplate_CanStartWithSlashOrTildeSlash(string routeTemplate)
+ {
+ // Arrange & Act
+ var template = TemplateParser.Parse(routeTemplate);
+
+ // Assert
+ Assert.Equal(routeTemplate, template.TemplateText);
+ }
+
[Fact]
public void InvalidTemplate_CannotHaveConsecutiveOpenBrace()
{
@@ -768,21 +780,12 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests
"Parameter name: routeTemplate");
}
- [Fact]
- public void InvalidTemplate_CannotStartWithSlash()
- {
- ExceptionAssert.Throws(
- () => TemplateParser.Parse("/foo"),
- "The route template cannot start with a '/' or '~' character." + Environment.NewLine +
- "Parameter name: routeTemplate");
- }
-
[Fact]
public void InvalidTemplate_CannotStartWithTilde()
{
ExceptionAssert.Throws(
() => TemplateParser.Parse("~foo"),
- "The route template cannot start with a '/' or '~' character." + Environment.NewLine +
+ "The route template cannot start with a '~' character unless followed by a '/'." + Environment.NewLine +
"Parameter name: routeTemplate");
}