[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;
if (!string.IsNullOrEmpty(url) && _options.LowercaseUrls)
if (!string.IsNullOrEmpty(url) && (_options.LowercaseUrls || _options.AppendTrailingSlash))
{
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.
url = lowercaseUrl + queryString;
if (_options.LowercaseUrls)
{
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);
}

View File

@ -15,6 +15,11 @@ namespace Microsoft.AspNet.Routing
/// </summary>
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();
public IDictionary<string, Type> ConstraintMap

View File

@ -20,16 +20,22 @@ namespace Microsoft.AspNet.Routing
public class RouteCollectionTest
{
[Theory]
[InlineData(@"Home/Index/23", "/home/index/23", true)]
[InlineData(@"Home/Index/23", "/Home/Index/23", false)]
[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", 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", true)]
public void GetVirtualPath_CanLowerCaseUrls_BasedOnOptions(
[InlineData(@"Home/Index/23", "/home/index/23", true, false)]
[InlineData(@"Home/Index/23", "/Home/Index/23", false, false)]
[InlineData(@"Home/Index/23", "/home/index/23/", true, 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, true)]
[InlineData(@"Home/Index/23?Param1=ABC&Param2=Xyz", "/Home/Index/23?Param1=ABC&Param2=Xyz", false, 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", 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 lowercaseUrl,
bool lowercaseUrls)
string expectedUrl,
bool lowercaseUrls,
bool appendTrailingSlash)
{
// Arrange
var target = new Mock<IRouter>(MockBehavior.Strict);
@ -39,17 +45,21 @@ namespace Microsoft.AspNet.Routing
var routeCollection = new RouteCollection();
routeCollection.Add(target.Object);
var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls));
var virtualPathContext = CreateVirtualPathContext(
options: GetRouteOptions(
lowerCaseUrls: lowercaseUrls,
useBestEffortLinkGeneration: true,
appendTrailingSlash: appendTrailingSlash));
// Act
var pathData = routeCollection.GetVirtualPath(virtualPathContext);
// Assert
Assert.Equal(new PathString(lowercaseUrl), pathData.VirtualPath);
Assert.Equal(new PathString(expectedUrl), pathData.VirtualPath);
Assert.Same(target.Object, pathData.Router);
Assert.Empty(pathData.DataTokens);
}
[Theory]
[InlineData(@"\u0130", @"/\u0130", true)]
[InlineData(@"\u0049", @"/\u0049", true)]
@ -114,7 +124,7 @@ namespace Microsoft.AspNet.Routing
Assert.Equal(dataToken.Value, pathData.DataTokens[dataToken.Key]);
}
}
[Fact]
public async Task RouteAsync_FirstMatches()
{
@ -225,7 +235,7 @@ namespace Microsoft.AspNet.Routing
// Assert
Assert.Null(stringVirtualPath);
}
[Fact]
public void NamedRouteTests_GetNamedRoute_AmbiguousRoutesInCollection_DoesNotThrowForUnambiguousRoute()
{
@ -245,7 +255,7 @@ namespace Microsoft.AspNet.Routing
Assert.Equal("Route1", namedRouter.Name);
Assert.Empty(pathData.DataTokens);
}
[Fact]
public void NamedRouteTests_GetNamedRoute_AmbiguousRoutesInCollection_ThrowsForAmbiguousRoute()
{
@ -880,11 +890,16 @@ namespace Microsoft.AspNet.Routing
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();
routeOptions.LowercaseUrls = lowerCaseUrls;
routeOptions.UseBestEffortLinkGeneration = useBestEffortLinkGeneration;
routeOptions.AppendTrailingSlash = appendTrailingSlash;
return routeOptions;
}
}