diff --git a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj index 99413eb58b..625a26262a 100644 --- a/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj +++ b/benchmarks/Microsoft.AspNetCore.Routing.Performance/Microsoft.AspNetCore.Routing.Performance.csproj @@ -36,6 +36,7 @@ Matching\TreeRouterMatcherBuilder.cs + diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/BoolRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/BoolRouteConstraint.cs index a65e88ef67..c91f7305a4 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/BoolRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/BoolRouteConstraint.cs @@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/CompositeRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/CompositeRouteConstraint.cs index 5acff08294..468699ce8a 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/CompositeRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/CompositeRouteConstraint.cs @@ -39,16 +39,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/DateTimeRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/DateTimeRouteConstraint.cs index 9015dc64be..9105108d00 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/DateTimeRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/DateTimeRouteConstraint.cs @@ -26,16 +26,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/DecimalRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/DecimalRouteConstraint.cs index ff29d98574..808d448706 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/DecimalRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/DecimalRouteConstraint.cs @@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/DoubleRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/DoubleRouteConstraint.cs index e7259d0bf3..0a8064f468 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/DoubleRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/DoubleRouteConstraint.cs @@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/FloatRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/FloatRouteConstraint.cs index 5db0e65c28..eff69c8ad1 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/FloatRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/FloatRouteConstraint.cs @@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/GuidRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/GuidRouteConstraint.cs index 00d451767f..aa1c74ec07 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/GuidRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/GuidRouteConstraint.cs @@ -22,16 +22,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/HttpMethodRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/HttpMethodRouteConstraint.cs index d01140157d..a894acaacb 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/HttpMethodRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/HttpMethodRouteConstraint.cs @@ -41,16 +41,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); @@ -64,6 +54,12 @@ namespace Microsoft.AspNetCore.Routing.Constraints switch (routeDirection) { case RouteDirection.IncomingRequest: + // Only required for constraining incoming requests + if (httpContext == null) + { + throw new ArgumentNullException(nameof(httpContext)); + } + return AllowedMethods.Contains(httpContext.Request.Method, StringComparer.OrdinalIgnoreCase); case RouteDirection.UrlGeneration: diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/IntRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/IntRouteConstraint.cs index 83b08533bd..82f32052ee 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/IntRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/IntRouteConstraint.cs @@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/LengthRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/LengthRouteConstraint.cs index f876c03cbc..f102af6ec4 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/LengthRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/LengthRouteConstraint.cs @@ -77,16 +77,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/LongRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/LongRouteConstraint.cs index a76a4de885..6d12c74d79 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/LongRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/LongRouteConstraint.cs @@ -20,16 +20,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/MaxLengthRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/MaxLengthRouteConstraint.cs index 42dde182ed..a7858477da 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/MaxLengthRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/MaxLengthRouteConstraint.cs @@ -40,16 +40,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/MaxRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/MaxRouteConstraint.cs index e43dac85fe..f20cce6c6c 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/MaxRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/MaxRouteConstraint.cs @@ -34,16 +34,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/MinLengthRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/MinLengthRouteConstraint.cs index 1ea64ae216..7a51bf7412 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/MinLengthRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/MinLengthRouteConstraint.cs @@ -40,16 +40,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/MinRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/MinRouteConstraint.cs index 68357c59e7..701d5c68e8 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/MinRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/MinRouteConstraint.cs @@ -34,16 +34,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/OptionalRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/OptionalRouteConstraint.cs index 3990376410..9a9cca8e42 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/OptionalRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/OptionalRouteConstraint.cs @@ -30,16 +30,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/RangeRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/RangeRouteConstraint.cs index 301a75f15a..cc2487cfcb 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/RangeRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/RangeRouteConstraint.cs @@ -48,16 +48,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/RegexRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/RegexRouteConstraint.cs index fb3d2390fe..c4e83f40e3 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/RegexRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/RegexRouteConstraint.cs @@ -44,16 +44,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/RequiredRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/RequiredRouteConstraint.cs index e03d618565..827f85ea39 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/RequiredRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/RequiredRouteConstraint.cs @@ -24,16 +24,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/Constraints/StringRouteConstraint.cs b/src/Microsoft.AspNetCore.Routing/Constraints/StringRouteConstraint.cs index e7d92ef13c..202fcbb02c 100644 --- a/src/Microsoft.AspNetCore.Routing/Constraints/StringRouteConstraint.cs +++ b/src/Microsoft.AspNetCore.Routing/Constraints/StringRouteConstraint.cs @@ -31,16 +31,6 @@ namespace Microsoft.AspNetCore.Routing.Constraints /// public bool Match(HttpContext httpContext, IRouter route, string routeKey, RouteValueDictionary values, RouteDirection routeDirection) { - if (httpContext == null) - { - throw new ArgumentNullException(nameof(httpContext)); - } - - if (route == null) - { - throw new ArgumentNullException(nameof(route)); - } - if (routeKey == null) { throw new ArgumentNullException(nameof(routeKey)); diff --git a/src/Microsoft.AspNetCore.Routing/DefaultInlineConstraintResolver.cs b/src/Microsoft.AspNetCore.Routing/DefaultInlineConstraintResolver.cs index ed58e053ba..1ed1f8e1ab 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultInlineConstraintResolver.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultInlineConstraintResolver.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Globalization; -using System.Linq; -using System.Reflection; +using Microsoft.AspNetCore.Routing.Internal; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Routing @@ -18,6 +16,7 @@ namespace Microsoft.AspNetCore.Routing public class DefaultInlineConstraintResolver : IInlineConstraintResolver { private readonly IDictionary _inlineConstraintMap; + private readonly IServiceProvider _serviceProvider; /// /// Initializes a new instance of the class. @@ -25,11 +24,23 @@ namespace Microsoft.AspNetCore.Routing /// /// Accessor for containing the constraints of interest. /// + [Obsolete("This constructor is obsolete. Use DefaultInlineConstraintResolver.ctor(IOptions, IServiceProvider) instead.")] public DefaultInlineConstraintResolver(IOptions routeOptions) { _inlineConstraintMap = routeOptions.Value.ConstraintMap; } + public DefaultInlineConstraintResolver(IOptions routeOptions, IServiceProvider serviceProvider) + { + if (serviceProvider == null) + { + throw new ArgumentNullException(nameof(serviceProvider)); + } + + _inlineConstraintMap = routeOptions.Value.ConstraintMap; + _serviceProvider = serviceProvider; + } + /// /// /// A typical constraint looks like the following @@ -45,112 +56,7 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(inlineConstraint)); } - string constraintKey; - string argumentString; - var indexOfFirstOpenParens = inlineConstraint.IndexOf('('); - if (indexOfFirstOpenParens >= 0 && inlineConstraint.EndsWith(")", StringComparison.Ordinal)) - { - constraintKey = inlineConstraint.Substring(0, indexOfFirstOpenParens); - argumentString = inlineConstraint.Substring(indexOfFirstOpenParens + 1, - inlineConstraint.Length - indexOfFirstOpenParens - 2); - } - else - { - constraintKey = inlineConstraint; - argumentString = null; - } - - Type constraintType; - if (!_inlineConstraintMap.TryGetValue(constraintKey, out constraintType)) - { - // Cannot resolve the constraint key - return null; - } - - if (!typeof(IRouteConstraint).GetTypeInfo().IsAssignableFrom(constraintType.GetTypeInfo())) - { - throw new RouteCreationException( - Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint( - constraintType, constraintKey, typeof(IRouteConstraint).Name)); - } - - try - { - return CreateConstraint(constraintType, argumentString); - } - catch (RouteCreationException) - { - throw; - } - catch (Exception exception) - { - throw new RouteCreationException( - $"An error occurred while trying to create an instance of route constraint '{constraintType.FullName}'.", - exception); - } - } - - internal static IRouteConstraint CreateConstraint(Type constraintType, string argumentString) - { - // No arguments - call the default constructor - if (argumentString == null) - { - return (IRouteConstraint)Activator.CreateInstance(constraintType); - } - - var constraintTypeInfo = constraintType.GetTypeInfo(); - ConstructorInfo activationConstructor = null; - object[] parameters = null; - var constructors = constraintTypeInfo.DeclaredConstructors.ToArray(); - - // If there is only one constructor and it has a single parameter, pass the argument string directly - // This is necessary for the Regex RouteConstraint to ensure that patterns are not split on commas. - if (constructors.Length == 1 && constructors[0].GetParameters().Length == 1) - { - activationConstructor = constructors[0]; - parameters = ConvertArguments(activationConstructor.GetParameters(), new string[] { argumentString }); - } - else - { - var arguments = argumentString.Split(',').Select(argument => argument.Trim()).ToArray(); - - var matchingConstructors = constructors.Where(ci => ci.GetParameters().Length == arguments.Length) - .ToArray(); - var constructorMatches = matchingConstructors.Length; - - if (constructorMatches == 0) - { - throw new RouteCreationException( - Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor( - constraintTypeInfo.Name, arguments.Length)); - } - else if (constructorMatches == 1) - { - activationConstructor = matchingConstructors[0]; - parameters = ConvertArguments(activationConstructor.GetParameters(), arguments); - } - else - { - throw new RouteCreationException( - Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors( - constraintTypeInfo.Name, arguments.Length)); - } - } - - return (IRouteConstraint)activationConstructor.Invoke(parameters); - } - - private static object[] ConvertArguments(ParameterInfo[] parameterInfos, string[] arguments) - { - var parameters = new object[parameterInfos.Length]; - for (var i = 0; i < parameterInfos.Length; i++) - { - var parameter = parameterInfos[i]; - var parameterType = parameter.ParameterType; - parameters[i] = Convert.ChangeType(arguments[i], parameterType, CultureInfo.InvariantCulture); - } - - return parameters; + return ParameterPolicyActivator.ResolveParameterPolicy(_inlineConstraintMap, _serviceProvider, inlineConstraint, out _); } } } diff --git a/src/Microsoft.AspNetCore.Routing/DefaultParameterPolicyFactory.cs b/src/Microsoft.AspNetCore.Routing/DefaultParameterPolicyFactory.cs index a85f3d826b..d14bd9f113 100644 --- a/src/Microsoft.AspNetCore.Routing/DefaultParameterPolicyFactory.cs +++ b/src/Microsoft.AspNetCore.Routing/DefaultParameterPolicyFactory.cs @@ -3,8 +3,8 @@ using System; using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Patterns; -using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Routing @@ -31,7 +31,7 @@ namespace Microsoft.AspNetCore.Routing if (parameterPolicy is IRouteConstraint routeConstraint) { - return InitializeRouteConstraint(parameter?.IsOptional ?? false, routeConstraint, argument: null); + return InitializeRouteConstraint(parameter?.IsOptional ?? false, routeConstraint); } return parameterPolicy; @@ -44,47 +44,26 @@ namespace Microsoft.AspNetCore.Routing throw new ArgumentNullException(nameof(inlineText)); } - // Example: - // {productId:regex(\d+)} - // - // ParameterName: productId - // value: regex(\d+) - // name: regex - // argument: \d+ - (var name, var argument) = Parse(inlineText); - - if (!_options.ConstraintMap.TryGetValue(name, out var type)) + var parameterPolicy = ParameterPolicyActivator.ResolveParameterPolicy(_options.ConstraintMap, _serviceProvider, inlineText, out var parameterPolicyKey); + if (parameterPolicy == null) { throw new InvalidOperationException(Resources.FormatRoutePattern_ConstraintReferenceNotFound( - name, - typeof(RouteOptions), - nameof(RouteOptions.ConstraintMap))); + parameterPolicyKey, + typeof(RouteOptions), + nameof(RouteOptions.ConstraintMap))); } - if (typeof(IRouteConstraint).IsAssignableFrom(type)) + if (parameterPolicy is IRouteConstraint constraint) { - var constraint = DefaultInlineConstraintResolver.CreateConstraint(type, argument); - return InitializeRouteConstraint(parameter?.IsOptional ?? false, constraint, argument); + return InitializeRouteConstraint(parameter?.IsOptional ?? false, constraint); } - if (typeof(IParameterPolicy).IsAssignableFrom(type)) - { - var parameterPolicy = (IParameterPolicy)_serviceProvider.GetRequiredService(type); - return parameterPolicy; - } - - var message = Resources.FormatRoutePattern_InvalidStringConstraintReference( - type, - name, - typeof(IRouteConstraint), - typeof(IParameterPolicy)); - throw new InvalidOperationException(message); + return parameterPolicy; } private IParameterPolicy InitializeRouteConstraint( bool optional, - IRouteConstraint routeConstraint, - string argument) + IRouteConstraint routeConstraint) { if (optional) { @@ -93,25 +72,5 @@ namespace Microsoft.AspNetCore.Routing return routeConstraint; } - - private (string name, string argument) Parse(string text) - { - string name; - string argument; - var indexOfFirstOpenParens = text.IndexOf('('); - if (indexOfFirstOpenParens >= 0 && text.EndsWith(")", StringComparison.Ordinal)) - { - name = text.Substring(0, indexOfFirstOpenParens); - argument = text.Substring( - indexOfFirstOpenParens + 1, - text.Length - indexOfFirstOpenParens - 2); - } - else - { - name = text; - argument = null; - } - return (name, argument); - } } } diff --git a/src/Microsoft.AspNetCore.Routing/Internal/ParameterPolicyActivator.cs b/src/Microsoft.AspNetCore.Routing/Internal/ParameterPolicyActivator.cs new file mode 100644 index 0000000000..1fc3f1a365 --- /dev/null +++ b/src/Microsoft.AspNetCore.Routing/Internal/ParameterPolicyActivator.cs @@ -0,0 +1,174 @@ +// 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.Generic; +using System.Globalization; +using System.Linq; +using System.Reflection; +using System.Text; +using Microsoft.Extensions.DependencyInjection; + +namespace Microsoft.AspNetCore.Routing.Internal +{ + internal static class ParameterPolicyActivator + { + public static T ResolveParameterPolicy(IDictionary inlineParameterPolicyMap, IServiceProvider serviceProvider, string inlineParameterPolicy, out string parameterPolicyKey) + where T : IParameterPolicy + { + // IServiceProvider could be null + // DefaultInlineConstraintResolver can be created without an IServiceProvider and then call this method + + if (inlineParameterPolicyMap == null) + { + throw new ArgumentNullException(nameof(inlineParameterPolicyMap)); + } + + if (inlineParameterPolicy == null) + { + throw new ArgumentNullException(nameof(inlineParameterPolicy)); + } + + string argumentString; + var indexOfFirstOpenParens = inlineParameterPolicy.IndexOf('('); + if (indexOfFirstOpenParens >= 0 && inlineParameterPolicy.EndsWith(")", StringComparison.Ordinal)) + { + parameterPolicyKey = inlineParameterPolicy.Substring(0, indexOfFirstOpenParens); + argumentString = inlineParameterPolicy.Substring( + indexOfFirstOpenParens + 1, + inlineParameterPolicy.Length - indexOfFirstOpenParens - 2); + } + else + { + parameterPolicyKey = inlineParameterPolicy; + argumentString = null; + } + + if (!inlineParameterPolicyMap.TryGetValue(parameterPolicyKey, out var parameterPolicyType)) + { + return default; + } + + if (!typeof(T).IsAssignableFrom(parameterPolicyType)) + { + throw new RouteCreationException( + Resources.FormatDefaultInlineConstraintResolver_TypeNotConstraint( + parameterPolicyType, parameterPolicyKey, typeof(T).Name)); + } + + try + { + return (T)CreateParameterPolicy(serviceProvider, parameterPolicyType, argumentString); + } + catch (RouteCreationException) + { + throw; + } + catch (Exception exception) + { + throw new RouteCreationException( + $"An error occurred while trying to create an instance of '{parameterPolicyType.FullName}'.", + exception); + } + } + + private static IParameterPolicy CreateParameterPolicy(IServiceProvider serviceProvider, Type parameterPolicyType, string argumentString) + { + ConstructorInfo activationConstructor = null; + object[] parameters = null; + var constructors = parameterPolicyType.GetConstructors(); + + // If there is only one constructor and it has a single parameter, pass the argument string directly + // This is necessary for the Regex RouteConstraint to ensure that patterns are not split on commas. + if (constructors.Length == 1 && GetNonConvertableParameterTypeCount(serviceProvider, constructors[0].GetParameters()) == 1) + { + activationConstructor = constructors[0]; + parameters = ConvertArguments(serviceProvider, activationConstructor.GetParameters(), new string[] { argumentString }); + } + else + { + var arguments = !string.IsNullOrEmpty(argumentString) + ? argumentString.Split(',').Select(argument => argument.Trim()).ToArray() + : Array.Empty(); + + // We want to find the constructors that match the number of passed in arguments + // We either want a single match, or a single best match. The best match is the one with the most + // arguments that can be resolved from DI + // + // For example, ctor(string, IService) will beat ctor(string) + var matchingConstructors = constructors + .Where(ci => GetNonConvertableParameterTypeCount(serviceProvider, ci.GetParameters()) == arguments.Length) + .OrderByDescending(ci => ci.GetParameters().Length) + .ToArray(); + + if (matchingConstructors.Length == 0) + { + throw new RouteCreationException( + Resources.FormatDefaultInlineConstraintResolver_CouldNotFindCtor( + parameterPolicyType.Name, arguments.Length)); + } + else + { + // When there are multiple matching constructors, choose the one with the most service arguments + if (matchingConstructors.Length == 1 + || matchingConstructors[0].GetParameters().Length > matchingConstructors[1].GetParameters().Length) + { + activationConstructor = matchingConstructors[0]; + } + else + { + throw new RouteCreationException( + Resources.FormatDefaultInlineConstraintResolver_AmbiguousCtors( + parameterPolicyType.Name, matchingConstructors[0].GetParameters().Length)); + } + + parameters = ConvertArguments(serviceProvider, activationConstructor.GetParameters(), arguments); + } + } + + return (IParameterPolicy)activationConstructor.Invoke(parameters); + } + + private static int GetNonConvertableParameterTypeCount(IServiceProvider serviceProvider, ParameterInfo[] parameters) + { + if (serviceProvider == null) + { + return parameters.Length; + } + + var count = 0; + for (var i = 0; i < parameters.Length; i++) + { + if (typeof(IConvertible).IsAssignableFrom(parameters[i].ParameterType)) + { + count++; + } + } + + return count; + } + + private static object[] ConvertArguments(IServiceProvider serviceProvider, ParameterInfo[] parameterInfos, string[] arguments) + { + var parameters = new object[parameterInfos.Length]; + var argumentPosition = 0; + for (var i = 0; i < parameterInfos.Length; i++) + { + var parameter = parameterInfos[i]; + var parameterType = parameter.ParameterType; + + if (serviceProvider != null && !typeof(IConvertible).IsAssignableFrom(parameterType)) + { + parameters[i] = serviceProvider.GetRequiredService(parameterType); + } + else + { + parameters[i] = Convert.ChangeType(arguments[argumentPosition], parameterType, CultureInfo.InvariantCulture); + argumentPosition++; + } + } + + return parameters; + } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultInlineConstraintResolverTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultInlineConstraintResolverTest.cs index bba78fb6b2..63aef81de4 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/DefaultInlineConstraintResolverTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultInlineConstraintResolverTest.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Options; using Moq; @@ -324,11 +325,33 @@ namespace Microsoft.AspNetCore.Routing.Tests ex.Message); } - private IInlineConstraintResolver GetInlineConstraintResolver(RouteOptions routeOptions) + [Fact] + public void ResolveConstraint_HasArguments_NoServiceProvider() + { + // Arrange + var routeOptions = new RouteOptions(); + var constraintResolver = GetInlineConstraintResolver(routeOptions, hasServiceProvider: false); + + // Act + var constraint = constraintResolver.ResolveConstraint("regex(ab,1)"); + + // Assert + Assert.IsType(constraint); + } + + private IInlineConstraintResolver GetInlineConstraintResolver(RouteOptions routeOptions, bool hasServiceProvider = true) { var optionsAccessor = new Mock>(); optionsAccessor.SetupGet(o => o.Value).Returns(routeOptions); + + if (hasServiceProvider) + { + return new DefaultInlineConstraintResolver(optionsAccessor.Object, new TestServiceProvider()); + } + +#pragma warning disable CS0618 // Type or member is obsolete return new DefaultInlineConstraintResolver(optionsAccessor.Object); +#pragma warning restore CS0618 // Type or member is obsolete } private class MultiConstructorRouteConstraint : IRouteConstraint diff --git a/test/Microsoft.AspNetCore.Routing.Tests/DefaultParameterPolicyFactoryTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/DefaultParameterPolicyFactoryTest.cs new file mode 100644 index 0000000000..69761f3a0d --- /dev/null +++ b/test/Microsoft.AspNetCore.Routing.Tests/DefaultParameterPolicyFactoryTest.cs @@ -0,0 +1,533 @@ +// 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.Globalization; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Options; +using Xunit; + +namespace Microsoft.AspNetCore.Routing +{ + public class DefaultParameterPolicyFactoryTest + { + [Fact] + public void Create_ThrowsException_IfNoConstraintOrParameterPolicy_FoundInMap() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + // Act + var exception = Assert.Throws( + () => factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), @"notpresent(\d+)")); + + // Assert + Assert.Equal( + $"The constraint reference 'notpresent' could not be resolved to a type. " + + $"Register the constraint type with '{typeof(RouteOptions)}.{nameof(RouteOptions.ConstraintMap)}'.", + exception.Message); + } + + [Fact] + public void Create_ThrowsException_OnInvalidType() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("bad", typeof(string)); + + var services = new ServiceCollection(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var exception = Assert.Throws( + () => factory.Create(RoutePatternFactory.ParameterPart("id"), @"bad")); + + // Assert + Assert.Equal( + $"The constraint type '{typeof(string)}' which is mapped to constraint key 'bad' must implement the '{nameof(IParameterPolicy)}' interface.", + exception.Message); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromRoutePattern_String() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + var parameter = RoutePatternFactory.ParameterPart( + "id", + @default: null, + parameterKind: RoutePatternParameterKind.Standard, + parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), }); + + // Act + var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); + + // Assert + Assert.IsType(parameterPolicy); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromRoutePattern_String_Optional() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + var parameter = RoutePatternFactory.ParameterPart( + "id", + @default: null, + parameterKind: RoutePatternParameterKind.Optional, + parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), }); + + // Act + var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); + + // Assert + var optionalConstraint = Assert.IsType(parameterPolicy); + Assert.IsType(optionalConstraint.InnerConstraint); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + var parameter = RoutePatternFactory.ParameterPart( + "id", + @default: null, + parameterKind: RoutePatternParameterKind.Standard, + parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), }); + + // Act + var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); + + // Assert + Assert.IsType(parameterPolicy); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint_Optional() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + var parameter = RoutePatternFactory.ParameterPart( + "id", + @default: null, + parameterKind: RoutePatternParameterKind.Optional, + parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), }); + + // Act + var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); + + // Assert + var optionalConstraint = Assert.IsType(parameterPolicy); + Assert.IsType(optionalConstraint.InnerConstraint); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromRoutePattern_ParameterPolicy() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + var parameter = RoutePatternFactory.ParameterPart( + "id", + @default: null, + parameterKind: RoutePatternParameterKind.Standard, + parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new CustomParameterPolicy()), }); + + // Act + var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); + + // Assert + Assert.IsType(parameterPolicy); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "int"); + + // Assert + Assert.IsType(parameterPolicy); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraintWithArgument() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "range(1,20)"); + + // Assert + var constraint = Assert.IsType(parameterPolicy); + Assert.Equal(1, constraint.Min); + Assert.Equal(20, constraint.Max); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint_Optional() + { + // Arrange + var factory = GetParameterPolicyFactory(); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "int"); + + // Assert + var optionalConstraint = Assert.IsType(parameterPolicy); + Assert.IsType(optionalConstraint.InnerConstraint); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy"); + + // Assert + Assert.IsType(parameterPolicy); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndServices() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithArguments)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20)"); + + // Assert + var constraint = Assert.IsType(parameterPolicy); + Assert.Equal(20, constraint.Count); + Assert.NotNull(constraint.TestService); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndMultipleServices() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithMultipleArguments)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20,-1)"); + + // Assert + var constraint = Assert.IsType(parameterPolicy); + Assert.Equal(20, constraint.First); + Assert.Equal(-1, constraint.Second); + Assert.NotNull(constraint.TestService1); + Assert.NotNull(constraint.TestService2); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithOnlyServiceArguments() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithOnlyServiceArguments)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy"); + + // Assert + var constraint = Assert.IsType(parameterPolicy); + Assert.NotNull(constraint.TestService1); + Assert.NotNull(constraint.TestService2); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithMultipleMatchingCtors() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithMultpleCtors)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(1)"); + + // Assert + var constraint = Assert.IsType(parameterPolicy); + Assert.NotNull(constraint.TestService); + Assert.Equal(1, constraint.Count); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithAmbigiousMatchingCtors() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithAmbigiousMultpleCtors)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var exception = Assert.Throws( + () => factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(1)")); + + // Assert + Assert.Equal($"The constructor to use for activating the constraint type '{nameof(CustomParameterPolicyWithAmbigiousMultpleCtors)}' is ambiguous. " + + $"Multiple constructors were found with the following number of parameters: 2.", exception.Message); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithSingleArgumentAndServiceArgument() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("regex-service", typeof(RegexInlineRouteConstraintWithService)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), @"regex-service(\\d{1,2})"); + + // Assert + var constraint = Assert.IsType(parameterPolicy); + Assert.NotNull(constraint.TestService); + Assert.Equal("\\\\d{1,2}", constraint.Constraint.ToString()); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicyWithArgumentAndUnresolvedServices_Throw() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customConstraintPolicy", typeof(CustomParameterPolicyWithArguments)); + + var services = new ServiceCollection(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var exception = Assert.Throws( + () => factory.Create(RoutePatternFactory.ParameterPart("id"), "customConstraintPolicy(20)")); + + // Assert + var inner = Assert.IsType(exception.InnerException); + Assert.Equal($"No service for type '{typeof(ITestService).FullName}' has been registered.", inner.Message); + } + + [Fact] + public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy_Optional() + { + // Arrange + var options = new RouteOptions(); + options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy)); + + var services = new ServiceCollection(); + services.AddTransient(); + + var factory = GetParameterPolicyFactory(options, services); + + // Act + var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy"); + + // Assert + Assert.IsType(parameterPolicy); + } + + private DefaultParameterPolicyFactory GetParameterPolicyFactory( + RouteOptions options = null, + ServiceCollection services = null) + { + if (options == null) + { + options = new RouteOptions(); + } + + if (services == null) + { + services = new ServiceCollection(); + } + + return new DefaultParameterPolicyFactory( + Options.Create(options), + services.BuildServiceProvider()); + } + + private class TestRouteConstraint : IRouteConstraint + { + private TestRouteConstraint() { } + + public HttpContext HttpContext { get; private set; } + public IRouter Route { get; private set; } + public string RouteKey { get; private set; } + public RouteValueDictionary Values { get; private set; } + public RouteDirection RouteDirection { get; private set; } + + public static TestRouteConstraint Create() + { + return new TestRouteConstraint(); + } + + public bool Match( + HttpContext httpContext, + IRouter route, + string routeKey, + RouteValueDictionary values, + RouteDirection routeDirection) + { + HttpContext = httpContext; + Route = route; + RouteKey = routeKey; + Values = values; + RouteDirection = routeDirection; + return false; + } + } + } + + public class CustomParameterPolicy : IParameterPolicy + { + } + + public class CustomParameterPolicyWithArguments : IParameterPolicy + { + public CustomParameterPolicyWithArguments(ITestService testService, int count) + { + TestService = testService; + Count = count; + } + + public ITestService TestService { get; } + public int Count { get; } + } + + public class CustomParameterPolicyWithMultpleCtors : IParameterPolicy + { + public CustomParameterPolicyWithMultpleCtors(ITestService testService, int count) + { + TestService = testService; + Count = count; + } + + public CustomParameterPolicyWithMultpleCtors(int count) + : this(testService: null, count) + { + } + + public ITestService TestService { get; } + public int Count { get; } + } + + public class CustomParameterPolicyWithAmbigiousMultpleCtors : IParameterPolicy + { + public CustomParameterPolicyWithAmbigiousMultpleCtors(ITestService testService, int count) + { + TestService = testService; + Count = count; + } + + public CustomParameterPolicyWithAmbigiousMultpleCtors(object testService, int count) + : this(testService: null, count) + { + } + + public CustomParameterPolicyWithAmbigiousMultpleCtors(int count) + : this(testService: null, count) + { + } + + public ITestService TestService { get; } + public int Count { get; } + } + + public class CustomParameterPolicyWithMultipleArguments : IParameterPolicy + { + public CustomParameterPolicyWithMultipleArguments(int first, ITestService testService1, int second, ITestService testService2) + { + First = first; + TestService1 = testService1; + Second = second; + TestService2 = testService2; + } + + public int First { get; } + public ITestService TestService1 { get; } + public int Second { get; } + public ITestService TestService2 { get; } + } + + public class CustomParameterPolicyWithOnlyServiceArguments : IParameterPolicy + { + public CustomParameterPolicyWithOnlyServiceArguments(ITestService testService1, ITestService testService2) + { + TestService1 = testService1; + TestService2 = testService2; + } + + public ITestService TestService1 { get; } + public ITestService TestService2 { get; } + } + + public interface ITestService + { + } + + public class TestService : ITestService + { + + } + + public class RegexInlineRouteConstraintWithService : RegexRouteConstraint + { + public RegexInlineRouteConstraintWithService(string regexPattern, ITestService testService) + : base(regexPattern) + { + TestService = testService; + } + + public ITestService TestService { get; } + } +} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/InlineRouteParameterParserTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/InlineRouteParameterParserTests.cs index 0e570c49cc..ccd44a8318 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/InlineRouteParameterParserTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/InlineRouteParameterParserTests.cs @@ -968,7 +968,7 @@ namespace Microsoft.AspNetCore.Routing.Tests .Add("test", typeof(TestRouteConstraint))); var serviceProvider = services.BuildServiceProvider(); var accessor = serviceProvider.GetRequiredService>(); - return new DefaultInlineConstraintResolver(accessor); + return new DefaultInlineConstraintResolver(accessor, serviceProvider); } private class TestRouteConstraint : IRouteConstraint diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultParameterPolicyFactoryTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultParameterPolicyFactoryTest.cs deleted file mode 100644 index abfb7bde3a..0000000000 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/DefaultParameterPolicyFactoryTest.cs +++ /dev/null @@ -1,272 +0,0 @@ -// 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.Globalization; -using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Routing.Constraints; -using Microsoft.AspNetCore.Routing.Patterns; -using Microsoft.Extensions.DependencyInjection; -using Microsoft.Extensions.Options; -using Xunit; - -namespace Microsoft.AspNetCore.Routing.Matching -{ - public class DefaultParameterPolicyFactoryTest - { - [Fact] - public void Create_ThrowsException_IfNoConstraintOrParameterPolicy_FoundInMap() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - // Act - var exception = Assert.Throws( - () => factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), @"notpresent(\d+)")); - - // Assert - Assert.Equal( - $"The constraint reference 'notpresent' could not be resolved to a type. " + - $"Register the constraint type with '{typeof(RouteOptions)}.{nameof(RouteOptions.ConstraintMap)}'.", - exception.Message); - } - - [Fact] - public void Create_ThrowsException_OnInvalidType() - { - // Arrange - var options = new RouteOptions(); - options.ConstraintMap.Add("bad", typeof(string)); - - var services = new ServiceCollection(); - - var factory = GetParameterPolicyFactory(options, services); - - // Act - var exception = Assert.Throws( - () => factory.Create(RoutePatternFactory.ParameterPart("id"), @"bad")); - - // Assert - Assert.Equal( - $"Invalid constraint type '{typeof(string)}' registered as 'bad'. " + - $"A constraint type must either implement '{typeof(IRouteConstraint)}', or inherit from '{typeof(IParameterPolicy)}'.", - exception.Message); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromRoutePattern_String() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - var parameter = RoutePatternFactory.ParameterPart( - "id", - @default: null, - parameterKind: RoutePatternParameterKind.Standard, - parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), }); - - // Act - var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); - - // Assert - Assert.IsType(parameterPolicy); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromRoutePattern_String_Optional() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - var parameter = RoutePatternFactory.ParameterPart( - "id", - @default: null, - parameterKind: RoutePatternParameterKind.Optional, - parameterPolicies: new[] { RoutePatternFactory.Constraint("int"), }); - - // Act - var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); - - // Assert - var optionalConstraint = Assert.IsType(parameterPolicy); - Assert.IsType(optionalConstraint.InnerConstraint); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - var parameter = RoutePatternFactory.ParameterPart( - "id", - @default: null, - parameterKind: RoutePatternParameterKind.Standard, - parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), }); - - // Act - var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); - - // Assert - Assert.IsType(parameterPolicy); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromRoutePattern_Constraint_Optional() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - var parameter = RoutePatternFactory.ParameterPart( - "id", - @default: null, - parameterKind: RoutePatternParameterKind.Optional, - parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new IntRouteConstraint()), }); - - // Act - var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); - - // Assert - var optionalConstraint = Assert.IsType(parameterPolicy); - Assert.IsType(optionalConstraint.InnerConstraint); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromRoutePattern_ParameterPolicy() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - var parameter = RoutePatternFactory.ParameterPart( - "id", - @default: null, - parameterKind: RoutePatternParameterKind.Standard, - parameterPolicies: new[] { RoutePatternFactory.ParameterPolicy(new CustomParameterPolicy()), }); - - // Act - var parameterPolicy = factory.Create(parameter, parameter.ParameterPolicies[0]); - - // Assert - Assert.IsType(parameterPolicy); - } - - private class CustomParameterPolicy : IParameterPolicy - { - } - - [Fact] - public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - // Act - var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id"), "int"); - - // Assert - Assert.IsType(parameterPolicy); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromConstraintText_AndRouteConstraint_Optional() - { - // Arrange - var factory = GetParameterPolicyFactory(); - - // Act - var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "int"); - - // Assert - var optionalConstraint = Assert.IsType(parameterPolicy); - Assert.IsType(optionalConstraint.InnerConstraint); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy() - { - // Arrange - var options = new RouteOptions(); - options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy)); - - var services = new ServiceCollection(); - services.AddTransient(); - - var factory = GetParameterPolicyFactory(options, services); - - // Act - var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy"); - - // Assert - Assert.IsType(parameterPolicy); - } - - [Fact] - public void Create_CreatesParameterPolicy_FromConstraintText_AndParameterPolicy_Optional() - { - // Arrange - var options = new RouteOptions(); - options.ConstraintMap.Add("customParameterPolicy", typeof(CustomParameterPolicy)); - - var services = new ServiceCollection(); - services.AddTransient(); - - var factory = GetParameterPolicyFactory(options, services); - - // Act - var parameterPolicy = factory.Create(RoutePatternFactory.ParameterPart("id", @default: null, RoutePatternParameterKind.Optional), "customParameterPolicy"); - - // Assert - Assert.IsType(parameterPolicy); - } - - private DefaultParameterPolicyFactory GetParameterPolicyFactory( - RouteOptions options = null, - ServiceCollection services = null) - { - if (options == null) - { - options = new RouteOptions(); - } - - if (services == null) - { - services = new ServiceCollection(); - } - - return new DefaultParameterPolicyFactory( - Options.Create(options), - services.BuildServiceProvider()); - } - - private class TestRouteConstraint : IRouteConstraint - { - private TestRouteConstraint() { } - - public HttpContext HttpContext { get; private set; } - public IRouter Route { get; private set; } - public string RouteKey { get; private set; } - public RouteValueDictionary Values { get; private set; } - public RouteDirection RouteDirection { get; private set; } - - public static TestRouteConstraint Create() - { - return new TestRouteConstraint(); - } - - public bool Match( - HttpContext httpContext, - IRouter route, - string routeKey, - RouteValueDictionary values, - RouteDirection routeDirection) - { - HttpContext = httpContext; - Route = route; - RouteKey = routeKey; - Values = values; - RouteDirection = routeDirection; - return false; - } - } - } -} diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/RouteMatcherBuilder.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/RouteMatcherBuilder.cs index ba8d46a920..5d21424cb5 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/RouteMatcherBuilder.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/RouteMatcherBuilder.cs @@ -7,6 +7,7 @@ using System.Linq; using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing.Patterns; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.Options; namespace Microsoft.AspNetCore.Routing.Matching @@ -18,7 +19,7 @@ namespace Microsoft.AspNetCore.Routing.Matching public RouteMatcherBuilder() { - _constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions())); + _constraintResolver = new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider()); _endpoints = new List(); } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Matching/TreeRouterMatcherBuilder.cs b/test/Microsoft.AspNetCore.Routing.Tests/Matching/TreeRouterMatcherBuilder.cs index b151d4cf4e..83c5115120 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Matching/TreeRouterMatcherBuilder.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Matching/TreeRouterMatcherBuilder.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Template; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Routing.Tree; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; @@ -34,7 +35,7 @@ namespace Microsoft.AspNetCore.Routing.Matching var builder = new TreeRouteBuilder( NullLoggerFactory.Instance, new DefaultObjectPool(new UriBuilderContextPooledObjectPolicy()), - new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()))); + new DefaultInlineConstraintResolver(Options.Create(new RouteOptions()), new TestServiceProvider())); var selector = new DefaultEndpointSelector(Array.Empty()); diff --git a/test/Microsoft.AspNetCore.Routing.Tests/RouteConstraintBuilderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/RouteConstraintBuilderTest.cs index 9eca4d7759..6fb5df1c3d 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/RouteConstraintBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/RouteConstraintBuilderTest.cs @@ -5,6 +5,7 @@ using System; using System.Linq; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Constraints; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Testing; using Microsoft.Extensions.Options; using Moq; @@ -183,7 +184,7 @@ namespace Microsoft.AspNetCore.Routing .SetupGet(o => o.Value) .Returns(new RouteOptions()); - var inlineConstraintResolver = new DefaultInlineConstraintResolver(options.Object); + var inlineConstraintResolver = new DefaultInlineConstraintResolver(options.Object, new TestServiceProvider()); return new RouteConstraintBuilder(inlineConstraintResolver, template); } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs index 1a52c9dec6..81e20b041f 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/RouteTest.cs @@ -1842,7 +1842,7 @@ namespace Microsoft.AspNetCore.Routing .SetupGet(o => o.Value) .Returns(new RouteOptions()); - return new DefaultInlineConstraintResolver(routeOptions.Object); + return new DefaultInlineConstraintResolver(routeOptions.Object, new TestServiceProvider()); } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs index c5fd2e7b9e..c3e4ebcb8c 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateBinderTests.cs @@ -1293,7 +1293,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests var services = new ServiceCollection().AddOptions(); var serviceProvider = services.BuildServiceProvider(); var accessor = serviceProvider.GetRequiredService>(); - return new DefaultInlineConstraintResolver(accessor); + return new DefaultInlineConstraintResolver(accessor, serviceProvider); } private class PathAndQuery diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateMatcherTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateMatcherTests.cs index 1e5eb7c39e..fc78ef4cdc 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateMatcherTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Template/TemplateMatcherTests.cs @@ -1136,7 +1136,7 @@ namespace Microsoft.AspNetCore.Routing.Template.Tests var services = new ServiceCollection().AddOptions(); var serviceProvider = services.BuildServiceProvider(); var accessor = serviceProvider.GetRequiredService>(); - return new DefaultInlineConstraintResolver(accessor); + return new DefaultInlineConstraintResolver(accessor, serviceProvider); } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/TemplateParserDefaultValuesTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/TemplateParserDefaultValuesTests.cs index 96c126fd30..159fb6cea2 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/TemplateParserDefaultValuesTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/TemplateParserDefaultValuesTests.cs @@ -143,7 +143,7 @@ namespace Microsoft.AspNetCore.Routing.Tests var services = new ServiceCollection().AddOptions(); var serviceProvider = services.BuildServiceProvider(); var accessor = serviceProvider.GetRequiredService>(); - return new DefaultInlineConstraintResolver(accessor); + return new DefaultInlineConstraintResolver(accessor, serviceProvider); } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouteBuilderTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouteBuilderTest.cs index 2645e78439..dc9d2c7594 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouteBuilderTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouteBuilderTest.cs @@ -260,7 +260,7 @@ namespace Microsoft.AspNetCore.Routing.Tree var services = new ServiceCollection().AddOptions(); var serviceProvider = services.BuildServiceProvider(); var accessor = serviceProvider.GetRequiredService>(); - return new DefaultInlineConstraintResolver(accessor); + return new DefaultInlineConstraintResolver(accessor, serviceProvider); } } } diff --git a/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouterTest.cs b/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouterTest.cs index a99846126c..37ba101520 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouterTest.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/Tree/TreeRouterTest.cs @@ -9,6 +9,7 @@ using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Routing.Internal; using Microsoft.AspNetCore.Routing.Template; +using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.ObjectPool; @@ -2133,7 +2134,7 @@ namespace Microsoft.AspNetCore.Routing.Tree var optionsMock = new Mock>(); optionsMock.SetupGet(o => o.Value).Returns(options); - return new DefaultInlineConstraintResolver(optionsMock.Object); + return new DefaultInlineConstraintResolver(optionsMock.Object, new TestServiceProvider()); } private static TreeRouteBuilder CreateBuilder()