RouteConstraints Step II + III
Include Url Generation support Add unit tests Clean issues found by unit tests
This commit is contained in:
parent
2b87a625d9
commit
77ef7a5cde
|
|
@ -17,7 +17,7 @@ namespace Microsoft.AspNet.Routing
|
|||
return BuildConstraintsCore(inputConstraints, routeTemplate);
|
||||
}
|
||||
|
||||
public static IDictionary<string, IRouteConstraint>
|
||||
private static IDictionary<string, IRouteConstraint>
|
||||
BuildConstraintsCore(IDictionary<string, object> inputConstraints, string routeTemplate)
|
||||
{
|
||||
if (inputConstraints == null || inputConstraints.Count == 0)
|
||||
|
|
@ -31,7 +31,7 @@ namespace Microsoft.AspNet.Routing
|
|||
{
|
||||
var constraint = kvp.Value as IRouteConstraint;
|
||||
|
||||
if (constraint == null)
|
||||
if (constraint == null)
|
||||
{
|
||||
var regexPattern = kvp.Value as string;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,12 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
|
||||
namespace Microsoft.AspNet.Routing
|
||||
{
|
||||
public static class RouteConstraintMatcher
|
||||
{
|
||||
public static bool Match([NotNull] IDictionary<string, IRouteConstraint> constraints,
|
||||
public static bool Match(IDictionary<string, IRouteConstraint> constraints,
|
||||
[NotNull] IDictionary<string, object> routeValues,
|
||||
[NotNull] HttpContext httpContext,
|
||||
[NotNull] IRouter route,
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
private readonly IDictionary<string, object> _defaults;
|
||||
private readonly IDictionary<string, IRouteConstraint> _constraints;
|
||||
private readonly IRouter _target;
|
||||
private readonly Template _parsedTemplate;
|
||||
private readonly string _routeTemplate;
|
||||
private readonly TemplateMatcher _matcher;
|
||||
private readonly TemplateBinder _binder;
|
||||
|
|
@ -30,10 +29,10 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
_constraints = RouteConstraintBuilder.BuildConstraints(constraints, _routeTemplate);
|
||||
|
||||
// The parser will throw for invalid routes.
|
||||
_parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
||||
var parsedTemplate = TemplateParser.Parse(RouteTemplate);
|
||||
|
||||
_matcher = new TemplateMatcher(_parsedTemplate);
|
||||
_binder = new TemplateBinder(_parsedTemplate, _defaults);
|
||||
_matcher = new TemplateMatcher(parsedTemplate);
|
||||
_binder = new TemplateBinder(parsedTemplate, _defaults);
|
||||
}
|
||||
|
||||
public IDictionary<string, object> Defaults
|
||||
|
|
@ -46,6 +45,11 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
get { return _routeTemplate; }
|
||||
}
|
||||
|
||||
public IDictionary<string, IRouteConstraint> Constraints
|
||||
{
|
||||
get { return _constraints; }
|
||||
}
|
||||
|
||||
public async virtual Task RouteAsync([NotNull] RouteContext context)
|
||||
{
|
||||
var requestPath = context.RequestPath;
|
||||
|
|
@ -54,7 +58,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
requestPath = requestPath.Substring(1);
|
||||
}
|
||||
|
||||
var values = _matcher.Match(requestPath, _defaults);
|
||||
var values = _matcher.Match(requestPath, Defaults);
|
||||
if (values == null)
|
||||
{
|
||||
// If we got back a null value set, that means the URI did not match
|
||||
|
|
@ -65,7 +69,7 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
// Not currently doing anything to clean this up if it's not a match. Consider hardening this.
|
||||
context.Values = values;
|
||||
|
||||
if (RouteConstraintMatcher.Match(_constraints,
|
||||
if (RouteConstraintMatcher.Match(Constraints,
|
||||
values,
|
||||
context.HttpContext,
|
||||
this,
|
||||
|
|
@ -85,6 +89,15 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!RouteConstraintMatcher.Match(Constraints,
|
||||
values,
|
||||
context.Context,
|
||||
this,
|
||||
RouteDirection.UrlGeneration))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
// Validate that the target can accept these values.
|
||||
var path = _target.GetVirtualPath(context);
|
||||
if (path != null)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,19 @@
|
|||
using Microsoft.AspNet.Abstractions;
|
||||
using Moq;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Tests
|
||||
{
|
||||
public static class RouteConstraintExtensions
|
||||
{
|
||||
public static bool EasyMatch(this IRouteConstraint constraint,
|
||||
string routeKey,
|
||||
RouteValueDictionary values)
|
||||
{
|
||||
return constraint.Match(httpContext: new Mock<HttpContext>().Object,
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeKey: routeKey,
|
||||
values: values,
|
||||
routeDirection: RouteDirection.IncomingRequest);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Tests
|
||||
{
|
||||
public class ConstraintMatcherTests
|
||||
{
|
||||
private class PassConstraint : IRouteConstraint
|
||||
{
|
||||
public bool Match(HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
IDictionary<string, object> values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
private class FailConstraint : IRouteConstraint
|
||||
{
|
||||
public bool Match(HttpContext httpContext,
|
||||
IRouter route,
|
||||
string routeKey,
|
||||
IDictionary<string, object> values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReturnsTrueOnValidConstraints()
|
||||
{
|
||||
var constraints = new Dictionary<string, IRouteConstraint>
|
||||
{
|
||||
{"a", new PassConstraint()},
|
||||
{"b", new PassConstraint()}
|
||||
};
|
||||
|
||||
var routeValueDictionary = new RouteValueDictionary(new {a = "value", b = "value"});
|
||||
|
||||
Assert.True(RouteConstraintMatcher.Match(
|
||||
constraints: constraints,
|
||||
routeValues: routeValueDictionary,
|
||||
httpContext: new Mock<HttpContext>().Object,
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeDirection: RouteDirection.IncomingRequest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReturnsFalseOnInvalidConstraintsThatDontMatch()
|
||||
{
|
||||
var constraints = new Dictionary<string, IRouteConstraint>
|
||||
{
|
||||
{"a", new FailConstraint()},
|
||||
{"b", new FailConstraint()}
|
||||
};
|
||||
|
||||
var routeValueDictionary = new RouteValueDictionary(new { c = "value", d = "value" });
|
||||
|
||||
Assert.False(RouteConstraintMatcher.Match(
|
||||
constraints: constraints,
|
||||
routeValues: routeValueDictionary,
|
||||
httpContext: new Mock<HttpContext>().Object,
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeDirection: RouteDirection.IncomingRequest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReturnsFalseOnInvalidConstraintsThatMatch()
|
||||
{
|
||||
var constraints = new Dictionary<string, IRouteConstraint>
|
||||
{
|
||||
{"a", new FailConstraint()},
|
||||
{"b", new FailConstraint()}
|
||||
};
|
||||
|
||||
var routeValueDictionary = new RouteValueDictionary(new { a = "value", b = "value" });
|
||||
|
||||
Assert.False(RouteConstraintMatcher.Match(
|
||||
constraints: constraints,
|
||||
routeValues: routeValueDictionary,
|
||||
httpContext: new Mock<HttpContext>().Object,
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeDirection: RouteDirection.IncomingRequest));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void ReturnsTrueOnNullInput()
|
||||
{
|
||||
Assert.True(RouteConstraintMatcher.Match(
|
||||
constraints: null,
|
||||
routeValues: new RouteValueDictionary(),
|
||||
httpContext: new Mock<HttpContext>().Object,
|
||||
route: new Mock<IRouter>().Object,
|
||||
routeDirection: RouteDirection.IncomingRequest));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,132 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using Moq;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Tests
|
||||
{
|
||||
public class ConstraintsBuilderTests
|
||||
{
|
||||
public static IEnumerable<object> EmptyAndNullDictionary
|
||||
{
|
||||
get
|
||||
{
|
||||
return new[]
|
||||
{
|
||||
new Object[]
|
||||
{
|
||||
null,
|
||||
},
|
||||
|
||||
new Object[]
|
||||
{
|
||||
new Dictionary<string, object>(),
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("EmptyAndNullDictionary")]
|
||||
public void ConstraintBuilderReturnsNull_OnNullOrEmptyInput(IDictionary<string, object> input)
|
||||
{
|
||||
var result = RouteConstraintBuilder.BuildConstraints(input);
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData("EmptyAndNullDictionary")]
|
||||
public void ConstraintBuilderWithTemplateReturnsNull_OnNullOrEmptyInput(IDictionary<string, object> input)
|
||||
{
|
||||
var result = RouteConstraintBuilder.BuildConstraints(input, "{controller}");
|
||||
|
||||
Assert.Null(result);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithConstraintsThatIsAStringCreatesARegex()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = "abc" });
|
||||
var constraintDictionary = RouteConstraintBuilder.BuildConstraints(dictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, constraintDictionary.Count);
|
||||
Assert.Equal("controller", constraintDictionary.First().Key);
|
||||
|
||||
var constraint = constraintDictionary["controller"];
|
||||
|
||||
Assert.IsType<RegexConstraint>(constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithConstraintsThatIsCustomConstraint_IsPassThrough()
|
||||
{
|
||||
// Arrange
|
||||
var originalConstraint = new Mock<IRouteConstraint>().Object;
|
||||
|
||||
var dictionary = new RouteValueDictionary(new { controller = originalConstraint });
|
||||
var constraintDictionary = RouteConstraintBuilder.BuildConstraints(dictionary);
|
||||
|
||||
// Assert
|
||||
Assert.Equal(1, constraintDictionary.Count);
|
||||
Assert.Equal("controller", constraintDictionary.First().Key);
|
||||
|
||||
var constraint = constraintDictionary["controller"];
|
||||
|
||||
Assert.Equal(originalConstraint, constraint);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetRouteDataWithConstraintsThatIsNotStringOrCustomConstraint_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = new RouteValueDictionary() });
|
||||
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => RouteConstraintBuilder.BuildConstraints(dictionary),
|
||||
"The constraint entry 'controller' must have a string value or be of a type which implements '" +
|
||||
typeof(IRouteConstraint) + "'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RouteTemplateGetRouteDataWithConstraintsThatIsNotStringOrCustomConstraint_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = new RouteValueDictionary() });
|
||||
|
||||
ExceptionAssert.Throws<InvalidOperationException>(
|
||||
() => RouteConstraintBuilder.BuildConstraints(dictionary, "{controller}/{action}"),
|
||||
"The constraint entry 'controller' on the route with route template " +
|
||||
"'{controller}/{action}' must have a string value or be of a type which implements '" +
|
||||
typeof(IRouteConstraint) + "'.");
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("abc", "abc", true)]
|
||||
[InlineData("Abc", "abc", true)]
|
||||
[InlineData("Abc ", "abc", false)]
|
||||
[InlineData("Abcd", "abc", false)]
|
||||
[InlineData("Abc", " abc", false)]
|
||||
public void StringConstraintsMatchesWholeValueCaseInsensitively(string routeValue,
|
||||
string constraintValue,
|
||||
bool shouldMatch)
|
||||
{
|
||||
// Arrange
|
||||
var dictionary = new RouteValueDictionary(new { controller = routeValue });
|
||||
|
||||
var constraintDictionary = RouteConstraintBuilder.BuildConstraints(
|
||||
new RouteValueDictionary(new { controller = constraintValue }));
|
||||
var constraint = constraintDictionary["controller"];
|
||||
|
||||
Assert.Equal(shouldMatch,
|
||||
constraint.EasyMatch("controller", dictionary));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,60 @@
|
|||
using System.Text.RegularExpressions;
|
||||
using Xunit;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Tests
|
||||
{
|
||||
public class RegexConstraintTests
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("abc", "abc", true)]
|
||||
[InlineData("Abc", "abc", true)]
|
||||
[InlineData("Abc ", "abc", true)]
|
||||
[InlineData("Abcd", "abc", true)]
|
||||
[InlineData("^Abcd", "abc", true)]
|
||||
[InlineData("Abc", " abc", false)]
|
||||
public void RegexConstraintDoesNotPrepend(string routeValue,
|
||||
string constraintValue,
|
||||
bool shouldMatch)
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new RegexConstraint(constraintValue);
|
||||
var values = new RouteValueDictionary(new {controller = routeValue});
|
||||
|
||||
// Assert
|
||||
Assert.Equal(shouldMatch, constraint.EasyMatch("controller", values));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegexConstraintCanTakeARegex_SuccessulMatch()
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new RegexConstraint(new Regex("^abc$"));
|
||||
var values = new RouteValueDictionary(new { controller = "abc"});
|
||||
|
||||
// Assert
|
||||
Assert.True(constraint.EasyMatch("controller", values));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegexConstraintFailsIfKeyIsNotFound()
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new RegexConstraint(new Regex("^abc$"));
|
||||
var values = new RouteValueDictionary(new { action = "abc" });
|
||||
|
||||
// Assert
|
||||
Assert.False(constraint.EasyMatch("controller", values));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegexConstraintCanTakeARegex_FailedMatch()
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new RegexConstraint(new Regex("^abc$"));
|
||||
var values = new RouteValueDictionary(new { controller = "Abc" });
|
||||
|
||||
// Assert
|
||||
Assert.False(constraint.EasyMatch("controller", values));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved. See License.txt in the project root for license information.
|
||||
|
||||
#if NET45
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using Microsoft.AspNet.Testing;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNet.Abstractions;
|
||||
using Moq;
|
||||
|
|
@ -115,7 +116,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}");
|
||||
var context = CreateVirtualPathContext(new {controller = "Home"});
|
||||
var context = CreateVirtualPathContext(new { controller = "Home" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
|
@ -160,7 +161,7 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}");
|
||||
var context = CreateVirtualPathContext(new { action = "Index"}, new { controller = "Home" });
|
||||
var context = CreateVirtualPathContext(new { action = "Index" }, new { controller = "Home" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
|
@ -189,6 +190,48 @@ namespace Microsoft.AspNet.Routing.Template.Tests
|
|||
|
||||
#endregion
|
||||
|
||||
#region Route Registration
|
||||
|
||||
[Fact]
|
||||
public void RegisteringRouteWithInvalidConstraints_Throws()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new RouteCollection();
|
||||
collection.DefaultHandler = new Mock<IRouter>().Object;
|
||||
|
||||
// Assert
|
||||
ExceptionAssert.Throws<InvalidOperationException>(() => collection.MapRoute("{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new { controller = "a.*", action = new Object() }),
|
||||
"The constraint entry 'action' on the route with route template '{controller}/{action}' " +
|
||||
"must have a string value or be of a type which implements '" +
|
||||
typeof(IRouteConstraint) + "'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RegisteringRouteWithTwoConstraints()
|
||||
{
|
||||
// Arrange
|
||||
var collection = new RouteCollection();
|
||||
collection.DefaultHandler = new Mock<IRouter>().Object;
|
||||
|
||||
var mockConstraint = new Mock<IRouteConstraint>().Object;
|
||||
|
||||
collection.MapRoute("{controller}/{action}",
|
||||
defaults: null,
|
||||
constraints: new {controller = "a.*", action = mockConstraint});
|
||||
|
||||
var constraints = ((TemplateRoute) collection[0]).Constraints;
|
||||
|
||||
// Assert
|
||||
Assert.Equal(2, constraints.Count);
|
||||
Assert.IsType<RegexConstraint>(constraints["controller"]);
|
||||
Assert.Equal(mockConstraint, constraints["action"]);
|
||||
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private static TemplateRoute CreateRoute(string template, bool accept = true)
|
||||
{
|
||||
return new TemplateRoute(CreateTarget(accept), template);
|
||||
|
|
|
|||
Loading…
Reference in New Issue