/// <summary> /// Generate routes given a collection of MethodInfo objects and templates that should call those methods /// </summary> /// <param name="templatesByHandler">Templates that should route to each handler</param> internal static MqttRouteTable Create(Dictionary <MethodInfo, string[]> templatesByHandler) { var routes = new List <MqttRoute>(); 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 MqttRoute(parsedTemplate, keyValuePair.Key, unusedRouteParameterNames); routes.Add(entry); } } return(new MqttRouteTable(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(MqttRoute x, MqttRoute y) { if (ReferenceEquals(x, y)) { return(0); } var xTemplate = x.Template; var yTemplate = y.Template; if (xTemplate.Segments.Count() != y.Template.Segments.Count()) { return(xTemplate.Segments.Count() < y.Template.Segments.Count() ? -1 : 1); } else { for (var i = 0; i < xTemplate.Segments.Count(); 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.DeclaringType.FullName}.{x.Handler.Name}' '{y.Template.TemplateText}' in '{y.Handler.DeclaringType.FullName}.{y.Handler.Name}' "); } }