| File: Template\RoutePrecedence.cs | Web Access |
| Project: src\aspnetcore\src\Http\Routing\src\Microsoft.AspNetCore.Routing.csproj (Microsoft.AspNetCore.Routing) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable enable using System.Diagnostics; #if !COMPONENTS using System.Linq; #endif using Microsoft.AspNetCore.Routing.Patterns; namespace Microsoft.AspNetCore.Routing.Template; #if !COMPONENTS /// <summary> /// Computes precedence for a route template. /// </summary> public static class RoutePrecedence #else internal static class RoutePrecedence #endif { #if !COMPONENTS /// <summary> /// Compute the precedence for matching a provided url /// </summary> /// <example> /// e.g.: /api/template == 1.1 /// /api/template/{id} == 1.13 /// /api/{id:int} == 1.2 /// /api/template/{id:int} == 1.12 /// </example> /// <param name="template">The <see cref="RouteTemplate"/> to compute precedence for.</param> /// <returns>A <see cref="decimal"/> representing the route's precedence.</returns> public static decimal ComputeInbound(RouteTemplate template) { ValidateSegementLength(template.Segments.Count); // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1, // and 4 results in a combined precedence of 2.14 (decimal). var precedence = 0m; for (var i = 0; i < template.Segments.Count; i++) { var segment = template.Segments[i]; var digit = ComputeInboundPrecedenceDigit(segment); Debug.Assert(digit >= 0 && digit < 10); precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i)); } return precedence; } #endif // See description on ComputeInbound(RouteTemplate) internal static decimal ComputeInbound(RoutePattern routePattern) { ValidateSegementLength(routePattern.PathSegments.Count); var precedence = 0m; for (var i = 0; i < routePattern.PathSegments.Count; i++) { var segment = routePattern.PathSegments[i]; var digit = ComputeInboundPrecedenceDigit(routePattern, segment); Debug.Assert(digit >= 0 && digit < 10); precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i)); } return precedence; } #if !COMPONENTS /// <summary> /// Compute the precedence for generating a url. /// </summary> /// <example> /// e.g.: /api/template == 5.5 /// /api/template/{id} == 5.53 /// /api/{id:int} == 5.4 /// /api/template/{id:int} == 5.54 /// </example> /// <param name="template">The <see cref="RouteTemplate"/> to compute precedence for.</param> /// <returns>A <see cref="decimal"/> representing the route's precedence.</returns> public static decimal ComputeOutbound(RouteTemplate template) { ValidateSegementLength(template.Segments.Count); // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1, // and 4 results in a combined precedence of 2.14 (decimal). var precedence = 0m; for (var i = 0; i < template.Segments.Count; i++) { var segment = template.Segments[i]; var digit = ComputeOutboundPrecedenceDigit(segment); Debug.Assert(digit >= 0 && digit < 10); precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i)); } return precedence; } #endif // see description on ComputeOutbound(RouteTemplate) internal static decimal ComputeOutbound(RoutePattern routePattern) { ValidateSegementLength(routePattern.PathSegments.Count); // Each precedence digit corresponds to one decimal place. For example, 3 segments with precedences 2, 1, // and 4 results in a combined precedence of 2.14 (decimal). var precedence = 0m; for (var i = 0; i < routePattern.PathSegments.Count; i++) { var segment = routePattern.PathSegments[i]; var digit = ComputeOutboundPrecedenceDigit(segment); Debug.Assert(digit >= 0 && digit < 10); precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i)); } return precedence; } private static void ValidateSegementLength(int length) { if (length > 28) { // An OverflowException will be thrown by Math.Pow when greater than 28 throw new InvalidOperationException("Route exceeds the maximum number of allowed segments of 28 and is unable to be processed."); } } #if !COMPONENTS // Segments have the following order: // 5 - Literal segments // 4 - Multi-part segments && Constrained parameter segments // 3 - Unconstrained parameter segments // 2 - Constrained wildcard parameter segments // 1 - Unconstrained wildcard parameter segments private static int ComputeOutboundPrecedenceDigit(TemplateSegment segment) { if (segment.Parts.Count > 1) { return 4; } var part = segment.Parts[0]; if (part.IsLiteral) { return 5; } else { Debug.Assert(part.IsParameter); var digit = part.IsCatchAll ? 1 : 3; if (part.InlineConstraints != null && part.InlineConstraints.Any()) { digit++; } return digit; } } #endif // See description on ComputeOutboundPrecedenceDigit(TemplateSegment segment) private static int ComputeOutboundPrecedenceDigit(RoutePatternPathSegment pathSegment) { if (pathSegment.Parts.Count > 1) { return 4; } var part = pathSegment.Parts[0]; if (part.IsLiteral) { return 5; } else if (part is RoutePatternParameterPart parameterPart) { Debug.Assert(parameterPart != null); var digit = parameterPart.IsCatchAll ? 1 : 3; if (parameterPart.ParameterPolicies.Count > 0) { digit++; } return digit; } else { // Unreachable throw new NotSupportedException(); } } #if !COMPONENTS // Segments have the following order: // 1 - Literal segments // 2 - Constrained parameter segments / Multi-part segments // 3 - Unconstrained parameter segments // 4 - Constrained wildcard parameter segments // 5 - Unconstrained wildcard parameter segments private static int ComputeInboundPrecedenceDigit(TemplateSegment segment) { if (segment.Parts.Count > 1) { // Multi-part segments should appear after literal segments and along with parameter segments return 2; } var part = segment.Parts[0]; // Literal segments always go first if (part.IsLiteral) { return 1; } else { Debug.Assert(part.IsParameter); var digit = part.IsCatchAll ? 5 : 3; // If there is a route constraint for the parameter, reduce order by 1 // Constrained parameters end up with order 2, Constrained catch alls end up with order 4 if (part.InlineConstraints != null && part.InlineConstraints.Any()) { digit--; } return digit; } } #endif // see description on ComputeInboundPrecedenceDigit(TemplateSegment segment) // // With a RoutePattern, parameters with a required value are treated as a literal segment internal static int ComputeInboundPrecedenceDigit(RoutePattern routePattern, RoutePatternPathSegment pathSegment) { if (pathSegment.Parts.Count > 1) { // Multi-part segments should appear after literal segments and along with parameter segments return 2; } var part = pathSegment.Parts[0]; // Literal segments always go first if (part.IsLiteral) { return 1; } else if (part is RoutePatternParameterPart parameterPart) { // Parameter with a required value is matched as a literal if (routePattern.RequiredValues.TryGetValue(parameterPart.Name, out var requiredValue) && !RouteValueEqualityComparer.Default.Equals(requiredValue, string.Empty)) { return 1; } var digit = parameterPart.IsCatchAll ? 5 : 3; // If there is a route constraint for the parameter, reduce order by 1 // Constrained parameters end up with order 2, Constrained catch alls end up with order 4 if (parameterPart.ParameterPolicies.Count > 0) { digit--; } return digit; } else { // Unreachable throw new NotSupportedException(); } } }