/// <summary> /// Route precedence algorithm. /// We collect all the routes and sort them from most specific to /// less specific. The specificity of a route is given by the specificity /// of its segments and the position of those segments in the route. /// * A literal segment is more specific than a parameter segment. /// * A parameter segment with more constraints is more specific than one with fewer constraints /// * Segment earlier in the route are evaluated before segments later in the route. /// For example: /// /Literal is more specific than /Parameter /// /Route/With/{parameter} is more specific than /{multiple}/With/{parameters} /// /Product/{id:int} is more specific than /Product/{id} /// /// Routes can be ambiguous if: /// They are composed of literals and those literals have the same values (case insensitive) /// They are composed of a mix of literals and parameters, in the same relative order and the /// literals have the same values. /// For example: /// * /literal and /Literal /// /{parameter}/literal and /{something}/literal /// /{parameter:constraint}/literal and /{something:constraint}/literal /// /// To calculate the precedence we sort the list of routes as follows: /// * Shorter routes go first. /// * A literal wins over a parameter in precedence. /// * For literals with different values (case insensitive) we choose the lexical order /// * For parameters with different numbers of constraints, the one with more wins /// If we get to the end of the comparison routing we've detected an ambiguous pair of routes. /// </summary> internal static int RouteComparison(MultiTenantRouteEntry x, MultiTenantRouteEntry y) { if (ReferenceEquals(x, y)) { return(0); } var xTemplate = x.Template; var yTemplate = y.Template; var minSegments = Math.Min(xTemplate.Segments.Length, yTemplate.Segments.Length); var currentResult = 0; for (var i = 0; i < minSegments; i++) { var xSegment = xTemplate.Segments[i]; var ySegment = yTemplate.Segments[i]; var xRank = GetRank(xSegment); var yRank = GetRank(ySegment); currentResult = xRank.CompareTo(yRank); // If they are both literals we can disambiguate if ((xRank, yRank) == (0, 0)) { currentResult = StringComparer.OrdinalIgnoreCase.Compare(xSegment.Value, ySegment.Value); } if (currentResult != 0) { break; } } if (currentResult == 0) { currentResult = xTemplate.Segments.Length.CompareTo(yTemplate.Segments.Length); } if (currentResult == 0) { throw new InvalidOperationException($@"The following routes are ambiguous: '{x.Template.TemplateText}' in '{x.Handler.FullName}' '{y.Template.TemplateText}' in '{y.Handler.FullName}' "); } return(currentResult); }
internal static MultiTenantRouteTable Create(Dictionary <Type, string[]> templatesByHandler) { var routes = new List <MultiTenantRouteEntry>(); foreach (var keyValuePair in templatesByHandler) { var parsedTemplates = keyValuePair.Value.Select(v => MultiTenantTemplateParser.ParseTemplate(v)).ToArray(); var allRouteParameterNames = parsedTemplates .SelectMany(GetParameterNames) .Distinct(StringComparer.OrdinalIgnoreCase) .ToArray(); foreach (var parsedTemplate in parsedTemplates) { var unusedRouteParameterNames = allRouteParameterNames .Except(GetParameterNames(parsedTemplate), StringComparer.OrdinalIgnoreCase) .ToArray(); var entry = new MultiTenantRouteEntry(parsedTemplate, keyValuePair.Key, unusedRouteParameterNames); routes.Add(entry); } } return(new MultiTenantRouteTable(routes.OrderBy(id => id, RoutePrecedence).ToArray())); }