Issue 140: Support lower case routes
This commit is contained in:
parent
f41b6e4d10
commit
81a17300b2
|
|
@ -9,8 +9,9 @@ using Microsoft.AspNet.Http;
|
|||
using Microsoft.AspNet.Routing.Logging;
|
||||
using Microsoft.AspNet.Routing.Logging.Internal;
|
||||
using Microsoft.Framework.DependencyInjection;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
{
|
||||
|
|
@ -154,7 +155,7 @@ namespace Microsoft.AspNet.Routing
|
|||
if (isValidated || useBestEffort)
|
||||
{
|
||||
context.IsBound = isValidated;
|
||||
return bestPath;
|
||||
return NormalizeVirtualPath(bestPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -177,7 +178,7 @@ namespace Microsoft.AspNet.Routing
|
|||
if (context.IsBound)
|
||||
{
|
||||
// This route has validated route values, short circuit.
|
||||
return path;
|
||||
return NormalizeVirtualPath(path);
|
||||
}
|
||||
else if (bestPath == null)
|
||||
{
|
||||
|
|
@ -188,7 +189,7 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
if (useBestEffort)
|
||||
{
|
||||
return bestPath;
|
||||
return NormalizeVirtualPath(bestPath);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
|
@ -197,7 +198,33 @@ namespace Microsoft.AspNet.Routing
|
|||
}
|
||||
}
|
||||
|
||||
private void EnsureLogger(HttpContext context)
|
||||
private string NormalizeVirtualPath(String url)
|
||||
{
|
||||
if (string.IsNullOrEmpty(url)) return url;
|
||||
|
||||
if (_options.LowercaseUrls)
|
||||
{
|
||||
var indexOfSeparator = url.IndexOfAny(new char[] { '?', '#' });
|
||||
|
||||
// No query string, lowercase the url
|
||||
if (indexOfSeparator == -1)
|
||||
{
|
||||
return url.ToLowerInvariant();
|
||||
}
|
||||
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.
|
||||
return lowercaseUrl + queryString;
|
||||
}
|
||||
}
|
||||
|
||||
return url;
|
||||
}
|
||||
|
||||
private void EnsureLogger(HttpContext context)
|
||||
{
|
||||
if (_logger == null)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -9,6 +9,11 @@ namespace Microsoft.AspNet.Routing
|
|||
{
|
||||
public class RouteOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// Gets or sets a value indicating whether all generated URLs are lower-case.
|
||||
/// </summary>
|
||||
public bool LowercaseUrls { get; set; }
|
||||
|
||||
private IDictionary<string, Type> _constraintTypeMap = GetDefaultConstraintMap();
|
||||
|
||||
public IDictionary<string, Type> ConstraintMap
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ using System.Linq;
|
|||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Http;
|
||||
using Microsoft.AspNet.Routing.Logging;
|
||||
using Microsoft.AspNet.Routing.Template;
|
||||
using Microsoft.Framework.Logging;
|
||||
using Microsoft.Framework.OptionsModel;
|
||||
using Moq;
|
||||
|
|
@ -17,6 +18,61 @@ 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(
|
||||
string returnUrl,
|
||||
string lowercaseUrl,
|
||||
bool lowercaseUrls)
|
||||
{
|
||||
// Arrange
|
||||
var target = new Mock<IRouter>(MockBehavior.Strict);
|
||||
target
|
||||
.Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||
.Returns(returnUrl);
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(target.Object);
|
||||
var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls));
|
||||
|
||||
// Act
|
||||
var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(lowercaseUrl, stringVirtualPath);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(@"\u0130", @"\u0130", true)]
|
||||
[InlineData(@"\u0049", @"\u0049", true)]
|
||||
[InlineData(@"üino", @"üino", true)]
|
||||
public void GetVirtualPath_DoesntLowerCaseUrls_Invariant(
|
||||
string returnUrl,
|
||||
string lowercaseUrl,
|
||||
bool lowercaseUrls)
|
||||
{
|
||||
// Arrange
|
||||
var target = new Mock<IRouter>(MockBehavior.Strict);
|
||||
target
|
||||
.Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||
.Returns(returnUrl);
|
||||
|
||||
var routeCollection = new RouteCollection();
|
||||
routeCollection.Add(target.Object);
|
||||
var virtualPathContext = CreateVirtualPathContext(options: GetRouteOptions(lowercaseUrls));
|
||||
|
||||
// Act
|
||||
var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(lowercaseUrl, stringVirtualPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_LogsCorrectValuesWhenHandled()
|
||||
{
|
||||
|
|
@ -124,8 +180,8 @@ namespace Microsoft.AspNet.Routing
|
|||
public async Task RouteAsync_SecondMatches()
|
||||
{
|
||||
// Arrange
|
||||
var routes = new RouteCollection();
|
||||
|
||||
var routes = new RouteCollection();
|
||||
var route1 = CreateRoute(accept: false);
|
||||
routes.Add(route1.Object);
|
||||
|
||||
|
|
@ -151,7 +207,6 @@ namespace Microsoft.AspNet.Routing
|
|||
{
|
||||
// Arrange
|
||||
var routes = new RouteCollection();
|
||||
|
||||
var route1 = CreateRoute(accept: false);
|
||||
routes.Add(route1.Object);
|
||||
|
||||
|
|
@ -171,18 +226,22 @@ namespace Microsoft.AspNet.Routing
|
|||
Assert.Empty(context.RouteData.Routers);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NamedRouteTests_GetNamedRoute_ReturnsValue()
|
||||
[Theory]
|
||||
[InlineData(false, "RouteName")]
|
||||
[InlineData(true, "routename")]
|
||||
public void NamedRouteTests_GetNamedRoute_ReturnsValue(bool lowercaseUrls, string expectedUrl)
|
||||
{
|
||||
// Arrange
|
||||
var routeCollection = GetNestedRouteCollection(new string[] { "Route1", "Route2", "RouteName", "Route3" });
|
||||
var virtualPathContext = CreateVirtualPathContext("RouteName");
|
||||
var virtualPathContext = CreateVirtualPathContext(
|
||||
routeName: "RouteName",
|
||||
options: GetRouteOptions(lowercaseUrls));
|
||||
|
||||
// Act
|
||||
var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("RouteName", stringVirtualPath);
|
||||
Assert.Equal(expectedUrl, stringVirtualPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -207,13 +266,13 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
// Add Duplicate route.
|
||||
routeCollection.Add(CreateNamedRoute("Route3"));
|
||||
var virtualPathContext = CreateVirtualPathContext("Route1");
|
||||
var virtualPathContext = CreateVirtualPathContext(routeName: "Route1", options: GetRouteOptions(true));
|
||||
|
||||
// Act
|
||||
var stringVirtualPath = routeCollection.GetVirtualPath(virtualPathContext);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Route1", stringVirtualPath);
|
||||
Assert.Equal("route1", stringVirtualPath);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -225,11 +284,13 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
// Add Duplicate route.
|
||||
routeCollection.Add(CreateNamedRoute(ambiguousRoute));
|
||||
var virtualPathContext = CreateVirtualPathContext(ambiguousRoute);
|
||||
var virtualPathContext = CreateVirtualPathContext(routeName: ambiguousRoute, options: GetRouteOptions());
|
||||
|
||||
// Act & Assert
|
||||
var ex = Assert.Throws<InvalidOperationException>(() => routeCollection.GetVirtualPath(virtualPathContext));
|
||||
Assert.Equal("The supplied route name 'ambiguousRoute' is ambiguous and matched more than one route.", ex.Message);
|
||||
Assert.Equal(
|
||||
"The supplied route name 'ambiguousRoute' is ambiguous and matched more than one route.",
|
||||
ex.Message);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -526,6 +587,75 @@ namespace Microsoft.AspNet.Routing
|
|||
route3.Verify(r => r.GetVirtualPath(It.IsAny<VirtualPathContext>()), Times.Once());
|
||||
}
|
||||
|
||||
// "Integration" tests for RouteCollection
|
||||
|
||||
public static IEnumerable<object[]> IntegrationTestData
|
||||
{
|
||||
get
|
||||
{
|
||||
yield return new object[] {
|
||||
"{controller}/{action}",
|
||||
new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } },
|
||||
"home/index",
|
||||
true };
|
||||
|
||||
yield return new object[] {
|
||||
"{controller}/{action}/",
|
||||
new RouteValueDictionary { { "controller", "Home" }, { "action", "Index" } },
|
||||
"Home/Index",
|
||||
false };
|
||||
|
||||
yield return new object[] {
|
||||
"api/{action}/",
|
||||
new RouteValueDictionary { { "action", "Create" } },
|
||||
"api/create",
|
||||
true };
|
||||
|
||||
yield return new object[] {
|
||||
"api/{action}/{id}",
|
||||
new RouteValueDictionary {
|
||||
{ "action", "Create" },
|
||||
{ "id", "23" },
|
||||
{ "Param1", "Value1" },
|
||||
{ "Param2", "Value2" } },
|
||||
"api/create/23?Param1=Value1&Param2=Value2",
|
||||
true };
|
||||
|
||||
yield return new object[] {
|
||||
"api/{action}/{id}",
|
||||
new RouteValueDictionary {
|
||||
{ "action", "Create" },
|
||||
{ "id", "23" },
|
||||
{ "Param1", "Value1" },
|
||||
{ "Param2", "Value2" } },
|
||||
"api/Create/23?Param1=Value1&Param2=Value2",
|
||||
false };
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("IntegrationTestData")]
|
||||
public void GetVirtualPath_Success(
|
||||
string template,
|
||||
RouteValueDictionary values,
|
||||
string expectedUrl,
|
||||
bool lowercaseUrls
|
||||
)
|
||||
{
|
||||
// Arrange
|
||||
var routeCollection = new RouteCollection();
|
||||
var route = CreateTemplateRoute(template);
|
||||
routeCollection.Add(route);
|
||||
var context = CreateVirtualPathContext(values, options: GetRouteOptions(lowercaseUrls));
|
||||
|
||||
// Act
|
||||
var path = routeCollection.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsBound);
|
||||
Assert.Equal(expectedUrl, path);
|
||||
}
|
||||
|
||||
private static async Task<TestSink> SetUp(bool enabled, bool handled)
|
||||
{
|
||||
// Arrange
|
||||
|
|
@ -615,6 +745,18 @@ namespace Microsoft.AspNet.Routing
|
|||
return target.Object;
|
||||
}
|
||||
|
||||
private static TemplateRoute CreateTemplateRoute(string template)
|
||||
{
|
||||
var target = new Mock<IRouter>(MockBehavior.Strict);
|
||||
target
|
||||
.Setup(e => e.GetVirtualPath(It.IsAny<VirtualPathContext>()))
|
||||
.Callback<VirtualPathContext>(c => c.IsBound = true)
|
||||
.Returns<VirtualPathContext>(rc => null);
|
||||
|
||||
var resolverMock = new Mock<IInlineConstraintResolver>();
|
||||
return new TemplateRoute(target.Object, template, resolverMock.Object);
|
||||
}
|
||||
|
||||
private static VirtualPathContext CreateVirtualPathContext(
|
||||
string routeName = null,
|
||||
ILoggerFactory loggerFactory = null,
|
||||
|
|
@ -646,6 +788,23 @@ namespace Microsoft.AspNet.Routing
|
|||
return new VirtualPathContext(context.Object, null, null, routeName);
|
||||
}
|
||||
|
||||
private static VirtualPathContext CreateVirtualPathContext(
|
||||
RouteValueDictionary values,
|
||||
RouteOptions options = null)
|
||||
{
|
||||
var optionsAccessor = new Mock<IOptions<RouteOptions>>(MockBehavior.Strict);
|
||||
optionsAccessor.SetupGet(o => o.Options).Returns(options);
|
||||
|
||||
var context = new Mock<HttpContext>(MockBehavior.Strict);
|
||||
context.Setup(m => m.RequestServices.GetService(typeof(IOptions<RouteOptions>)))
|
||||
.Returns(optionsAccessor.Object);
|
||||
context.Setup(m => m.RequestServices.GetService(typeof(ILoggerFactory)))
|
||||
.Returns(NullLoggerFactory.Instance);
|
||||
|
||||
|
||||
return new VirtualPathContext(context.Object, null, values);
|
||||
}
|
||||
|
||||
private static RouteContext CreateRouteContext(
|
||||
string requestPath,
|
||||
ILoggerFactory loggerFactory = null,
|
||||
|
|
@ -697,6 +856,14 @@ namespace Microsoft.AspNet.Routing
|
|||
|
||||
return target;
|
||||
}
|
||||
|
||||
private static RouteOptions GetRouteOptions(bool lowerCaseUrls = false, bool useBestEffortLinkGeneration = true)
|
||||
{
|
||||
var routeOptions = new RouteOptions();
|
||||
routeOptions.LowercaseUrls = lowerCaseUrls;
|
||||
routeOptions.UseBestEffortLinkGeneration = useBestEffortLinkGeneration;
|
||||
return routeOptions;
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
Loading…
Reference in New Issue