Fixing the issue #123.
Added OptionalRouteConstraint class to take care of optional inline parameter. It will create the OptionalRouteConstraint for a inline parameter that is optional with real constraint on the parameter as inner constraint of OptionalRouteConstraint.
This commit is contained in:
parent
0e3e53dcfe
commit
f549a550a9
|
|
@ -0,0 +1,40 @@
|
|||
// Copyright (c) Microsoft Open Technologies, Inc. All rights reserved.
|
||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNet.Http;
|
||||
|
||||
namespace Microsoft.AspNet.Routing.Constraints
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines a constraint on an optional parameter. If the parameter is present, then it is constrained by InnerConstraint.
|
||||
/// </summary>
|
||||
public class OptionalRouteConstraint : IRouteConstraint
|
||||
{
|
||||
public OptionalRouteConstraint([NotNull] IRouteConstraint innerConstraint)
|
||||
{
|
||||
InnerConstraint = innerConstraint;
|
||||
}
|
||||
|
||||
public IRouteConstraint InnerConstraint { get; }
|
||||
|
||||
public bool Match([NotNull] HttpContext httpContext,
|
||||
[NotNull] IRouter route,
|
||||
[NotNull] string routeKey,
|
||||
[NotNull] IDictionary<string, object> values,
|
||||
RouteDirection routeDirection)
|
||||
{
|
||||
object value;
|
||||
if (values.TryGetValue(routeKey, out value))
|
||||
{
|
||||
return InnerConstraint.Match(httpContext,
|
||||
route,
|
||||
routeKey,
|
||||
values,
|
||||
routeDirection);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -20,7 +20,7 @@ namespace Microsoft.AspNet.Routing
|
|||
private readonly string _displayName;
|
||||
|
||||
private readonly Dictionary<string, List<IRouteConstraint>> _constraints;
|
||||
|
||||
private readonly HashSet<string> _optionalParameters;
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="RouteConstraintBuilder"/> instance.
|
||||
/// </summary>
|
||||
|
|
@ -34,6 +34,7 @@ namespace Microsoft.AspNet.Routing
|
|||
_displayName = displayName;
|
||||
|
||||
_constraints = new Dictionary<string, List<IRouteConstraint>>(StringComparer.OrdinalIgnoreCase);
|
||||
_optionalParameters = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -55,7 +56,15 @@ namespace Microsoft.AspNet.Routing
|
|||
constraint = new CompositeRouteConstraint(kvp.Value.ToArray());
|
||||
}
|
||||
|
||||
constraints.Add(kvp.Key, constraint);
|
||||
if (_optionalParameters.Contains(kvp.Key))
|
||||
{
|
||||
var optionalConstraint = new OptionalRouteConstraint(constraint);
|
||||
constraints.Add(kvp.Key, optionalConstraint);
|
||||
}
|
||||
else
|
||||
{
|
||||
constraints.Add(kvp.Key, constraint);
|
||||
}
|
||||
}
|
||||
|
||||
return constraints;
|
||||
|
|
@ -123,6 +132,15 @@ namespace Microsoft.AspNet.Routing
|
|||
Add(key, constraint);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Sets the given key as optional.
|
||||
/// </summary>
|
||||
/// <param name="key">The key.</param>
|
||||
public void SetOptional([NotNull] string key)
|
||||
{
|
||||
_optionalParameters.Add(key);
|
||||
}
|
||||
|
||||
private void Add(string key, IRouteConstraint constraint)
|
||||
{
|
||||
List<IRouteConstraint> list;
|
||||
|
|
|
|||
|
|
@ -274,10 +274,15 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
|
||||
foreach (var parameter in parsedTemplate.Parameters)
|
||||
{
|
||||
if (parameter.IsOptional)
|
||||
{
|
||||
constraintBuilder.SetOptional(parameter.Name);
|
||||
}
|
||||
|
||||
foreach (var inlineConstraint in parameter.InlineConstraints)
|
||||
{
|
||||
constraintBuilder.AddResolvedConstraint(parameter.Name, inlineConstraint.Constraint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return constraintBuilder.Build();
|
||||
|
|
|
|||
|
|
@ -102,6 +102,53 @@ namespace Microsoft.AspNet.Routing
|
|||
"of type 'DefaultInlineConstraintResolver'.");
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddResolvedConstraint_ForOptionalParameter()
|
||||
{
|
||||
var builder = CreateBuilder("{controller}/{action}/{id}");
|
||||
builder.SetOptional("id");
|
||||
builder.AddResolvedConstraint("id", "int");
|
||||
|
||||
var result = builder.Build();
|
||||
Assert.Equal(1, result.Count);
|
||||
Assert.Equal("id", result.First().Key);
|
||||
Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddResolvedConstraint_SetOptionalParameter_AfterAddingTheParameter()
|
||||
{
|
||||
var builder = CreateBuilder("{controller}/{action}/{id}");
|
||||
builder.AddResolvedConstraint("id", "int");
|
||||
builder.SetOptional("id");
|
||||
|
||||
var result = builder.Build();
|
||||
Assert.Equal(1, result.Count);
|
||||
Assert.Equal("id", result.First().Key);
|
||||
Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void AddResolvedConstraint_And_AddConstraint_ForOptionalParameter()
|
||||
{
|
||||
var builder = CreateBuilder("{controller}/{action}/{name}");
|
||||
builder.SetOptional("name");
|
||||
builder.AddResolvedConstraint("name", "alpha");
|
||||
var minLenConstraint = new MinLengthRouteConstraint(10);
|
||||
builder.AddConstraint("name", minLenConstraint);
|
||||
|
||||
var result = builder.Build();
|
||||
Assert.Equal(1, result.Count);
|
||||
Assert.Equal("name", result.First().Key);
|
||||
Assert.IsType<OptionalRouteConstraint>(Assert.Single(result).Value);
|
||||
var optionalConstraint = (OptionalRouteConstraint)result.First().Value;
|
||||
var compositeConstraint = Assert.IsType<CompositeRouteConstraint>(optionalConstraint.InnerConstraint); ;
|
||||
Assert.Equal(compositeConstraint.Constraints.Count(), 2);
|
||||
|
||||
Assert.Single(compositeConstraint.Constraints, c => c is MinLengthRouteConstraint);
|
||||
Assert.Single(compositeConstraint.Constraints, c => c is AlphaRouteConstraint);
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData("abc", "abc", true)] // simple case
|
||||
[InlineData("abc", "bbb|abc", true)] // Regex or
|
||||
|
|
|
|||
|
|
@ -224,6 +224,82 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.NotSame(route.DataTokens, context.RouteData.DataTokens);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstrait_OptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:int?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index/5");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeValues);
|
||||
Assert.True(routeValues.ContainsKey("id"));
|
||||
Assert.Equal("5", routeValues["id"]);
|
||||
|
||||
Assert.True(context.RouteData.Values.ContainsKey("id"));
|
||||
Assert.Equal("5", context.RouteData.Values["id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstrait_OptionalParameter_NotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:int?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.NotNull(routeValues);
|
||||
Assert.False(routeValues.ContainsKey("id"));
|
||||
|
||||
Assert.False(context.RouteData.Values.ContainsKey("id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_MergesExistingRouteData_PassedToConstraint()
|
||||
{
|
||||
|
|
@ -463,6 +539,177 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.Equal(0, sink.Writes.Count);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstraint_OptionalParameter()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:int?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index/5");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
Assert.NotEmpty(route.Constraints);
|
||||
Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.True(routeValues.ContainsKey("id"));
|
||||
Assert.Equal("5", routeValues["id"]);
|
||||
|
||||
Assert.True(context.RouteData.Values.ContainsKey("id"));
|
||||
Assert.Equal("5", context.RouteData.Values["id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstraint_OptionalParameter_NotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:int?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
Assert.NotEmpty(route.Constraints);
|
||||
Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.NotNull(routeValues);
|
||||
Assert.False(routeValues.ContainsKey("id"));
|
||||
Assert.False(context.RouteData.Values.ContainsKey("id"));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstraint_OptionalParameter_WithInConstructorConstraint()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:int?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index/5");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var constraints = new Dictionary<string, object>();
|
||||
constraints.Add("id", new RangeRouteConstraint(1, 20));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: constraints,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
Assert.NotEmpty(route.Constraints);
|
||||
Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
|
||||
var innerConstraint = ((OptionalRouteConstraint)route.Constraints["id"]).InnerConstraint;
|
||||
Assert.IsType<CompositeRouteConstraint>(innerConstraint);
|
||||
var compositeConstraint = (CompositeRouteConstraint)innerConstraint;
|
||||
Assert.Equal(compositeConstraint.Constraints.Count<IRouteConstraint>(), 2);
|
||||
|
||||
Assert.Single(compositeConstraint.Constraints, c => c is IntRouteConstraint);
|
||||
Assert.Single(compositeConstraint.Constraints, c => c is RangeRouteConstraint);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.True(context.IsHandled);
|
||||
Assert.True(routeValues.ContainsKey("id"));
|
||||
Assert.Equal("5", routeValues["id"]);
|
||||
|
||||
Assert.True(context.RouteData.Values.ContainsKey("id"));
|
||||
Assert.Equal("5", context.RouteData.Values["id"]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public async Task RouteAsync_InlineConstraint_OptionalParameter_ConstraintFails()
|
||||
{
|
||||
// Arrange
|
||||
var template = "{controller}/{action}/{id:range(1,20)?}";
|
||||
|
||||
var context = CreateRouteContext("/Home/Index/100");
|
||||
|
||||
IDictionary<string, object> routeValues = null;
|
||||
var mockTarget = new Mock<IRouter>(MockBehavior.Strict);
|
||||
mockTarget
|
||||
.Setup(s => s.RouteAsync(It.IsAny<RouteContext>()))
|
||||
.Callback<RouteContext>(ctx =>
|
||||
{
|
||||
routeValues = ctx.RouteData.Values;
|
||||
ctx.IsHandled = true;
|
||||
})
|
||||
.Returns(Task.FromResult(true));
|
||||
|
||||
var route = new TemplateRoute(
|
||||
mockTarget.Object,
|
||||
template,
|
||||
defaults: null,
|
||||
constraints: null,
|
||||
dataTokens: null,
|
||||
inlineConstraintResolver: _inlineConstraintResolver);
|
||||
|
||||
Assert.NotEmpty(route.Constraints);
|
||||
Assert.IsType<OptionalRouteConstraint>(route.Constraints["id"]);
|
||||
|
||||
// Act
|
||||
await route.RouteAsync(context);
|
||||
|
||||
// Assert
|
||||
Assert.False(context.IsHandled);
|
||||
}
|
||||
|
||||
#region Route Matching
|
||||
|
||||
// PathString in HttpAbstractions guarantees a leading slash - so no value in testing other cases.
|
||||
|
|
@ -994,6 +1241,118 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
Assert.Equal(expectedValues.OrderBy(kvp => kvp.Key), constraint.Values.OrderBy(kvp => kvp.Key));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_InlineConstraints_Success()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}/{id:int}");
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", id = 4 });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/4", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_InlineConstraints_NonMatchingvalue()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}/{id:int}");
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", id = "asf" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_InlineConstraints_OptionalParameter_ValuePresent()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}/{id:int?}");
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", id = 98 });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/98", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_InlineConstraints_OptionalParameter_ValueNotPresent()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}/{id:int?}");
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_InlineConstraints_OptionalParameter_ValuePresent_ConstraintFails()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}/{id:int?}");
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", id = "sdfd" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Null(path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_InlineConstraints_CompositeInlineConstraint()
|
||||
{
|
||||
// Arrange
|
||||
var route = CreateRoute("{controller}/{action}/{id:int:range(1,20)}");
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", id = 14 });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/14", path);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void GetVirtualPath_InlineConstraints_CompositeConstraint_FromConstructor()
|
||||
{
|
||||
// Arrange
|
||||
var constraint = new MaxLengthRouteConstraint(20);
|
||||
var route = CreateRoute(
|
||||
template: "{controller}/{action}/{name:alpha}",
|
||||
defaults: null,
|
||||
accept: true,
|
||||
constraints: new { name = constraint });
|
||||
|
||||
var context = CreateVirtualPathContext(
|
||||
values: new { action = "Index", controller = "Home", name = "products" });
|
||||
|
||||
// Act
|
||||
var path = route.GetVirtualPath(context);
|
||||
|
||||
// Assert
|
||||
Assert.Equal("Home/Index/products", path);
|
||||
}
|
||||
|
||||
|
||||
private static VirtualPathContext CreateVirtualPathContext(object values)
|
||||
{
|
||||
return CreateVirtualPathContext(new RouteValueDictionary(values), null);
|
||||
|
|
@ -1263,6 +1622,9 @@ namespace Microsoft.AspNet.Routing.Template
|
|||
{
|
||||
var resolverMock = new Mock<IInlineConstraintResolver>();
|
||||
resolverMock.Setup(o => o.ResolveConstraint("int")).Returns(new IntRouteConstraint());
|
||||
resolverMock.Setup(o => o.ResolveConstraint("range(1,20)")).Returns(new RangeRouteConstraint(1, 20));
|
||||
resolverMock.Setup(o => o.ResolveConstraint("alpha")).Returns(new AlphaRouteConstraint());
|
||||
|
||||
return resolverMock.Object;
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue