Replace InnerAttributeRoute with TreeRouter
This commit is contained in:
parent
b8d58133c3
commit
b8b222b295
|
|
@ -12,11 +12,11 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
// A decision tree that matches link generation entries based on route data.
|
||||
public class LinkGenerationDecisionTree
|
||||
{
|
||||
private readonly DecisionTreeNode<AttributeRouteLinkGenerationEntry> _root;
|
||||
private readonly DecisionTreeNode<TreeRouteLinkGenerationEntry> _root;
|
||||
|
||||
public LinkGenerationDecisionTree(IReadOnlyList<AttributeRouteLinkGenerationEntry> entries)
|
||||
public LinkGenerationDecisionTree(IReadOnlyList<TreeRouteLinkGenerationEntry> entries)
|
||||
{
|
||||
_root = DecisionTreeBuilder<AttributeRouteLinkGenerationEntry>.GenerateTree(
|
||||
_root = DecisionTreeBuilder<TreeRouteLinkGenerationEntry>.GenerateTree(
|
||||
entries,
|
||||
new AttributeRouteLinkGenerationEntryClassifier());
|
||||
}
|
||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
private void Walk(
|
||||
List<LinkGenerationMatch> results,
|
||||
VirtualPathContext context,
|
||||
DecisionTreeNode<AttributeRouteLinkGenerationEntry> node,
|
||||
DecisionTreeNode<TreeRouteLinkGenerationEntry> node,
|
||||
bool isFallbackPath)
|
||||
{
|
||||
// Any entries in node.Matches have had all their required values satisfied, so add them
|
||||
|
|
@ -75,7 +75,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
object value;
|
||||
if (context.Values.TryGetValue(key, out value))
|
||||
{
|
||||
DecisionTreeNode<AttributeRouteLinkGenerationEntry> branch;
|
||||
DecisionTreeNode<TreeRouteLinkGenerationEntry> branch;
|
||||
if (criterion.Branches.TryGetValue(value ?? string.Empty, out branch))
|
||||
{
|
||||
Walk(results, context, branch, isFallbackPath);
|
||||
|
|
@ -86,7 +86,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
// If a value wasn't explicitly supplied, match BOTH the ambient value and the empty value
|
||||
// if an ambient value was supplied. The path explored with the empty value is considered
|
||||
// the fallback path.
|
||||
DecisionTreeNode<AttributeRouteLinkGenerationEntry> branch;
|
||||
DecisionTreeNode<TreeRouteLinkGenerationEntry> branch;
|
||||
if (context.AmbientValues.TryGetValue(key, out value) &&
|
||||
!criterion.Branches.Comparer.Equals(value, string.Empty))
|
||||
{
|
||||
|
|
@ -104,7 +104,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
}
|
||||
}
|
||||
|
||||
private class AttributeRouteLinkGenerationEntryClassifier : IClassifier<AttributeRouteLinkGenerationEntry>
|
||||
private class AttributeRouteLinkGenerationEntryClassifier : IClassifier<TreeRouteLinkGenerationEntry>
|
||||
{
|
||||
public AttributeRouteLinkGenerationEntryClassifier()
|
||||
{
|
||||
|
|
@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
|
||||
public IEqualityComparer<object> ValueComparer { get; private set; }
|
||||
|
||||
public IDictionary<string, DecisionCriterionValue> GetCriteria(AttributeRouteLinkGenerationEntry item)
|
||||
public IDictionary<string, DecisionCriterionValue> GetCriteria(TreeRouteLinkGenerationEntry item)
|
||||
{
|
||||
var results = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
|
||||
foreach (var kvp in item.RequiredLinkValues)
|
||||
|
|
|
|||
|
|
@ -8,15 +8,15 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public struct LinkGenerationMatch
|
||||
{
|
||||
private readonly bool _isFallbackMatch;
|
||||
private readonly AttributeRouteLinkGenerationEntry _entry;
|
||||
private readonly TreeRouteLinkGenerationEntry _entry;
|
||||
|
||||
public LinkGenerationMatch(AttributeRouteLinkGenerationEntry entry, bool isFallbackMatch)
|
||||
public LinkGenerationMatch(TreeRouteLinkGenerationEntry entry, bool isFallbackMatch)
|
||||
{
|
||||
_entry = entry;
|
||||
_isFallbackMatch = isFallbackMatch;
|
||||
}
|
||||
|
||||
public AttributeRouteLinkGenerationEntry Entry { get { return _entry; } }
|
||||
public TreeRouteLinkGenerationEntry Entry { get { return _entry; } }
|
||||
|
||||
public bool IsFallbackMatch { get { return _isFallbackMatch; } }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,16 @@
|
|||
using System;
|
||||
// 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.
|
||||
|
||||
using System;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Logging
|
||||
{
|
||||
internal static class InnerAttributeRouteLoggerExtensions
|
||||
internal static class TreeRouterLoggerExtensions
|
||||
{
|
||||
private static readonly Action<ILogger, string, string, Exception> _matchedRouteName;
|
||||
|
||||
static InnerAttributeRouteLoggerExtensions()
|
||||
static TreeRouterLoggerExtensions()
|
||||
{
|
||||
_matchedRouteName = LoggerMessage.Define<string, string>(
|
||||
LogLevel.Verbose,
|
||||
|
|
@ -19,12 +19,10 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
private readonly IRouter _target;
|
||||
private readonly IActionDescriptorsCollectionProvider _actionDescriptorsCollectionProvider;
|
||||
private readonly IInlineConstraintResolver _constraintResolver;
|
||||
|
||||
// These loggers are used by the inner route, keep them around to avoid re-creating.
|
||||
private readonly ILogger _routeLogger;
|
||||
private readonly ILogger _constraintLogger;
|
||||
|
||||
private InnerAttributeRoute _inner;
|
||||
private TreeRouter _router;
|
||||
|
||||
public AttributeRoute(
|
||||
IRouter target,
|
||||
|
|
@ -56,48 +54,48 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
_actionDescriptorsCollectionProvider = actionDescriptorsCollectionProvider;
|
||||
_constraintResolver = constraintResolver;
|
||||
|
||||
_routeLogger = loggerFactory.CreateLogger<InnerAttributeRoute>();
|
||||
_routeLogger = loggerFactory.CreateLogger<TreeRouter>();
|
||||
_constraintLogger = loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
var route = GetInnerRoute();
|
||||
return route.GetVirtualPath(context);
|
||||
var router = GetTreeRouter();
|
||||
return router.GetVirtualPath(context);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task RouteAsync(RouteContext context)
|
||||
{
|
||||
var route = GetInnerRoute();
|
||||
return route.RouteAsync(context);
|
||||
var router = GetTreeRouter();
|
||||
return router.RouteAsync(context);
|
||||
}
|
||||
|
||||
private InnerAttributeRoute GetInnerRoute()
|
||||
private TreeRouter GetTreeRouter()
|
||||
{
|
||||
var actions = _actionDescriptorsCollectionProvider.ActionDescriptors;
|
||||
|
||||
// This is a safe-race. We'll never set inner back to null after initializing
|
||||
// This is a safe-race. We'll never set router back to null after initializing
|
||||
// it on startup.
|
||||
if (_inner == null || _inner.Version != actions.Version)
|
||||
if (_router == null || _router.Version != actions.Version)
|
||||
{
|
||||
_inner = BuildRoute(actions);
|
||||
_router = BuildRoute(actions);
|
||||
}
|
||||
|
||||
return _inner;
|
||||
return _router;
|
||||
}
|
||||
|
||||
private InnerAttributeRoute BuildRoute(ActionDescriptorsCollection actions)
|
||||
private TreeRouter BuildRoute(ActionDescriptorsCollection actions)
|
||||
{
|
||||
var routeBuilder = new TreeRouteBuilder(_target, _routeLogger, _constraintLogger);
|
||||
var routeInfos = GetRouteInfos(_constraintResolver, actions.Items);
|
||||
|
||||
// We're creating one AttributeRouteGenerationEntry per action. This allows us to match the intended
|
||||
// action by expected route values, and then use the TemplateBinder to generate the link.
|
||||
var generationEntries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
foreach (var routeInfo in routeInfos)
|
||||
{
|
||||
generationEntries.Add(new AttributeRouteLinkGenerationEntry()
|
||||
routeBuilder.Add(new TreeRouteLinkGenerationEntry()
|
||||
{
|
||||
Binder = new TemplateBinder(routeInfo.ParsedTemplate, routeInfo.Defaults),
|
||||
Defaults = routeInfo.Defaults,
|
||||
|
|
@ -116,16 +114,15 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// groups. It's guaranteed that all members of the group have the same template and precedence,
|
||||
// so we only need to hang on to a single instance of the RouteInfo for each group.
|
||||
var distinctRouteInfosByGroup = GroupRouteInfosByGroupId(routeInfos);
|
||||
var matchingEntries = new List<AttributeRouteMatchingEntry>();
|
||||
foreach (var routeInfo in distinctRouteInfosByGroup)
|
||||
{
|
||||
matchingEntries.Add(new AttributeRouteMatchingEntry()
|
||||
routeBuilder.Add(new TreeRouteMatchingEntry()
|
||||
{
|
||||
Order = routeInfo.Order,
|
||||
Precedence = routeInfo.MatchPrecedence,
|
||||
Target = _target,
|
||||
RouteName = routeInfo.Name,
|
||||
RouteTemplate = routeInfo.RouteTemplate,
|
||||
RouteTemplate = TemplateParser.Parse(routeInfo.RouteTemplate),
|
||||
TemplateMatcher = new TemplateMatcher(
|
||||
routeInfo.ParsedTemplate,
|
||||
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||
|
|
@ -136,13 +133,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
});
|
||||
}
|
||||
|
||||
return new InnerAttributeRoute(
|
||||
_target,
|
||||
matchingEntries,
|
||||
generationEntries,
|
||||
_routeLogger,
|
||||
_constraintLogger,
|
||||
actions.Version);
|
||||
return routeBuilder.Build(actions.Version);
|
||||
}
|
||||
|
||||
private static IEnumerable<RouteInfo> GroupRouteInfosByGroupId(List<RouteInfo> routeInfos)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,167 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Routing
|
||||
{
|
||||
public class TreeRouteBuilder
|
||||
{
|
||||
private readonly IRouter _target;
|
||||
private readonly List<TreeRouteLinkGenerationEntry> _generatingEntries;
|
||||
private readonly List<TreeRouteMatchingEntry> _matchingEntries;
|
||||
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger _constraintLogger;
|
||||
|
||||
public TreeRouteBuilder(IRouter target, ILogger routeLogger, ILogger constraintLogger)
|
||||
{
|
||||
_target = target;
|
||||
_generatingEntries = new List<TreeRouteLinkGenerationEntry>();
|
||||
_matchingEntries = new List<TreeRouteMatchingEntry>();
|
||||
|
||||
_logger = routeLogger;
|
||||
_constraintLogger = constraintLogger;
|
||||
}
|
||||
|
||||
public void Add(TreeRouteLinkGenerationEntry entry)
|
||||
{
|
||||
_generatingEntries.Add(entry);
|
||||
}
|
||||
|
||||
public void Add(TreeRouteMatchingEntry entry)
|
||||
{
|
||||
_matchingEntries.Add(entry);
|
||||
}
|
||||
|
||||
public TreeRouter Build(int version)
|
||||
{
|
||||
var trees = new Dictionary<int, UrlMatchingTree>();
|
||||
|
||||
foreach (var entry in _matchingEntries)
|
||||
{
|
||||
UrlMatchingTree tree;
|
||||
if (!trees.TryGetValue(entry.Order, out tree))
|
||||
{
|
||||
tree = new UrlMatchingTree(entry.Order);
|
||||
trees.Add(entry.Order, tree);
|
||||
}
|
||||
|
||||
AddEntryToTree(tree, entry);
|
||||
}
|
||||
|
||||
return new TreeRouter(
|
||||
_target,
|
||||
trees.Values.OrderBy(tree => tree.Order).ToArray(),
|
||||
_generatingEntries,
|
||||
_logger,
|
||||
_constraintLogger,
|
||||
version);
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
_generatingEntries.Clear();
|
||||
_matchingEntries.Clear();
|
||||
}
|
||||
|
||||
private void AddEntryToTree(UrlMatchingTree tree, TreeRouteMatchingEntry entry)
|
||||
{
|
||||
var current = tree.Root;
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
if (part.IsParameter && (part.IsOptional || part.IsCatchAll))
|
||||
{
|
||||
current.Matches.Add(entry);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
current = current.ConstrainedCatchAlls;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (part.IsParameter && part.IsCatchAll)
|
||||
{
|
||||
if (current.CatchAlls == null)
|
||||
{
|
||||
current.CatchAlls = new UrlMatchingNode(length: i + 1);
|
||||
}
|
||||
|
||||
current = current.CatchAlls;
|
||||
continue;
|
||||
}
|
||||
|
||||
Debug.Fail("We shouldn't get here.");
|
||||
}
|
||||
|
||||
current.Matches.Add(entry);
|
||||
current.Matches.Sort((x, y) =>
|
||||
{
|
||||
var result = x.Precedence.CompareTo(y.Precedence);
|
||||
return result == 0 ? x.RouteTemplate.Template.CompareTo(y.RouteTemplate.Template) : result;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -8,10 +8,10 @@ using Microsoft.AspNet.Routing.Template;
|
|||
namespace Microsoft.AspNet.Mvc.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to build an <see cref="InnerAttributeRoute"/>. Represents an individual URL-generating route that will be
|
||||
/// aggregated into the <see cref="InnerAttributeRoute"/>.
|
||||
/// Used to build a <see cref="TreeRouter"/>. Represents an individual URL-generating route that will be
|
||||
/// aggregated into the <see cref="TreeRouter"/>.
|
||||
/// </summary>
|
||||
public class AttributeRouteLinkGenerationEntry
|
||||
public class TreeRouteLinkGenerationEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="TemplateBinder"/>.
|
||||
|
|
@ -9,10 +9,10 @@ using Microsoft.Extensions.Logging;
|
|||
namespace Microsoft.AspNet.Mvc.Routing
|
||||
{
|
||||
/// <summary>
|
||||
/// Used to build an <see cref="InnerAttributeRoute"/>. Represents an individual URL-matching route that will be
|
||||
/// aggregated into the <see cref="InnerAttributeRoute"/>.
|
||||
/// Used to build an <see cref="TreeRouter"/>. Represents an individual URL-matching route that will be
|
||||
/// aggregated into the <see cref="TreeRouter"/>.
|
||||
/// </summary>
|
||||
public class AttributeRouteMatchingEntry
|
||||
public class TreeRouteMatchingEntry
|
||||
{
|
||||
/// <summary>
|
||||
/// The order of the template.
|
||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
|
||||
public string RouteName { get; set; }
|
||||
|
||||
public string RouteTemplate { get; set; }
|
||||
public RouteTemplate RouteTemplate { get; set; }
|
||||
|
||||
public TemplateMatcher TemplateMatcher { get; set; }
|
||||
|
||||
|
|
@ -1,7 +1,8 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
|
|
@ -9,7 +10,8 @@ using Microsoft.AspNet.Mvc.Core;
|
|||
using Microsoft.AspNet.Mvc.Internal.Routing;
|
||||
using Microsoft.AspNet.Mvc.Logging;
|
||||
using Microsoft.AspNet.Routing;
|
||||
using Microsoft.AspNet.Routing.Template;
|
||||
using Microsoft.AspNet.Routing.Internal;
|
||||
using Microsoft.Extensions.Internal;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Routing
|
||||
|
|
@ -17,26 +19,31 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
/// <summary>
|
||||
/// An <see cref="IRouter"/> implementation for attribute routing.
|
||||
/// </summary>
|
||||
public class InnerAttributeRoute : IRouter
|
||||
public class TreeRouter : IRouter
|
||||
{
|
||||
private readonly IRouter _next;
|
||||
private readonly LinkGenerationDecisionTree _linkGenerationTree;
|
||||
private readonly AttributeRouteMatchingEntry[] _matchingEntries;
|
||||
private readonly IDictionary<string, AttributeRouteLinkGenerationEntry> _namedEntries;
|
||||
private readonly UrlMatchingTree[] _trees;
|
||||
private readonly IDictionary<string, TreeRouteLinkGenerationEntry> _namedEntries;
|
||||
|
||||
private ILogger _logger;
|
||||
private ILogger _constraintLogger;
|
||||
private readonly ILogger _logger;
|
||||
private readonly ILogger _constraintLogger;
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="InnerAttributeRoute"/>.
|
||||
/// Creates a new <see cref="TreeRouter"/>.
|
||||
/// </summary>
|
||||
/// <param name="next">The next router. Invoked when a route entry matches.</param>
|
||||
/// <param name="entries">The set of route entries.</param>
|
||||
public InnerAttributeRoute(
|
||||
/// <param name="trees">The list of <see cref="UrlMatchingTree"/> that contains the route entries.</param>
|
||||
/// <param name="linkGenerationEntries">The set of <see cref="TreeRouteLinkGenerationEntry"/>.</param>
|
||||
/// <param name="routeLogger">The <see cref="ILogger"/> instance.</param>
|
||||
/// <param name="constraintLogger">The <see cref="ILogger"/> instance used
|
||||
/// in <see cref="RouteConstraintMatcher"/>.</param>
|
||||
/// <param name="version">The version of this route.</param>
|
||||
public TreeRouter(
|
||||
IRouter next,
|
||||
IEnumerable<AttributeRouteMatchingEntry> matchingEntries,
|
||||
IEnumerable<AttributeRouteLinkGenerationEntry> linkGenerationEntries,
|
||||
ILogger logger,
|
||||
UrlMatchingTree[] trees,
|
||||
IEnumerable<TreeRouteLinkGenerationEntry> linkGenerationEntries,
|
||||
ILogger routeLogger,
|
||||
ILogger constraintLogger,
|
||||
int version)
|
||||
{
|
||||
|
|
@ -45,9 +52,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
throw new ArgumentNullException(nameof(next));
|
||||
}
|
||||
|
||||
if (matchingEntries == null)
|
||||
if (trees == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(matchingEntries));
|
||||
throw new ArgumentNullException(nameof(trees));
|
||||
}
|
||||
|
||||
if (linkGenerationEntries == null)
|
||||
|
|
@ -55,9 +62,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
throw new ArgumentNullException(nameof(linkGenerationEntries));
|
||||
}
|
||||
|
||||
if (logger == null)
|
||||
if (routeLogger == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(logger));
|
||||
throw new ArgumentNullException(nameof(routeLogger));
|
||||
}
|
||||
|
||||
if (constraintLogger == null)
|
||||
|
|
@ -66,23 +73,11 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
}
|
||||
|
||||
_next = next;
|
||||
_logger = logger;
|
||||
_trees = trees;
|
||||
_logger = routeLogger;
|
||||
_constraintLogger = constraintLogger;
|
||||
|
||||
Version = version;
|
||||
|
||||
// Order all the entries by order, then precedence, and then finally by template in order to provide
|
||||
// a stable routing and link generation order for templates with same order and precedence.
|
||||
// We use ordinal comparison for the templates because we only care about them being exactly equal and
|
||||
// we don't want to make any equivalence between templates based on the culture of the machine.
|
||||
|
||||
_matchingEntries = matchingEntries
|
||||
.OrderBy(o => o.Order)
|
||||
.ThenBy(e => e.Precedence)
|
||||
.ThenBy(e => e.RouteTemplate, StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
|
||||
var namedEntries = new Dictionary<string, AttributeRouteLinkGenerationEntry>(
|
||||
var namedEntries = new Dictionary<string, TreeRouteLinkGenerationEntry>(
|
||||
StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var entry in linkGenerationEntries)
|
||||
|
|
@ -96,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// We only need to keep one AttributeRouteLinkGenerationEntry per route template
|
||||
// so in case two entries have the same name and the same template we only keep
|
||||
// the first entry.
|
||||
AttributeRouteLinkGenerationEntry namedEntry = null;
|
||||
TreeRouteLinkGenerationEntry namedEntry = null;
|
||||
if (namedEntries.TryGetValue(entry.Name, out namedEntry) &&
|
||||
!namedEntry.TemplateText.Equals(entry.TemplateText, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
|
|
@ -114,6 +109,8 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
|
||||
// The decision tree will take care of ordering for these entries.
|
||||
_linkGenerationTree = new LinkGenerationDecisionTree(linkGenerationEntries.ToArray());
|
||||
|
||||
Version = version;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -122,67 +119,6 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
/// </summary>
|
||||
public int Version { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RouteAsync(RouteContext context)
|
||||
{
|
||||
if (context == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
foreach (var matchingEntry in _matchingEntries)
|
||||
{
|
||||
var requestPath = context.HttpContext.Request.Path;
|
||||
var values = matchingEntry.TemplateMatcher.Match(requestPath);
|
||||
if (values == null)
|
||||
{
|
||||
// If we got back a null value set, that means the URI did not match
|
||||
continue;
|
||||
}
|
||||
|
||||
var oldRouteData = context.RouteData;
|
||||
|
||||
var newRouteData = new RouteData(oldRouteData);
|
||||
newRouteData.Routers.Add(matchingEntry.Target);
|
||||
MergeValues(newRouteData.Values, values);
|
||||
|
||||
if (!RouteConstraintMatcher.Match(
|
||||
matchingEntry.Constraints,
|
||||
newRouteData.Values,
|
||||
context.HttpContext,
|
||||
this,
|
||||
RouteDirection.IncomingRequest,
|
||||
_constraintLogger))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
_logger.MatchedRouteName(
|
||||
matchingEntry.RouteName,
|
||||
matchingEntry.RouteTemplate);
|
||||
|
||||
try
|
||||
{
|
||||
context.RouteData = newRouteData;
|
||||
|
||||
await matchingEntry.Target.RouteAsync(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
// Restore the original values to prevent polluting the route data.
|
||||
if (!context.IsHandled)
|
||||
{
|
||||
context.RouteData = oldRouteData;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.IsHandled)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||
{
|
||||
|
|
@ -215,9 +151,232 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
return null;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task RouteAsync(RouteContext context)
|
||||
{
|
||||
foreach (var tree in _trees)
|
||||
{
|
||||
var tokenizer = new PathTokenizer(context.HttpContext.Request.Path);
|
||||
var enumerator = tokenizer.GetEnumerator();
|
||||
var root = tree.Root;
|
||||
|
||||
var treeEnumerator = new TreeEnumerator(root, tokenizer);
|
||||
|
||||
while (treeEnumerator.MoveNext())
|
||||
{
|
||||
var node = treeEnumerator.Current;
|
||||
foreach (var item in node.Matches)
|
||||
{
|
||||
var values = item.TemplateMatcher.Match(context.HttpContext.Request.Path);
|
||||
if (values == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var match = new TemplateMatch(item, values);
|
||||
|
||||
var oldRouteData = context.RouteData;
|
||||
|
||||
var newRouteData = new RouteData(oldRouteData);
|
||||
|
||||
newRouteData.Routers.Add(match.Entry.Target);
|
||||
MergeValues(newRouteData.Values, match.Values);
|
||||
|
||||
if (!RouteConstraintMatcher.Match(
|
||||
match.Entry.Constraints,
|
||||
newRouteData.Values,
|
||||
context.HttpContext,
|
||||
this,
|
||||
RouteDirection.IncomingRequest,
|
||||
_constraintLogger))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.MatchedRouteName(match.Entry.RouteName, match.Entry.RouteTemplate.Template);
|
||||
|
||||
context.RouteData = newRouteData;
|
||||
|
||||
try
|
||||
{
|
||||
await match.Entry.Target.RouteAsync(context);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (!context.IsHandled)
|
||||
{
|
||||
// Restore the original values to prevent polluting the route data.
|
||||
context.RouteData = oldRouteData;
|
||||
}
|
||||
}
|
||||
|
||||
if (context.IsHandled)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TreeEnumerator : IEnumerator<UrlMatchingNode>
|
||||
{
|
||||
private readonly Stack<UrlMatchingNode> _stack;
|
||||
private readonly PathTokenizer _tokenizer;
|
||||
|
||||
private int _segmentIndex;
|
||||
|
||||
public TreeEnumerator(UrlMatchingNode root, PathTokenizer tokenizer)
|
||||
{
|
||||
_stack = new Stack<UrlMatchingNode>();
|
||||
_tokenizer = tokenizer;
|
||||
Current = null;
|
||||
_segmentIndex = -1;
|
||||
|
||||
_stack.Push(root);
|
||||
}
|
||||
|
||||
public UrlMatchingNode Current { get; private set; }
|
||||
|
||||
object IEnumerator.Current => Current;
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
public bool MoveNext()
|
||||
{
|
||||
if (_stack == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
while (_stack.Count > 0)
|
||||
{
|
||||
var next = _stack.Pop();
|
||||
if (++_segmentIndex >= _tokenizer.Count)
|
||||
{
|
||||
_segmentIndex--;
|
||||
if (next.Matches.Count > 0)
|
||||
{
|
||||
Current = next;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
if (_tokenizer.Count == 0)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (next.CatchAlls != null)
|
||||
{
|
||||
_stack.Push(next.CatchAlls);
|
||||
}
|
||||
|
||||
if (next.ConstrainedCatchAlls != null)
|
||||
{
|
||||
_stack.Push(next.ConstrainedCatchAlls);
|
||||
}
|
||||
|
||||
if (next.Parameters != null)
|
||||
{
|
||||
_stack.Push(next.Parameters);
|
||||
}
|
||||
|
||||
if (next.ConstrainedParameters != null)
|
||||
{
|
||||
_stack.Push(next.ConstrainedParameters);
|
||||
}
|
||||
|
||||
if (next.Literals.Count > 0)
|
||||
{
|
||||
UrlMatchingNode node;
|
||||
if (next.Literals.TryGetValue(_tokenizer[_segmentIndex].Value, out node))
|
||||
{
|
||||
_stack.Push(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
_stack.Clear();
|
||||
Current = null;
|
||||
_segmentIndex = -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static void MergeValues(
|
||||
IDictionary<string, object> destination,
|
||||
IDictionary<string, object> values)
|
||||
{
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
// This will replace the original value for the specified key.
|
||||
// Values from the matched route will take preference over previous
|
||||
// data in the route context.
|
||||
destination[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private struct TemplateMatch : IEquatable<TemplateMatch>
|
||||
{
|
||||
public TemplateMatch(TreeRouteMatchingEntry entry, IDictionary<string, object> values)
|
||||
{
|
||||
Entry = entry;
|
||||
Values = values;
|
||||
}
|
||||
|
||||
public TreeRouteMatchingEntry Entry { get; }
|
||||
|
||||
public IDictionary<string, object> Values { get; }
|
||||
|
||||
public override bool Equals(object obj)
|
||||
{
|
||||
if (obj is TemplateMatch)
|
||||
{
|
||||
return Equals((TemplateMatch)obj);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public bool Equals(TemplateMatch other)
|
||||
{
|
||||
return
|
||||
object.ReferenceEquals(Entry, other.Entry) &&
|
||||
object.ReferenceEquals(Values, other.Values);
|
||||
}
|
||||
|
||||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCodeCombiner();
|
||||
hash.Add(Entry);
|
||||
hash.Add(Values);
|
||||
return hash.CombinedHash;
|
||||
}
|
||||
|
||||
public static bool operator ==(TemplateMatch left, TemplateMatch right)
|
||||
{
|
||||
return left.Equals(right);
|
||||
}
|
||||
|
||||
public static bool operator !=(TemplateMatch left, TemplateMatch right)
|
||||
{
|
||||
return !left.Equals(right);
|
||||
}
|
||||
}
|
||||
|
||||
private VirtualPathData GetVirtualPathForNamedRoute(VirtualPathContext context)
|
||||
{
|
||||
AttributeRouteLinkGenerationEntry entry;
|
||||
TreeRouteLinkGenerationEntry entry;
|
||||
if (_namedEntries.TryGetValue(context.RouteName, out entry))
|
||||
{
|
||||
var path = GenerateVirtualPath(context, entry);
|
||||
|
|
@ -230,7 +389,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
return null;
|
||||
}
|
||||
|
||||
private VirtualPathData GenerateVirtualPath(VirtualPathContext context, AttributeRouteLinkGenerationEntry entry)
|
||||
private VirtualPathData GenerateVirtualPath(VirtualPathContext context, TreeRouteLinkGenerationEntry entry)
|
||||
{
|
||||
// In attribute the context includes the values that are used to select this entry - typically
|
||||
// these will be the standard 'action', 'controller' and maybe 'area' tokens. However, we don't
|
||||
|
|
@ -315,40 +474,5 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
|
||||
return new VirtualPathData(this, path);
|
||||
}
|
||||
|
||||
private static void MergeValues(
|
||||
IDictionary<string, object> destination,
|
||||
IDictionary<string, object> values)
|
||||
{
|
||||
foreach (var kvp in values)
|
||||
{
|
||||
if (kvp.Value != null)
|
||||
{
|
||||
// This will replace the original value for the specified key.
|
||||
// Values from the matched route will take preference over previous
|
||||
// data in the route context.
|
||||
destination[kvp.Key] = kvp.Value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool ContextHasSameValue(VirtualPathContext context, string key, object value)
|
||||
{
|
||||
object providedValue;
|
||||
if (!context.Values.TryGetValue(key, out providedValue))
|
||||
{
|
||||
// If the required value is an 'empty' route value, then ignore ambient values.
|
||||
// This handles a case where we're generating a link to an action like:
|
||||
// { area = "", controller = "Home", action = "Index" }
|
||||
//
|
||||
// and the ambient values has a value for area.
|
||||
if (value != null)
|
||||
{
|
||||
context.AmbientValues.TryGetValue(key, out providedValue);
|
||||
}
|
||||
}
|
||||
|
||||
return TemplateBinder.RoutePartsEqual(providedValue, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
// 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.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Routing
|
||||
{
|
||||
public class UrlMatchingNode
|
||||
{
|
||||
public UrlMatchingNode(int length)
|
||||
{
|
||||
Length = length;
|
||||
|
||||
Matches = new List<TreeRouteMatchingEntry>();
|
||||
Literals = new Dictionary<string, UrlMatchingNode>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
public int Length { get; }
|
||||
|
||||
// These entries are sorted by precedence then template
|
||||
public List<TreeRouteMatchingEntry> Matches { get; }
|
||||
|
||||
public Dictionary<string, UrlMatchingNode> Literals { get; }
|
||||
|
||||
public UrlMatchingNode ConstrainedParameters { get; set; }
|
||||
|
||||
public UrlMatchingNode Parameters { get; set; }
|
||||
|
||||
public UrlMatchingNode ConstrainedCatchAlls { get; set; }
|
||||
|
||||
public UrlMatchingNode CatchAlls { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
// 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.
|
||||
|
||||
namespace Microsoft.AspNet.Mvc.Routing
|
||||
{
|
||||
public class UrlMatchingTree
|
||||
{
|
||||
public UrlMatchingTree(int order)
|
||||
{
|
||||
Order = order;
|
||||
}
|
||||
|
||||
public int Order { get; }
|
||||
|
||||
public UrlMatchingNode Root { get; } = new UrlMatchingNode(length: 0);
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,10 @@
|
|||
"version": "1.0.0-*",
|
||||
"type": "build"
|
||||
},
|
||||
"Microsoft.Extensions.HashCodeCombiner.Sources": {
|
||||
"type": "build",
|
||||
"version": "1.0.0-*"
|
||||
},
|
||||
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
||||
"Microsoft.Extensions.MemoryPool": "1.0.0-*",
|
||||
"Microsoft.Extensions.PropertyActivator.Sources": {
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectSingleEntry_NoCriteria()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry = CreateEntry(new { });
|
||||
entries.Add(entry);
|
||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectSingleEntry_MultipleCriteria()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry);
|
||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectSingleEntry_MultipleCriteria_AmbientValues()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry);
|
||||
|
|
@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectSingleEntry_MultipleCriteria_Replaced()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry);
|
||||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectSingleEntry_MultipleCriteria_AmbientValue_Ignored()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry = CreateEntry(new { controller = "Store", action = (string)null });
|
||||
entries.Add(entry);
|
||||
|
|
@ -126,7 +126,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectSingleEntry_MultipleCriteria_NoMatch()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry);
|
||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectSingleEntry_MultipleCriteria_AmbientValue_NoMatch()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry);
|
||||
|
|
@ -168,7 +168,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectMultipleEntries_OneDoesntMatch()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry1);
|
||||
|
|
@ -193,7 +193,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectMultipleEntries_BothMatch_CriteriaSubset()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry1);
|
||||
|
|
@ -219,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectMultipleEntries_BothMatch_NonOverlappingCriteria()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entries.Add(entry1);
|
||||
|
|
@ -244,7 +244,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectMultipleEntries_BothMatch_OrderedByOrder()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entry1.GenerationPrecedence = 0;
|
||||
|
|
@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectMultipleEntries_BothMatch_OrderedByPrecedence()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entry1.GenerationPrecedence = 1;
|
||||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
public void SelectMultipleEntries_BothMatch_OrderedByTemplate()
|
||||
{
|
||||
// Arrange
|
||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
||||
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||
entry1.TemplateText = "a";
|
||||
|
|
@ -318,9 +318,9 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
|||
Assert.Equal(entries, matches);
|
||||
}
|
||||
|
||||
private AttributeRouteLinkGenerationEntry CreateEntry(object requiredValues)
|
||||
private TreeRouteLinkGenerationEntry CreateEntry(object requiredValues)
|
||||
{
|
||||
var entry = new AttributeRouteLinkGenerationEntry();
|
||||
var entry = new TreeRouteLinkGenerationEntry();
|
||||
entry.RequiredLinkValues = new RouteValueDictionary(requiredValues);
|
||||
return entry;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ using Xunit;
|
|||
|
||||
namespace Microsoft.AspNet.Mvc.Routing
|
||||
{
|
||||
public class InnerAttributeRouteTest
|
||||
public class TreeRouterTest
|
||||
{
|
||||
[Theory]
|
||||
[InlineData("template/5", "template/{parameter:int}")]
|
||||
|
|
@ -53,7 +53,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// try to route the request, the route with a higher precedence gets tried first.
|
||||
var matchingRoutes = new[] { secondRoute, firstRoute };
|
||||
|
||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
||||
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
||||
|
||||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// relative order gets tried first.
|
||||
var matchingRoutes = new[] { firstRoute, secondRoute };
|
||||
|
||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
||||
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
||||
|
||||
|
|
@ -143,7 +143,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// we try to route the request, the route with the higher relative order gets tried first.
|
||||
var matchingRoutes = new[] { firstRoute, secondRoute };
|
||||
|
||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
||||
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
||||
|
||||
|
|
@ -185,7 +185,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// we try to route the request, the route with the higher template order gets tried first.
|
||||
var matchingRoutes = new[] { secondRoute, firstRoute };
|
||||
|
||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
||||
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
||||
|
||||
|
|
@ -225,7 +225,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// try to route the request, the route with a higher precedence gets tried first.
|
||||
var matchingRoutes = new[] { firstRoute };
|
||||
|
||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
||||
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
||||
|
||||
|
|
@ -290,7 +290,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// We setup the route entries in reverse order of precedence to ensure that when we
|
||||
// try to route the request, the route with a higher precedence gets tried first.
|
||||
var matchingEntries = new[] { firstRoute };
|
||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
||||
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||
var context = CreateRouteContext(request);
|
||||
|
||||
|
|
@ -348,7 +348,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// We setup the route entries in reverse order of precedence to ensure that when we
|
||||
// try to route the request, the route with a higher precedence gets tried first.
|
||||
var matchingEntries = new[] { firstRoute };
|
||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
||||
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||
|
||||
var context = CreateRouteContext(request);
|
||||
|
|
@ -547,7 +547,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
})
|
||||
.Returns((VirtualPathData)null);
|
||||
|
||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var firstEntry = CreateGenerationEntry(firstTemplate, requiredValues: null);
|
||||
var secondEntry = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||
|
|
@ -598,7 +598,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
})
|
||||
.Returns((VirtualPathData)null);
|
||||
|
||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var entry = CreateGenerationEntry(template, requiredValues: null);
|
||||
|
||||
|
|
@ -658,7 +658,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
})
|
||||
.Returns((VirtualPathData)null);
|
||||
|
||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 1);
|
||||
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||
|
|
@ -704,7 +704,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
})
|
||||
.Returns((VirtualPathData)null);
|
||||
|
||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 1);
|
||||
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||
|
|
@ -749,7 +749,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
})
|
||||
.Returns((VirtualPathData)null);
|
||||
|
||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 0);
|
||||
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||
|
|
@ -778,7 +778,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
{
|
||||
get
|
||||
{
|
||||
var data = new TheoryData<IEnumerable<AttributeRouteLinkGenerationEntry>>();
|
||||
var data = new TheoryData<IEnumerable<TreeRouteLinkGenerationEntry>>();
|
||||
data.Add(new[]
|
||||
{
|
||||
CreateGenerationEntry("template", null, 0, "NamedEntry"),
|
||||
|
|
@ -806,9 +806,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InnerAttributeRouteTest.NamedEntriesWithDifferentTemplates))]
|
||||
[MemberData(nameof(TreeRouterTest.NamedEntriesWithDifferentTemplates))]
|
||||
public void AttributeRoute_CreateAttributeRoute_ThrowsIfDifferentEntriesHaveTheSameName(
|
||||
IEnumerable<AttributeRouteLinkGenerationEntry> namedEntries)
|
||||
IEnumerable<TreeRouteLinkGenerationEntry> namedEntries)
|
||||
{
|
||||
// Arrange
|
||||
string expectedExceptionMessage = "Two or more routes named 'NamedEntry' have different templates." +
|
||||
|
|
@ -817,18 +817,19 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
|
||||
var next = new Mock<IRouter>().Object;
|
||||
|
||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
|
||||
// Act
|
||||
var builder = new TreeRouteBuilder(next, NullLogger.Instance, NullLogger.Instance);
|
||||
var exception = Assert.Throws<ArgumentException>(
|
||||
"linkGenerationEntries",
|
||||
() => new InnerAttributeRoute(
|
||||
next,
|
||||
matchingEntries,
|
||||
namedEntries,
|
||||
NullLogger.Instance,
|
||||
NullLogger.Instance,
|
||||
version: 1));
|
||||
() =>
|
||||
{
|
||||
foreach (var entry in namedEntries)
|
||||
{
|
||||
builder.Add(entry);
|
||||
}
|
||||
|
||||
return builder.Build(version: 1);
|
||||
});
|
||||
|
||||
Assert.Equal(expectedExceptionMessage, exception.Message, StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
|
@ -837,7 +838,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
{
|
||||
get
|
||||
{
|
||||
var data = new TheoryData<IEnumerable<AttributeRouteLinkGenerationEntry>>();
|
||||
var data = new TheoryData<IEnumerable<TreeRouteLinkGenerationEntry>>();
|
||||
|
||||
data.Add(new[]
|
||||
{
|
||||
|
|
@ -866,9 +867,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
}
|
||||
|
||||
[Theory]
|
||||
[MemberData(nameof(InnerAttributeRouteTest.NamedEntriesWithTheSameTemplate))]
|
||||
[MemberData(nameof(TreeRouterTest.NamedEntriesWithTheSameTemplate))]
|
||||
public void AttributeRoute_GeneratesLink_ForMultipleNamedEntriesWithTheSameTemplate(
|
||||
IEnumerable<AttributeRouteLinkGenerationEntry> namedEntries)
|
||||
IEnumerable<TreeRouteLinkGenerationEntry> namedEntries)
|
||||
{
|
||||
// Arrange
|
||||
var expectedLink = new PathString(
|
||||
|
|
@ -885,7 +886,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
})
|
||||
.Returns((VirtualPathData)null);
|
||||
|
||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingEntries, namedEntries);
|
||||
|
||||
|
|
@ -926,7 +927,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// if it were an unnamed route.
|
||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||
|
||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||
|
||||
|
|
@ -966,7 +967,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// if it were an unnamed route.
|
||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||
|
||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||
|
||||
|
|
@ -1006,7 +1007,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// if it were an unnamed route.
|
||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||
|
||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||
|
||||
|
|
@ -1048,7 +1049,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
// if it were an unnamed route.
|
||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||
|
||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
|
||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||
|
||||
|
|
@ -1796,12 +1797,12 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
name);
|
||||
}
|
||||
|
||||
private static AttributeRouteMatchingEntry CreateMatchingEntry(IRouter router, string template, int order)
|
||||
private static TreeRouteMatchingEntry CreateMatchingEntry(IRouter router, string template, int order)
|
||||
{
|
||||
var routeGroup = string.Format("{0}&&{1}", order, template);
|
||||
var entry = new AttributeRouteMatchingEntry();
|
||||
var entry = new TreeRouteMatchingEntry();
|
||||
entry.Target = router;
|
||||
entry.RouteTemplate = template;
|
||||
entry.RouteTemplate = TemplateParser.Parse(template);
|
||||
var parsedRouteTemplate = TemplateParser.Parse(template);
|
||||
entry.TemplateMatcher = new TemplateMatcher(
|
||||
parsedRouteTemplate,
|
||||
|
|
@ -1812,7 +1813,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
return entry;
|
||||
}
|
||||
|
||||
private static AttributeRouteLinkGenerationEntry CreateGenerationEntry(
|
||||
private static TreeRouteLinkGenerationEntry CreateGenerationEntry(
|
||||
string template,
|
||||
object requiredValues,
|
||||
int order = 0,
|
||||
|
|
@ -1820,7 +1821,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
{
|
||||
var constraintResolver = CreateConstraintResolver();
|
||||
|
||||
var entry = new AttributeRouteLinkGenerationEntry();
|
||||
var entry = new TreeRouteLinkGenerationEntry();
|
||||
entry.TemplateText = template;
|
||||
entry.Template = TemplateParser.Parse(template);
|
||||
|
||||
|
|
@ -1858,7 +1859,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
return entry;
|
||||
}
|
||||
|
||||
private AttributeRouteMatchingEntry CreateMatchingEntry(string template)
|
||||
private TreeRouteMatchingEntry CreateMatchingEntry(string template)
|
||||
{
|
||||
var mockConstraint = new Mock<IRouteConstraint>();
|
||||
mockConstraint.Setup(c => c.Match(
|
||||
|
|
@ -1874,9 +1875,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
It.IsAny<string>()))
|
||||
.Returns(mockConstraint.Object);
|
||||
|
||||
var entry = new AttributeRouteMatchingEntry();
|
||||
var entry = new TreeRouteMatchingEntry();
|
||||
entry.Target = new StubRouter();
|
||||
entry.RouteTemplate = template;
|
||||
entry.RouteTemplate = TemplateParser.Parse(template);
|
||||
|
||||
return entry;
|
||||
}
|
||||
|
|
@ -1895,52 +1896,61 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
return new DefaultInlineConstraintResolver(optionsMock.Object);
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateAttributeRoute(AttributeRouteLinkGenerationEntry entry)
|
||||
private static TreeRouter CreateAttributeRoute(TreeRouteLinkGenerationEntry entry)
|
||||
{
|
||||
return CreateAttributeRoute(new StubRouter(), entry);
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateAttributeRoute(IRouter next, AttributeRouteLinkGenerationEntry entry)
|
||||
private static TreeRouter CreateAttributeRoute(IRouter next, TreeRouteLinkGenerationEntry entry)
|
||||
{
|
||||
return CreateAttributeRoute(next, new[] { entry });
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateAttributeRoute(params AttributeRouteLinkGenerationEntry[] entries)
|
||||
private static TreeRouter CreateAttributeRoute(params TreeRouteLinkGenerationEntry[] entries)
|
||||
{
|
||||
return CreateAttributeRoute(new StubRouter(), entries);
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateAttributeRoute(IRouter next, params AttributeRouteLinkGenerationEntry[] entries)
|
||||
private static TreeRouter CreateAttributeRoute(IRouter next, params TreeRouteLinkGenerationEntry[] entries)
|
||||
{
|
||||
return CreateAttributeRoute(
|
||||
next,
|
||||
Enumerable.Empty<AttributeRouteMatchingEntry>(),
|
||||
Enumerable.Empty<TreeRouteMatchingEntry>(),
|
||||
entries);
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateAttributeRoute(IRouter next, params AttributeRouteMatchingEntry[] entries)
|
||||
private static TreeRouter CreateAttributeRoute(IRouter next, params TreeRouteMatchingEntry[] entries)
|
||||
{
|
||||
return CreateAttributeRoute(
|
||||
next,
|
||||
entries,
|
||||
Enumerable.Empty<AttributeRouteLinkGenerationEntry>());
|
||||
Enumerable.Empty<TreeRouteLinkGenerationEntry>());
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateAttributeRoute(
|
||||
private static TreeRouter CreateAttributeRoute(
|
||||
IRouter next,
|
||||
IEnumerable<AttributeRouteMatchingEntry> matchingEntries,
|
||||
IEnumerable<AttributeRouteLinkGenerationEntry> generationEntries)
|
||||
IEnumerable<TreeRouteMatchingEntry> matchingEntries,
|
||||
IEnumerable<TreeRouteLinkGenerationEntry> generationEntries)
|
||||
{
|
||||
return new InnerAttributeRoute(
|
||||
var builder = new TreeRouteBuilder(
|
||||
next,
|
||||
matchingEntries,
|
||||
generationEntries,
|
||||
NullLogger.Instance,
|
||||
NullLogger.Instance,
|
||||
version: 1);
|
||||
NullLogger.Instance);
|
||||
|
||||
foreach (var entry in matchingEntries)
|
||||
{
|
||||
builder.Add(entry);
|
||||
}
|
||||
|
||||
foreach (var entry in generationEntries)
|
||||
{
|
||||
builder.Add(entry);
|
||||
}
|
||||
|
||||
return builder.Build(version: 1);
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateAttributeRoute(
|
||||
private static TreeRouter CreateAttributeRoute(
|
||||
Action<VirtualPathContext> virtualPathCallback,
|
||||
string firstTemplate,
|
||||
string secondTemplate)
|
||||
|
|
@ -1949,7 +1959,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
next.Setup(n => n.GetVirtualPath(It.IsAny<VirtualPathContext>())).Callback<VirtualPathContext>(virtualPathCallback)
|
||||
.Returns((VirtualPathData)null);
|
||||
|
||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
||||
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||
var firstEntry = CreateGenerationEntry(firstTemplate, requiredValues: null);
|
||||
var secondEntry = CreateGenerationEntry(secondTemplate, requiredValues: null);
|
||||
|
||||
|
|
@ -1959,19 +1969,23 @@ namespace Microsoft.AspNet.Mvc.Routing
|
|||
new[] { secondEntry, firstEntry });
|
||||
}
|
||||
|
||||
private static InnerAttributeRoute CreateRoutingAttributeRoute(
|
||||
private static TreeRouter CreateRoutingAttributeRoute(
|
||||
ILoggerFactory loggerFactory = null,
|
||||
params AttributeRouteMatchingEntry[] entries)
|
||||
params TreeRouteMatchingEntry[] entries)
|
||||
{
|
||||
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||
|
||||
return new InnerAttributeRoute(
|
||||
var builder = new TreeRouteBuilder(
|
||||
new StubRouter(),
|
||||
entries,
|
||||
Enumerable.Empty<AttributeRouteLinkGenerationEntry>(),
|
||||
loggerFactory.CreateLogger<AttributeRoute>(),
|
||||
loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName),
|
||||
version: 1);
|
||||
loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName));
|
||||
|
||||
foreach (var entry in entries)
|
||||
{
|
||||
builder.Add(entry);
|
||||
}
|
||||
|
||||
return builder.Build(version: 1);
|
||||
}
|
||||
|
||||
private static IReadOnlyDictionary<string, IRouteConstraint> GetRouteConstriants(
|
||||
Loading…
Reference in New Issue