aspnetcore/src/Microsoft.AspNetCore.Routing/Template/RoutePrecedence.cs

131 lines
4.4 KiB
C#

// 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.Diagnostics;
using System.Linq;
namespace Microsoft.AspNetCore.Routing.Template
{
/// <summary>
/// Computes precedence for an attribute route template.
/// </summary>
public static class RoutePrecedence
{
// Compute the precedence for matching a provided url
// e.g.: /api/template == 1.1
// /api/template/{id} == 1.13
// /api/{id:int} == 1.2
// /api/template/{id:int} == 1.12
public static decimal ComputeMatched(RouteTemplate template)
{
// 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 = ComputeMatchDigit(segment);
Debug.Assert(digit >= 0 && digit < 10);
precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
}
return precedence;
}
// Compute the precedence for generating a url
// e.g.: /api/template == 5.5
// /api/template/{id} == 5.53
// /api/{id:int} == 5.4
// /api/template/{id:int} == 5.54
public static decimal ComputeGenerated(RouteTemplate template)
{
// 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 = ComputeGenerationDigit(segment);
Debug.Assert(digit >= 0 && digit < 10);
precedence += decimal.Divide(digit, (decimal)Math.Pow(10, i));
}
return precedence;
}
// Segments have the following order:
// 5 - Literal segments
// 4 - Multi-part segments && Constrained parameter segments
// 3 - Unconstrained parameter segements
// 2 - Constrained wildcard parameter segments
// 1 - Unconstrained wildcard parameter segments
private static int ComputeGenerationDigit(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;
}
}
// 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 ComputeMatchDigit(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;
}
}
}
}