Support multiple policies for a key with RoutePatternFactory.Pattern (#6593)

This commit is contained in:
James Newton-King 2019-01-12 16:00:42 +13:00 committed by GitHub
parent 7d5067519f
commit accbcebac0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 133 additions and 7 deletions

View File

@ -1,7 +1,8 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
@ -51,6 +52,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the parsed route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
public static RoutePattern Parse(string pattern, object defaults, object parameterPolicies)
@ -78,6 +80,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the parsed route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="requiredValues">
/// Route values that can be substituted for parameters in the route pattern. See remarks on <see cref="RoutePattern.RequiredValues"/>.
@ -138,6 +141,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
@ -168,6 +172,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
@ -229,6 +234,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
@ -259,6 +265,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
/// Additional parameter policies to associated with the route pattern. May be null.
/// The provided object will be converted to key-value pairs using <see cref="RouteValueDictionary"/>
/// and then merged into the route pattern.
/// Multiple policies can be specified for a key by providing a collection as the value.
/// </param>
/// <param name="segments">The collection of segments.</param>
/// <returns>The <see cref="RoutePattern"/>.</returns>
@ -312,12 +319,33 @@ namespace Microsoft.AspNetCore.Routing.Patterns
foreach (var kvp in parameterPolicies)
{
updatedParameterPolicies.Add(kvp.Key, new List<RoutePatternParameterPolicyReference>()
var policyReferences = new List<RoutePatternParameterPolicyReference>();
if (kvp.Value is IParameterPolicy parameterPolicy)
{
kvp.Value is IParameterPolicy parameterPolicy
? ParameterPolicy(parameterPolicy)
: Constraint(kvp.Value), // Constraint will convert string values into regex constraints
});
policyReferences.Add(ParameterPolicy(parameterPolicy));
}
else if (kvp.Value is string)
{
// Constraint will convert string values into regex constraints
policyReferences.Add(Constraint(kvp.Value));
}
else if (kvp.Value is IEnumerable multiplePolicies)
{
foreach (var item in multiplePolicies)
{
// Constraint will convert string values into regex constraints
policyReferences.Add(item is IParameterPolicy p ? ParameterPolicy(p) : Constraint(item));
}
}
else
{
throw new InvalidOperationException(Resources.FormatRoutePattern_InvalidConstraintReference(
kvp.Value ?? "null",
typeof(IRouteConstraint)));
}
updatedParameterPolicies.Add(kvp.Key, policyReferences);
}
}

View File

@ -1,4 +1,4 @@
// Copyright (c) .NET Foundation. All rights reserved.
// Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
@ -218,6 +218,104 @@ namespace Microsoft.AspNetCore.Routing.Patterns
});
}
[Fact]
public void Pattern_ExtraConstraints_MultipleConstraintsForKey()
{
// Arrange
var template = "{a}/{b}/{c}";
var defaults = new { };
var constraints = new { d = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
var original = RoutePatternFactory.Parse(template);
// Act
var actual = RoutePatternFactory.Pattern(
original.RawText,
defaults,
constraints,
original.PathSegments);
// Assert
Assert.Collection(
actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal("d", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
});
}
[Fact]
public void Pattern_ExtraConstraints_MergeMultipleConstraintsForKey()
{
// Arrange
var template = "{a:int}/{b}/{c:int}";
var defaults = new { };
var constraints = new { b = "fizz", c = new object[] { new RegexRouteConstraint("foo"), new RegexRouteConstraint("bar"), "baz" } };
var original = RoutePatternFactory.Parse(template);
// Act
var actual = RoutePatternFactory.Pattern(
original.RawText,
defaults,
constraints,
original.PathSegments);
// Assert
Assert.Collection(
actual.ParameterPolicies.OrderBy(kvp => kvp.Key),
kvp =>
{
Assert.Equal("a", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("int", c.Content));
},
kvp =>
{
Assert.Equal("b", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("^(fizz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()));
},
kvp =>
{
Assert.Equal("c", kvp.Key);
Assert.Collection(
kvp.Value,
c => Assert.Equal("foo", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("bar", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("^(baz)$", Assert.IsType<RegexRouteConstraint>(c.ParameterPolicy).Constraint.ToString()),
c => Assert.Equal("int", c.Content));
});
}
[Fact]
public void Pattern_ExtraConstraints_NestedArray_Throws()
{
// Arrange
var template = "{a}/{b}/{c:int}";
var defaults = new { };
var constraints = new { c = new object[] { new object[0] } };
var original = RoutePatternFactory.Parse(template);
// Act & Assert
Assert.Throws<InvalidOperationException>(() =>
{
RoutePatternFactory.Pattern(
original.RawText,
defaults,
constraints,
original.PathSegments);
});
}
[Fact]
public void Pattern_ExtraConstraints_RouteConstraint()
{