parent
e014f7218f
commit
1b0258ab8f
|
|
@ -5,8 +5,9 @@ using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using Microsoft.AspNetCore.Dispatcher.Internal;
|
using Microsoft.AspNetCore.Dispatcher.Internal;
|
||||||
|
|
||||||
#if ROUTING
|
#if ROUTING
|
||||||
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Tree
|
namespace Microsoft.AspNetCore.Routing.Tree
|
||||||
#elif DISPATCHER
|
#elif DISPATCHER
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
@ -115,5 +116,4 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
Current = null;
|
Current = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,13 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Diagnostics;
|
||||||
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Dispatcher.Patterns;
|
||||||
#if ROUTING
|
#if ROUTING
|
||||||
|
using Microsoft.AspNetCore.Routing.Template;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Tree
|
namespace Microsoft.AspNetCore.Routing.Tree
|
||||||
#elif DISPATCHER
|
#elif DISPATCHER
|
||||||
namespace Microsoft.AspNetCore.Dispatcher
|
namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
@ -36,5 +42,194 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
/// Gets the root of the <see cref="UrlMatchingTree"/>.
|
/// Gets the root of the <see cref="UrlMatchingTree"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public UrlMatchingNode Root { get; } = new UrlMatchingNode(0);
|
public UrlMatchingNode Root { get; } = new UrlMatchingNode(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
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static bool RemainingSegmentsAreOptional(IReadOnlyList<RoutePatternPathSegment> segments, int currentParameterIndex)
|
||||||
|
{
|
||||||
|
for (var i = currentParameterIndex; i < segments.Count; i++)
|
||||||
|
{
|
||||||
|
if (!segments[i].IsSimple)
|
||||||
|
{
|
||||||
|
// /{complex}-{segment}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var part = segments[i].Parts[0];
|
||||||
|
if (!part.IsParameter)
|
||||||
|
{
|
||||||
|
// /literal
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
var parameter = (RoutePatternParameter)part;
|
||||||
|
var isOptionalCatchAllOrHasDefaultValue = parameter.IsOptional ||
|
||||||
|
parameter.IsCatchAll ||
|
||||||
|
parameter.DefaultValue != null;
|
||||||
|
|
||||||
|
if (!isOptionalCatchAllOrHasDefaultValue)
|
||||||
|
{
|
||||||
|
// /{parameter}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -52,9 +52,8 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
object value;
|
|
||||||
var parameter = (RoutePatternParameter)part;
|
var parameter = (RoutePatternParameter)part;
|
||||||
if (Defaults.TryGetValue(parameter.Name, out value))
|
if (Defaults.TryGetValue(parameter.Name, out var value))
|
||||||
{
|
{
|
||||||
_hasDefaultValue[i] = true;
|
_hasDefaultValue[i] = true;
|
||||||
_defaultValues[i] = value;
|
_defaultValues[i] = value;
|
||||||
|
|
|
||||||
|
|
@ -175,7 +175,7 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
|
|
||||||
var tree = trees[entry.Order];
|
var tree = trees[entry.Order];
|
||||||
|
|
||||||
AddEntryToTree(tree, entry);
|
UrlMatchingTree.AddEntryToTree(tree, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Cache(trees.ToArray());
|
return new Cache(trees.ToArray());
|
||||||
|
|
@ -229,169 +229,6 @@ namespace Microsoft.AspNetCore.Dispatcher
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
var matcher = new RoutePatternMatcher(entry.RoutePattern, entry.Defaults);
|
|
||||||
|
|
||||||
for (var i = 0; i < entry.RoutePattern.PathSegments.Count; i++)
|
|
||||||
{
|
|
||||||
var segment = entry.RoutePattern.PathSegments[i];
|
|
||||||
if (!segment.IsSimple)
|
|
||||||
{
|
|
||||||
// Treat complex segments as a constrained parameter
|
|
||||||
if (current.ConstrainedParameters == null)
|
|
||||||
{
|
|
||||||
current.ConstrainedParameters = new UrlMatchingNode(depth: 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(depth: 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(entry.RoutePattern.PathSegments, i))
|
|
||||||
{
|
|
||||||
current.Matches.Add(new InboundMatch() { Entry = entry, RoutePatternMatcher = matcher });
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameter = part as RoutePatternParameter;
|
|
||||||
if (parameter != null && parameter.Constraints.Any() && !parameter.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.ConstrainedParameters == null)
|
|
||||||
{
|
|
||||||
current.ConstrainedParameters = new UrlMatchingNode(depth: i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.ConstrainedParameters;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter != null && !parameter.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.Parameters == null)
|
|
||||||
{
|
|
||||||
current.Parameters = new UrlMatchingNode(depth: i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.Parameters;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter != null && parameter.Constraints.Any() && parameter.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.ConstrainedCatchAlls == null)
|
|
||||||
{
|
|
||||||
current.ConstrainedCatchAlls = new UrlMatchingNode(depth: i + 1) { IsCatchAll = true };
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.ConstrainedCatchAlls;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (parameter != null && parameter.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.CatchAlls == null)
|
|
||||||
{
|
|
||||||
current.CatchAlls = new UrlMatchingNode(depth: i + 1) { IsCatchAll = true };
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.CatchAlls;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Fail("We shouldn't get here.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool RemainingSegmentsAreOptional(IReadOnlyList<RoutePatternPathSegment> segments, int currentParameterIndex)
|
|
||||||
{
|
|
||||||
for (var i = currentParameterIndex; i < segments.Count; i++)
|
|
||||||
{
|
|
||||||
if (!segments[i].IsSimple)
|
|
||||||
{
|
|
||||||
// /{complex}-{segment}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var part = segments[i].Parts[0];
|
|
||||||
if (!part.IsParameter)
|
|
||||||
{
|
|
||||||
// /literal
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var parameter = (RoutePatternParameter)part;
|
|
||||||
var isOptionlCatchAllOrHasDefaultValue = parameter.IsOptional ||
|
|
||||||
parameter.IsCatchAll ||
|
|
||||||
parameter.DefaultValue != null;
|
|
||||||
|
|
||||||
if (!isOptionlCatchAllOrHasDefaultValue)
|
|
||||||
{
|
|
||||||
// /{parameter}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct Key : IEquatable<Key>
|
private struct Key : IEquatable<Key>
|
||||||
{
|
{
|
||||||
public readonly int Order;
|
public readonly int Order;
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
{
|
{
|
||||||
Text = literal.Content;
|
Text = literal.Content;
|
||||||
}
|
}
|
||||||
else if (other.IsParameter && other is Microsoft.AspNetCore.Dispatcher.Patterns.RoutePatternParameter parameter)
|
else if (other.IsParameter && other is Dispatcher.Patterns.RoutePatternParameter parameter)
|
||||||
{
|
{
|
||||||
// Text is unused by TemplatePart and assumed to be null when the part is a parameter.
|
// Text is unused by TemplatePart and assumed to be null when the part is a parameter.
|
||||||
Name = parameter.Name;
|
Name = parameter.Name;
|
||||||
|
|
@ -34,7 +34,7 @@ namespace Microsoft.AspNetCore.Routing.Template
|
||||||
DefaultValue = parameter.DefaultValue;
|
DefaultValue = parameter.DefaultValue;
|
||||||
InlineConstraints = parameter.Constraints?.Select(p => new InlineConstraint(p));
|
InlineConstraints = parameter.Constraints?.Select(p => new InlineConstraint(p));
|
||||||
}
|
}
|
||||||
else if (other.IsSeparator && other is Microsoft.AspNetCore.Dispatcher.Patterns.RoutePatternSeparator separator)
|
else if (other.IsSeparator && other is Dispatcher.Patterns.RoutePatternSeparator separator)
|
||||||
{
|
{
|
||||||
Text = separator.Content;
|
Text = separator.Content;
|
||||||
IsOptionalSeperator = true;
|
IsOptionalSeperator = true;
|
||||||
|
|
|
||||||
|
|
@ -221,14 +221,13 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
||||||
|
|
||||||
foreach (var entry in InboundEntries)
|
foreach (var entry in InboundEntries)
|
||||||
{
|
{
|
||||||
UrlMatchingTree tree;
|
if (!trees.TryGetValue(entry.Order, out var tree))
|
||||||
if (!trees.TryGetValue(entry.Order, out tree))
|
|
||||||
{
|
{
|
||||||
tree = new UrlMatchingTree(entry.Order);
|
tree = new UrlMatchingTree(entry.Order);
|
||||||
trees.Add(entry.Order, tree);
|
trees.Add(entry.Order, tree);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddEntryToTree(tree, entry);
|
UrlMatchingTree.AddEntryToTree(tree, entry);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new TreeRouter(
|
return new TreeRouter(
|
||||||
|
|
@ -249,167 +248,5 @@ namespace Microsoft.AspNetCore.Routing.Tree
|
||||||
InboundEntries.Clear();
|
InboundEntries.Clear();
|
||||||
OutboundEntries.Clear();
|
OutboundEntries.Clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
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;
|
|
||||||
var matcher = new TemplateMatcher(entry.RouteTemplate, entry.Defaults);
|
|
||||||
|
|
||||||
for (var i = 0; i < entry.RouteTemplate.Segments.Count; i++)
|
|
||||||
{
|
|
||||||
var segment = entry.RouteTemplate.Segments[i];
|
|
||||||
if (!segment.IsSimple)
|
|
||||||
{
|
|
||||||
// Treat complex segments as a constrained parameter
|
|
||||||
if (current.ConstrainedParameters == null)
|
|
||||||
{
|
|
||||||
current.ConstrainedParameters = new UrlMatchingNode(length: i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.ConstrainedParameters;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Assert(segment.Parts.Count == 1);
|
|
||||||
var part = segment.Parts[0];
|
|
||||||
if (part.IsLiteral)
|
|
||||||
{
|
|
||||||
UrlMatchingNode next;
|
|
||||||
if (!current.Literals.TryGetValue(part.Text, out next))
|
|
||||||
{
|
|
||||||
next = new UrlMatchingNode(length: i + 1);
|
|
||||||
current.Literals.Add(part.Text, 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(entry.RouteTemplate.Segments, i))
|
|
||||||
{
|
|
||||||
current.Matches.Add(new InboundMatch() { Entry = entry, TemplateMatcher = matcher });
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.IsParameter && part.InlineConstraints.Any() && !part.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.ConstrainedParameters == null)
|
|
||||||
{
|
|
||||||
current.ConstrainedParameters = new UrlMatchingNode(length: i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.ConstrainedParameters;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.IsParameter && !part.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.Parameters == null)
|
|
||||||
{
|
|
||||||
current.Parameters = new UrlMatchingNode(length: i + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.Parameters;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.IsParameter && part.InlineConstraints.Any() && part.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.ConstrainedCatchAlls == null)
|
|
||||||
{
|
|
||||||
current.ConstrainedCatchAlls = new UrlMatchingNode(length: i + 1) { IsCatchAll = true };
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.ConstrainedCatchAlls;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (part.IsParameter && part.IsCatchAll)
|
|
||||||
{
|
|
||||||
if (current.CatchAlls == null)
|
|
||||||
{
|
|
||||||
current.CatchAlls = new UrlMatchingNode(length: i + 1) { IsCatchAll = true };
|
|
||||||
}
|
|
||||||
|
|
||||||
current = current.CatchAlls;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
Debug.Fail("We shouldn't get here.");
|
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
private static bool RemainingSegmentsAreOptional(IList<TemplateSegment> segments, int currentParameterIndex)
|
|
||||||
{
|
|
||||||
for (var i = currentParameterIndex; i < segments.Count; i++)
|
|
||||||
{
|
|
||||||
if (!segments[i].IsSimple)
|
|
||||||
{
|
|
||||||
// /{complex}-{segment}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var part = segments[i].Parts[0];
|
|
||||||
if (!part.IsParameter)
|
|
||||||
{
|
|
||||||
// /literal
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var isOptionlCatchAllOrHasDefaultValue = part.IsOptional ||
|
|
||||||
part.IsCatchAll ||
|
|
||||||
part.DefaultValue != null;
|
|
||||||
|
|
||||||
if (!isOptionlCatchAllOrHasDefaultValue)
|
|
||||||
{
|
|
||||||
// /{parameter}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue