private static void RunTest(
        string template,
        string path,
        RouteValueDictionary defaults,
        IDictionary <string, object> expected)
    {
        // Arrange
        var matcher = new RoutePatternMatcher(
            RoutePatternParser.Parse(template),
            defaults ?? new RouteValueDictionary());

        var values = new RouteValueDictionary();

        // Act
        var match = matcher.TryMatch(new PathString(path), values);

        // Assert
        if (expected == null)
        {
            Assert.False(match);
        }
        else
        {
            Assert.True(match);
            Assert.Equal(expected.Count, values.Count);
            foreach (string key in values.Keys)
            {
                Assert.Equal(expected[key], values[key]);
            }
        }
    }
Esempio n. 2
0
    private MatcherState CreateRoutePatternMatcher(RouteEndpoint endpoint)
    {
        var constraints = new Dictionary <string, List <IRouteConstraint> >(StringComparer.OrdinalIgnoreCase);

        var policies = endpoint.RoutePattern.ParameterPolicies;

        foreach (var kvp in policies)
        {
            var constraintsForParameter = new List <IRouteConstraint>();
            var parameter = endpoint.RoutePattern.GetParameter(kvp.Key);
            for (var i = 0; i < kvp.Value.Count; i++)
            {
                var policy = _parameterPolicyFactory.Create(parameter, kvp.Value[i]);
                if (policy is IRouteConstraint constraint)
                {
                    constraintsForParameter.Add(constraint);
                }
            }

            if (constraintsForParameter.Count > 0)
            {
                constraints.Add(kvp.Key, constraintsForParameter);
            }
        }

        var matcher = new RoutePatternMatcher(endpoint.RoutePattern, new RouteValueDictionary(endpoint.RoutePattern.Defaults));

        return(new MatcherState(matcher, constraints));
    }
Esempio n. 3
0
            private void AddParentsMatchingComplexSegment(RouteEndpoint endpoint, List <DfaNode> nextParents, RoutePatternPathSegment segment, DfaNode parent, RoutePatternParameterPart parameterPart)
            {
                var routeValues = new RouteValueDictionary();

                foreach (var literal in parent.Literals.Keys)
                {
                    if (RoutePatternMatcher.MatchComplexSegment(segment, literal, routeValues))
                    {
                        // If we got here (rare) it means that the literal matches the complex segment (for example the literal is something A-B)
                        // there is another thing we can try here, which is to evaluate the policies for the parts in case they have one (for example {a:length(4)}-{b:regex(\d+)})
                        // so that even if it maps closely to a complex parameter we have a chance to discard it and avoid adding the extra branches.
                        var passedAllPolicies = true;
                        for (var i = 0; i < segment.Parts.Count; i++)
                        {
                            var segmentPart = segment.Parts[i];
                            if (segmentPart is not RoutePatternParameterPart partParameter)
                            {
                                // We skip over the literals and the separator since we already checked against them
                                continue;
                            }

                            if (!routeValues.TryGetValue(partParameter.Name, out var parameterValue))
                            {
                                // We have a pattern like {a}-{b}.{part?} and a literal "a-b". Since we've matched the complex segment it means that the optional
                                // parameter was not specified, so we skip it.
                                Debug.Assert(i == segment.Parts.Count - 1 && partParameter.IsOptional);
                                continue;
                            }

                            if (endpoint.RoutePattern.ParameterPolicies.TryGetValue(partParameter.Name, out var parameterPolicyReferences))
                            {
                                for (var j = 0; j < parameterPolicyReferences.Count; j++)
                                {
                                    var reference       = parameterPolicyReferences[j];
                                    var parameterPolicy = _parameterPolicyFactory.Create(parameterPart, reference);
                                    if (parameterPolicy is IParameterLiteralNodeMatchingPolicy constraint && !constraint.MatchesLiteral(partParameter.Name, (string)parameterValue))
                                    {
                                        passedAllPolicies = false;
                                        break;
                                    }
                                }
                            }
                        }

                        if (passedAllPolicies)
                        {
                            nextParents.Add(parent.Literals[literal]);
                        }
                    }

                    routeValues.Clear();
                }
            }
Esempio n. 4
0
        public TemplateMatcher(
            RouteTemplate template,
            RouteValueDictionary defaults)
        {
            if (template == null)
            {
                throw new ArgumentNullException(nameof(template));
            }

            Template = template;
            Defaults = defaults ?? new RouteValueDictionary();

            // Perf: cache the default value for each parameter (other than complex segments).
            _hasDefaultValue = new bool[Template.Segments.Count];
            _defaultValues   = new object[Template.Segments.Count];

            for (var i = 0; i < Template.Segments.Count; i++)
            {
                var segment = Template.Segments[i];
                if (!segment.IsSimple)
                {
                    continue;
                }

                var part = segment.Parts[0];
                if (!part.IsParameter)
                {
                    continue;
                }

                object value;
                if (Defaults.TryGetValue(part.Name, out value))
                {
                    _hasDefaultValue[i] = true;
                    _defaultValues[i]   = value;
                }
            }

            var routePattern = Template.ToRoutePattern();

            _routePatternMatcher = new RoutePatternMatcher(routePattern, Defaults);
        }
Esempio n. 5
0
 public void Deconstruct(out RoutePatternMatcher matcher, out Dictionary <string, List <IRouteConstraint> > constraints)
 {
     matcher     = Matcher;
     constraints = Constraints;
 }
Esempio n. 6
0
 public MatcherState(RoutePatternMatcher matcher, Dictionary <string, List <IRouteConstraint> > constraints)
 {
     Matcher     = matcher;
     Constraints = constraints;
 }
