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]); } } }
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)); }
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(); } }
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); }
public void Deconstruct(out RoutePatternMatcher matcher, out Dictionary <string, List <IRouteConstraint> > constraints) { matcher = Matcher; constraints = Constraints; }
public MatcherState(RoutePatternMatcher matcher, Dictionary <string, List <IRouteConstraint> > constraints) { Matcher = matcher; Constraints = constraints; }
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 }