From accbcebac0857798335ad59b70e1878d4cdd18cc Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Sat, 12 Jan 2019 16:00:42 +1300 Subject: [PATCH] Support multiple policies for a key with RoutePatternFactory.Pattern (#6593) --- .../src/Patterns/RoutePatternFactory.cs | 40 +++++-- .../Patterns/RoutePatternFactoryTest.cs | 100 +++++++++++++++++- 2 files changed, 133 insertions(+), 7 deletions(-) diff --git a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs index 7b577e25bd..91c8ae8b38 100644 --- a/src/Http/Routing/src/Patterns/RoutePatternFactory.cs +++ b/src/Http/Routing/src/Patterns/RoutePatternFactory.cs @@ -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 /// and then merged into the parsed route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. /// /// The . 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 /// and then merged into the parsed route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. /// /// /// Route values that can be substituted for parameters in the route pattern. See remarks on . @@ -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 /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. /// /// The collection of segments. /// The . @@ -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 /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. /// /// The collection of segments. /// The . @@ -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 /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. /// /// The collection of segments. /// The . @@ -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 /// and then merged into the route pattern. + /// Multiple policies can be specified for a key by providing a collection as the value. /// /// The collection of segments. /// The . @@ -312,12 +319,33 @@ namespace Microsoft.AspNetCore.Routing.Patterns foreach (var kvp in parameterPolicies) { - updatedParameterPolicies.Add(kvp.Key, new List() + var policyReferences = new List(); + + 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); } } diff --git a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs index ef4ac2fc82..faace7a6b0 100644 --- a/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs +++ b/src/Http/Routing/test/UnitTests/Patterns/RoutePatternFactoryTest.cs @@ -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(c.ParameterPolicy).Constraint.ToString()), + c => Assert.Equal("bar", Assert.IsType(c.ParameterPolicy).Constraint.ToString()), + c => Assert.Equal("^(baz)$", Assert.IsType(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(c.ParameterPolicy).Constraint.ToString())); + }, + kvp => + { + Assert.Equal("c", kvp.Key); + Assert.Collection( + kvp.Value, + c => Assert.Equal("foo", Assert.IsType(c.ParameterPolicy).Constraint.ToString()), + c => Assert.Equal("bar", Assert.IsType(c.ParameterPolicy).Constraint.ToString()), + c => Assert.Equal("^(baz)$", Assert.IsType(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(() => + { + RoutePatternFactory.Pattern( + original.RawText, + defaults, + constraints, + original.PathSegments); + }); + } + [Fact] public void Pattern_ExtraConstraints_RouteConstraint() {