internal static RouteTable Create(Dictionary <object, string[]> templatesByHandler) { var routes = new List <RouteEntry>(); foreach (var keyValuePair in templatesByHandler) { var parsedTemplates = keyValuePair.Value.Select(v => TemplateParser.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 RouteEntry(parsedTemplate, keyValuePair.Key, unusedRouteParameterNames); routes.Add(entry); } } return(new RouteTable(routes.OrderBy(id => id, RoutePrecedence).ToArray())); }
/// <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(RouteEntry x, RouteEntry y) { if (ReferenceEquals(x, y)) { return(0); } var xTemplate = x.Template; var yTemplate = y.Template; if (xTemplate.Segments.Length != y.Template.Segments.Length) { return(xTemplate.Segments.Length < y.Template.Segments.Length ? -1 : 1); } else { for (var i = 0; i < xTemplate.Segments.Length; i++) { var xSegment = xTemplate.Segments[i]; var ySegment = yTemplate.Segments[i]; if (!xSegment.IsParameter && ySegment.IsParameter) { return(-1); } if (xSegment.IsParameter && !ySegment.IsParameter) { return(1); } if (xSegment.IsParameter) { // Always favor non-optional parameters over optional ones if (!xSegment.IsOptional && ySegment.IsOptional) { return(-1); } if (xSegment.IsOptional && !ySegment.IsOptional) { return(1); } if (xSegment.Constraints.Length > ySegment.Constraints.Length) { return(-1); } else if (xSegment.Constraints.Length < ySegment.Constraints.Length) { return(1); } } else { var comparison = string.Compare(xSegment.Value, ySegment.Value, StringComparison.OrdinalIgnoreCase); if (comparison != 0) { return(comparison); } } } throw new InvalidOperationException($@"The following routes are ambiguous: '{x.Template.TemplateText}' in '{x.Handler}' '{y.Template.TemplateText}' in '{y.Handler}' "); } }