[Fixes #188] Added support for AppendTrailingSlash in RouteOptions

This commit is contained in:
Ajay Bhargav Baaskaran 2015-06-17 16:47:43 -07:00
parent 7172608722
commit b135a9d53b
3 changed files with 53 additions and 26 deletions

View File

@ -203,24 +203,31 @@ namespace Microsoft.AspNet.Routing
{ {
var url = path.Value; var url = path.Value;
if (!string.IsNullOrEmpty(url) && _options.LowercaseUrls) if (!string.IsNullOrEmpty(url) && (_options.LowercaseUrls || _options.AppendTrailingSlash))
{ {
var indexOfSeparator = url.IndexOfAny(new char[] { '?', '#' }); var indexOfSeparator = url.IndexOfAny(new char[] { '?', '#' });
var urlWithoutQueryString = url;
var queryString = string.Empty;
// No query string, lowercase the url if (indexOfSeparator != -1)
if (indexOfSeparator == -1)
{ {
url = url.ToLowerInvariant(); urlWithoutQueryString = url.Substring(0, indexOfSeparator);
queryString = url.Substring(indexOfSeparator);
} }
else
{
var lowercaseUrl = url.Substring(0, indexOfSeparator).ToLowerInvariant();
var queryString = url.Substring(indexOfSeparator);
// queryString will contain the delimiter ? or # as the first character, so it's safe to append. if (_options.LowercaseUrls)
url = lowercaseUrl + queryString; {
urlWithoutQueryString = urlWithoutQueryString.ToLowerInvariant();
} }
if (_options.AppendTrailingSlash && !urlWithoutQueryString.EndsWith("/"))
{
urlWithoutQueryString += "/";
}
// queryString will contain the delimiter ? or # as the first character, so it's safe to append.
url = urlWithoutQueryString + queryString;
return new PathString(url); return new PathString(url);
} }

View File

@ -15,6 +15,11 @@ namespace Microsoft.AspNet.Routing
/// </summary> /// </summary>
public bool LowercaseUrls { get; set; } public bool LowercaseUrls { get; set; }
/// <summary>
/// Gets or sets a value indicating whether a trailing slash should be appended to the generated URLs.
/// </summary>
public bool AppendTrailingSlash { get; set; }
private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap(); private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
public IDictionary<string, Type> ConstraintMap public IDictionary<string, Type> ConstraintMap

View File

@ -20,16 +20,22 @@ namespace Microsoft.AspNet.Routing
public class RouteCollectionTest public class RouteCollectionTest
{ {
[Theory] [Theory]
[InlineData(@"Home/Index/23", "/home/index/23", true)] [InlineData(@"Home/Index/23", "/home/index/23", true, false)]
[InlineData(@"Home/Index/23", "/Home/Index/23", false)] [InlineData(@"Home/Index/23", "/Home/Index/23", false, false)]
[InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false)] [InlineData(@"Home/Index/23", "/home/index/23/", true, true)]
[InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/home/index/23?Param1=ABC&Param2=Xyz", true)] [InlineData(@"Home/Index/23", "/Home/Index/23/", false, true)]
[InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/Home/Index/23#Param1=ABC&Param2=Xyz", false)] [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23/?Param1=ABC&Param2=Xyz", false, true)]
[InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/home/index/23#Param1=ABC&Param2=Xyz", true)] [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, false)]
public void GetVirtualPath_CanLowerCaseUrls_BasedOnOptions( [InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/home/index/23/?Param1=ABC&Param2=Xyz", true, true)]
[InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/Home/Index/23/#Param1=ABC&Param2=Xyz", false, true)]
[InlineData(@"Home/Index/23#Param1=ABC&Param2=Xyz", "/home/index/23#Param1=ABC&Param2=Xyz", true, false)]
[InlineData(@"Home/Index/23/?Param1=ABC&Param2=Xyz", "/home/index/23/?Param1=ABC&Param2=Xyz", true, true)]
[InlineData(@"Home/Index/23/#Param1=ABC&Param2=Xyz", "/home/index/23/#Param1=ABC&Param2=Xyz", true, false)]
public void GetVirtualPath_CanLowerCaseUrls_And_AppendTrailingSlash_BasedOnOptions(
string returnUrl, string returnUrl,
string lowercaseUrl, string expectedUrl,
bool lowercaseUrls) bool lowercaseUrls,
bool appendTrailingSlash)
{ {
// Arrange // Arrange
var target = new Mock<IRouter>(MockBehavior.Strict); var target = new Mock<IRouter>(MockBehavior.Strict);
@ -39,13 +45,17 @@ namespace Microsoft.AspNet.Routing
var routeCollection = new RouteCollection(); var routeCollection = new RouteCollection();
routeCollection.Add(target.Object); routeCollection.Add(target.Object);
var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls)); var virtualPathContext = CreateVirtualPathContext(
options: GetRouteOptions(
lowerCaseUrls: lowercaseUrls,
useBestEffortLinkGeneration: true,
appendTrailingSlash: appendTrailingSlash));
// Act // Act
var pathData = routeCollection.GetVirtualPath(virtualPathContext); var pathData = routeCollection.GetVirtualPath(virtualPathContext);
// Assert // Assert
Assert.Equal(new PathString(lowercaseUrl), pathData.VirtualPath); Assert.Equal(new PathString(expectedUrl), pathData.VirtualPath);
Assert.Same(target.Object, pathData.Router); Assert.Same(target.Object, pathData.Router);
Assert.Empty(pathData.DataTokens); Assert.Empty(pathData.DataTokens);
} }
@ -880,11 +890,16 @@ namespace Microsoft.AspNet.Routing
return target; return target;
} }
private static RouteOptions GetRouteOptions(bool lowerCaseUrls = false, bool useBestEffortLinkGeneration = true) private static RouteOptions GetRouteOptions(
bool lowerCaseUrls = false,
bool useBestEffortLinkGeneration = true,
bool appendTrailingSlash = false)
{ {
var routeOptions = new RouteOptions(); var routeOptions = new RouteOptions();
routeOptions.LowercaseUrls = lowerCaseUrls; routeOptions.LowercaseUrls = lowerCaseUrls;
routeOptions.UseBestEffortLinkGeneration = useBestEffortLinkGeneration; routeOptions.UseBestEffortLinkGeneration = useBestEffortLinkGeneration;
routeOptions.AppendTrailingSlash = appendTrailingSlash;
return routeOptions; return routeOptions;
} }
} }