Example #1
0
        internal static LegacyMultiTenantRouteTable Create(Dictionary <Type, string[]> templatesByHandler)
        {
            var routes = new List <LegacyMultiTenantRouteEntry>();

            foreach (var keyValuePair in templatesByHandler)
            {
                var parsedTemplates        = keyValuePair.Value.Select(v => LegacyMultiTenantTemplateParser.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 LegacyMultiTenantRouteEntry(parsedTemplate, keyValuePair.Key, unusedRouteParameterNames);
                    routes.Add(entry);
                }
            }

            return(new LegacyMultiTenantRouteTable(routes.OrderBy(id => id, RoutePrecedence).ToArray()));
        }
Example #2
0
        /// <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(LegacyMultiTenantRouteEntry x, LegacyMultiTenantRouteEntry 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.FullName}'
                                                    '{y.Template.TemplateText}' in '{y.Handler.FullName}'
                                                    ");
            }
        }