Esempio n. 7
0
        internal static void AddEntryToTree(UrlMatchingTree tree, InboundRouteEntry entry)
        {
            // The url matching tree represents all the routes asociated with a given
            // order. Each node in the tree represents all the different categories
            // a segment can have for which there is a defined inbound route entry.
            // Each node contains a set of Matches that indicate all the routes for which
            // a URL is a potential match. This list contains the routes with the same
            // number of segments and the routes with the same number of segments plus an
            // additional catch all parameter (as it can be empty).
            // For example, for a set of routes like:
            // 'Customer/Index/{id}'
            // '{Controller}/{Action}/{*parameters}'
            //
            // The route tree will look like:
            // Root ->
            //     Literals: Customer ->
            //                   Literals: Index ->
            //                                Parameters: {id}
            //                                                Matches: 'Customer/Index/{id}'
            //     Parameters: {Controller} ->
            //                     Parameters: {Action} ->
            //                                     Matches: '{Controller}/{Action}/{*parameters}'
            //                                     CatchAlls: {*parameters}
            //                                                    Matches: '{Controller}/{Action}/{*parameters}'
            //
            // When the tree router tries to match a route, it iterates the list of url matching trees
            // in ascending order. For each tree it traverses each node starting from the root in the
            // following order: Literals, constrained parameters, parameters, constrained catch all routes, catch alls.
            // When it gets to a node of the same length as the route its trying to match, it simply looks at the list of
            // candidates (which is in precence order) and tries to match the url against it.

            var current = tree.Root;

#if ROUTING
            var routePattern = entry.RouteTemplate.ToRoutePattern();
            var matcher      = new TemplateMatcher(entry.RouteTemplate, entry.Defaults);
#elif DISPATCHER
            var routePattern = entry.RoutePattern;
            var matcher      = new RoutePatternMatcher(routePattern, entry.Defaults);
#else
#error
#endif

            for (var i = 0; i < routePattern.PathSegments.Count; i++)
            {
                var segment = routePattern.PathSegments[i];
                if (!segment.IsSimple)
                {
                    // Treat complex segments as a constrained parameter
                    if (current.ConstrainedParameters == null)
                    {
                        current.ConstrainedParameters = new UrlMatchingNode(i + 1);
                    }

                    current = current.ConstrainedParameters;
                    continue;
                }

                Debug.Assert(segment.Parts.Count == 1);
                var part = segment.Parts[0];
                if (part.IsLiteral)
                {
                    var literal = (RoutePatternLiteral)part;
                    if (!current.Literals.TryGetValue(literal.Content, out var next))
                    {
                        next = new UrlMatchingNode(i + 1);
                        current.Literals.Add(literal.Content, next);
                    }

                    current = next;
                    continue;
                }

                // We accept templates that have intermediate optional values, but we ignore
                // those values for route matching. For that reason, we need to add the entry
                // to the list of matches, only if the remaining segments are optional. For example:
                // /{controller}/{action=Index}/{id} will be equivalent to /{controller}/{action}/{id}
                // for the purposes of route matching.
                if (part.IsParameter &&
                    RemainingSegmentsAreOptional(routePattern.PathSegments, i))
                {
#if ROUTING
                    current.Matches.Add(new InboundMatch()
                    {
                        Entry = entry, TemplateMatcher = matcher
                    });
#elif DISPATCHER
                    current.Matches.Add(new InboundMatch()
                    {
                        Entry = entry, RoutePatternMatcher = matcher
                    });
#else
#error
#endif
                }

                var parameter = (RoutePatternParameter)part;
                if (parameter != null && parameter.Constraints.Any() && !parameter.IsCatchAll)
                {
                    if (current.ConstrainedParameters == null)
                    {
                        current.ConstrainedParameters = new UrlMatchingNode(i + 1);
                    }

                    current = current.ConstrainedParameters;
                    continue;
                }

                if (parameter != null && !parameter.IsCatchAll)
                {
                    if (current.Parameters == null)
                    {
                        current.Parameters = new UrlMatchingNode(i + 1);
                    }

                    current = current.Parameters;
                    continue;
                }

                if (parameter != null && parameter.Constraints.Any() && parameter.IsCatchAll)
                {
                    if (current.ConstrainedCatchAlls == null)
                    {
                        current.ConstrainedCatchAlls = new UrlMatchingNode(i + 1)
                        {
                            IsCatchAll = true
                        };
                    }

                    current = current.ConstrainedCatchAlls;
                    continue;
                }

                if (parameter != null && parameter.IsCatchAll)
                {
                    if (current.CatchAlls == null)
                    {
                        current.CatchAlls = new UrlMatchingNode(i + 1)
                        {
                            IsCatchAll = true
                        };
                    }

                    current = current.CatchAlls;
                    continue;
                }

                Debug.Fail("We shouldn't get here.");
            }

#if ROUTING
            current.Matches.Add(new InboundMatch()
            {
                Entry = entry, TemplateMatcher = matcher
            });
            current.Matches.Sort((x, y) =>
            {
                var result = x.Entry.Precedence.CompareTo(y.Entry.Precedence);
                return(result == 0 ? x.Entry.RouteTemplate.TemplateText.CompareTo(y.Entry.RouteTemplate.TemplateText) : result);
            });
#elif DISPATCHER
            current.Matches.Add(new InboundMatch()
            {
                Entry = entry, RoutePatternMatcher = matcher
            });
            current.Matches.Sort((x, y) =>
            {
                var result = x.Entry.Precedence.CompareTo(y.Entry.Precedence);
                return(result == 0 ? x.Entry.RoutePattern.RawText.CompareTo(y.Entry.RoutePattern.RawText) : result);
            });
#else
#error
#endif
        }