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.
|
// A decision tree that matches link generation entries based on route data.
|
||||||
public class LinkGenerationDecisionTree
|
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,
|
entries,
|
||||||
new AttributeRouteLinkGenerationEntryClassifier());
|
new AttributeRouteLinkGenerationEntryClassifier());
|
||||||
}
|
}
|
||||||
|
|
@ -57,7 +57,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
private void Walk(
|
private void Walk(
|
||||||
List<LinkGenerationMatch> results,
|
List<LinkGenerationMatch> results,
|
||||||
VirtualPathContext context,
|
VirtualPathContext context,
|
||||||
DecisionTreeNode<AttributeRouteLinkGenerationEntry> node,
|
DecisionTreeNode<TreeRouteLinkGenerationEntry> node,
|
||||||
bool isFallbackPath)
|
bool isFallbackPath)
|
||||||
{
|
{
|
||||||
// Any entries in node.Matches have had all their required values satisfied, so add them
|
// 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;
|
object value;
|
||||||
if (context.Values.TryGetValue(key, out value))
|
if (context.Values.TryGetValue(key, out value))
|
||||||
{
|
{
|
||||||
DecisionTreeNode<AttributeRouteLinkGenerationEntry> branch;
|
DecisionTreeNode<TreeRouteLinkGenerationEntry> branch;
|
||||||
if (criterion.Branches.TryGetValue(value ?? string.Empty, out branch))
|
if (criterion.Branches.TryGetValue(value ?? string.Empty, out branch))
|
||||||
{
|
{
|
||||||
Walk(results, context, branch, isFallbackPath);
|
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 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
|
// if an ambient value was supplied. The path explored with the empty value is considered
|
||||||
// the fallback path.
|
// the fallback path.
|
||||||
DecisionTreeNode<AttributeRouteLinkGenerationEntry> branch;
|
DecisionTreeNode<TreeRouteLinkGenerationEntry> branch;
|
||||||
if (context.AmbientValues.TryGetValue(key, out value) &&
|
if (context.AmbientValues.TryGetValue(key, out value) &&
|
||||||
!criterion.Branches.Comparer.Equals(value, string.Empty))
|
!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()
|
public AttributeRouteLinkGenerationEntryClassifier()
|
||||||
{
|
{
|
||||||
|
|
@ -113,7 +113,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
|
|
||||||
public IEqualityComparer<object> ValueComparer { get; private set; }
|
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);
|
var results = new Dictionary<string, DecisionCriterionValue>(StringComparer.OrdinalIgnoreCase);
|
||||||
foreach (var kvp in item.RequiredLinkValues)
|
foreach (var kvp in item.RequiredLinkValues)
|
||||||
|
|
|
||||||
|
|
@ -8,15 +8,15 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public struct LinkGenerationMatch
|
public struct LinkGenerationMatch
|
||||||
{
|
{
|
||||||
private readonly bool _isFallbackMatch;
|
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;
|
_entry = entry;
|
||||||
_isFallbackMatch = isFallbackMatch;
|
_isFallbackMatch = isFallbackMatch;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AttributeRouteLinkGenerationEntry Entry { get { return _entry; } }
|
public TreeRouteLinkGenerationEntry Entry { get { return _entry; } }
|
||||||
|
|
||||||
public bool IsFallbackMatch { get { return _isFallbackMatch; } }
|
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;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Logging
|
namespace Microsoft.AspNet.Mvc.Logging
|
||||||
{
|
{
|
||||||
internal static class InnerAttributeRouteLoggerExtensions
|
internal static class TreeRouterLoggerExtensions
|
||||||
{
|
{
|
||||||
private static readonly Action<ILogger, string, string, Exception> _matchedRouteName;
|
private static readonly Action<ILogger, string, string, Exception> _matchedRouteName;
|
||||||
|
|
||||||
static InnerAttributeRouteLoggerExtensions()
|
static TreeRouterLoggerExtensions()
|
||||||
{
|
{
|
||||||
_matchedRouteName = LoggerMessage.Define<string, string>(
|
_matchedRouteName = LoggerMessage.Define<string, string>(
|
||||||
LogLevel.Verbose,
|
LogLevel.Verbose,
|
||||||
|
|
@ -19,12 +19,10 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
private readonly IRouter _target;
|
private readonly IRouter _target;
|
||||||
private readonly IActionDescriptorsCollectionProvider _actionDescriptorsCollectionProvider;
|
private readonly IActionDescriptorsCollectionProvider _actionDescriptorsCollectionProvider;
|
||||||
private readonly IInlineConstraintResolver _constraintResolver;
|
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 _routeLogger;
|
||||||
private readonly ILogger _constraintLogger;
|
private readonly ILogger _constraintLogger;
|
||||||
|
|
||||||
private InnerAttributeRoute _inner;
|
private TreeRouter _router;
|
||||||
|
|
||||||
public AttributeRoute(
|
public AttributeRoute(
|
||||||
IRouter target,
|
IRouter target,
|
||||||
|
|
@ -56,48 +54,48 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
_actionDescriptorsCollectionProvider = actionDescriptorsCollectionProvider;
|
_actionDescriptorsCollectionProvider = actionDescriptorsCollectionProvider;
|
||||||
_constraintResolver = constraintResolver;
|
_constraintResolver = constraintResolver;
|
||||||
|
|
||||||
_routeLogger = loggerFactory.CreateLogger<InnerAttributeRoute>();
|
_routeLogger = loggerFactory.CreateLogger<TreeRouter>();
|
||||||
_constraintLogger = loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
|
_constraintLogger = loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||||
{
|
{
|
||||||
var route = GetInnerRoute();
|
var router = GetTreeRouter();
|
||||||
return route.GetVirtualPath(context);
|
return router.GetVirtualPath(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public Task RouteAsync(RouteContext context)
|
public Task RouteAsync(RouteContext context)
|
||||||
{
|
{
|
||||||
var route = GetInnerRoute();
|
var router = GetTreeRouter();
|
||||||
return route.RouteAsync(context);
|
return router.RouteAsync(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
private InnerAttributeRoute GetInnerRoute()
|
private TreeRouter GetTreeRouter()
|
||||||
{
|
{
|
||||||
var actions = _actionDescriptorsCollectionProvider.ActionDescriptors;
|
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.
|
// 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);
|
var routeInfos = GetRouteInfos(_constraintResolver, actions.Items);
|
||||||
|
|
||||||
// We're creating one AttributeRouteGenerationEntry per action. This allows us to match the intended
|
// 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.
|
// action by expected route values, and then use the TemplateBinder to generate the link.
|
||||||
var generationEntries = new List<AttributeRouteLinkGenerationEntry>();
|
|
||||||
foreach (var routeInfo in routeInfos)
|
foreach (var routeInfo in routeInfos)
|
||||||
{
|
{
|
||||||
generationEntries.Add(new AttributeRouteLinkGenerationEntry()
|
routeBuilder.Add(new TreeRouteLinkGenerationEntry()
|
||||||
{
|
{
|
||||||
Binder = new TemplateBinder(routeInfo.ParsedTemplate, routeInfo.Defaults),
|
Binder = new TemplateBinder(routeInfo.ParsedTemplate, routeInfo.Defaults),
|
||||||
Defaults = 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,
|
// 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.
|
// so we only need to hang on to a single instance of the RouteInfo for each group.
|
||||||
var distinctRouteInfosByGroup = GroupRouteInfosByGroupId(routeInfos);
|
var distinctRouteInfosByGroup = GroupRouteInfosByGroupId(routeInfos);
|
||||||
var matchingEntries = new List<AttributeRouteMatchingEntry>();
|
|
||||||
foreach (var routeInfo in distinctRouteInfosByGroup)
|
foreach (var routeInfo in distinctRouteInfosByGroup)
|
||||||
{
|
{
|
||||||
matchingEntries.Add(new AttributeRouteMatchingEntry()
|
routeBuilder.Add(new TreeRouteMatchingEntry()
|
||||||
{
|
{
|
||||||
Order = routeInfo.Order,
|
Order = routeInfo.Order,
|
||||||
Precedence = routeInfo.MatchPrecedence,
|
Precedence = routeInfo.MatchPrecedence,
|
||||||
Target = _target,
|
Target = _target,
|
||||||
RouteName = routeInfo.Name,
|
RouteName = routeInfo.Name,
|
||||||
RouteTemplate = routeInfo.RouteTemplate,
|
RouteTemplate = TemplateParser.Parse(routeInfo.RouteTemplate),
|
||||||
TemplateMatcher = new TemplateMatcher(
|
TemplateMatcher = new TemplateMatcher(
|
||||||
routeInfo.ParsedTemplate,
|
routeInfo.ParsedTemplate,
|
||||||
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase)
|
||||||
|
|
@ -136,13 +133,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return new InnerAttributeRoute(
|
return routeBuilder.Build(actions.Version);
|
||||||
_target,
|
|
||||||
matchingEntries,
|
|
||||||
generationEntries,
|
|
||||||
_routeLogger,
|
|
||||||
_constraintLogger,
|
|
||||||
actions.Version);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<RouteInfo> GroupRouteInfosByGroupId(List<RouteInfo> routeInfos)
|
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
|
namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to build an <see cref="InnerAttributeRoute"/>. Represents an individual URL-generating route that will be
|
/// Used to build a <see cref="TreeRouter"/>. Represents an individual URL-generating route that will be
|
||||||
/// aggregated into the <see cref="InnerAttributeRoute"/>.
|
/// aggregated into the <see cref="TreeRouter"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AttributeRouteLinkGenerationEntry
|
public class TreeRouteLinkGenerationEntry
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The <see cref="TemplateBinder"/>.
|
/// The <see cref="TemplateBinder"/>.
|
||||||
|
|
@ -9,10 +9,10 @@ using Microsoft.Extensions.Logging;
|
||||||
namespace Microsoft.AspNet.Mvc.Routing
|
namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Used to build an <see cref="InnerAttributeRoute"/>. Represents an individual URL-matching route that will be
|
/// Used to build an <see cref="TreeRouter"/>. Represents an individual URL-matching route that will be
|
||||||
/// aggregated into the <see cref="InnerAttributeRoute"/>.
|
/// aggregated into the <see cref="TreeRouter"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class AttributeRouteMatchingEntry
|
public class TreeRouteMatchingEntry
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// The order of the template.
|
/// The order of the template.
|
||||||
|
|
@ -28,7 +28,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
|
|
||||||
public string RouteName { get; set; }
|
public string RouteName { get; set; }
|
||||||
|
|
||||||
public string RouteTemplate { get; set; }
|
public RouteTemplate RouteTemplate { get; set; }
|
||||||
|
|
||||||
public TemplateMatcher TemplateMatcher { 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.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
|
|
@ -9,7 +10,8 @@ using Microsoft.AspNet.Mvc.Core;
|
||||||
using Microsoft.AspNet.Mvc.Internal.Routing;
|
using Microsoft.AspNet.Mvc.Internal.Routing;
|
||||||
using Microsoft.AspNet.Mvc.Logging;
|
using Microsoft.AspNet.Mvc.Logging;
|
||||||
using Microsoft.AspNet.Routing;
|
using Microsoft.AspNet.Routing;
|
||||||
using Microsoft.AspNet.Routing.Template;
|
using Microsoft.AspNet.Routing.Internal;
|
||||||
|
using Microsoft.Extensions.Internal;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Routing
|
namespace Microsoft.AspNet.Mvc.Routing
|
||||||
|
|
@ -17,26 +19,31 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An <see cref="IRouter"/> implementation for attribute routing.
|
/// An <see cref="IRouter"/> implementation for attribute routing.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class InnerAttributeRoute : IRouter
|
public class TreeRouter : IRouter
|
||||||
{
|
{
|
||||||
private readonly IRouter _next;
|
private readonly IRouter _next;
|
||||||
private readonly LinkGenerationDecisionTree _linkGenerationTree;
|
private readonly LinkGenerationDecisionTree _linkGenerationTree;
|
||||||
private readonly AttributeRouteMatchingEntry[] _matchingEntries;
|
private readonly UrlMatchingTree[] _trees;
|
||||||
private readonly IDictionary<string, AttributeRouteLinkGenerationEntry> _namedEntries;
|
private readonly IDictionary<string, TreeRouteLinkGenerationEntry> _namedEntries;
|
||||||
|
|
||||||
private ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
private ILogger _constraintLogger;
|
private readonly ILogger _constraintLogger;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a new <see cref="InnerAttributeRoute"/>.
|
/// Creates a new <see cref="TreeRouter"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="next">The next router. Invoked when a route entry matches.</param>
|
/// <param name="next">The next router. Invoked when a route entry matches.</param>
|
||||||
/// <param name="entries">The set of route entries.</param>
|
/// <param name="trees">The list of <see cref="UrlMatchingTree"/> that contains the route entries.</param>
|
||||||
public InnerAttributeRoute(
|
/// <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,
|
IRouter next,
|
||||||
IEnumerable<AttributeRouteMatchingEntry> matchingEntries,
|
UrlMatchingTree[] trees,
|
||||||
IEnumerable<AttributeRouteLinkGenerationEntry> linkGenerationEntries,
|
IEnumerable<TreeRouteLinkGenerationEntry> linkGenerationEntries,
|
||||||
ILogger logger,
|
ILogger routeLogger,
|
||||||
ILogger constraintLogger,
|
ILogger constraintLogger,
|
||||||
int version)
|
int version)
|
||||||
{
|
{
|
||||||
|
|
@ -45,9 +52,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
throw new ArgumentNullException(nameof(next));
|
throw new ArgumentNullException(nameof(next));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (matchingEntries == null)
|
if (trees == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(matchingEntries));
|
throw new ArgumentNullException(nameof(trees));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (linkGenerationEntries == null)
|
if (linkGenerationEntries == null)
|
||||||
|
|
@ -55,9 +62,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
throw new ArgumentNullException(nameof(linkGenerationEntries));
|
throw new ArgumentNullException(nameof(linkGenerationEntries));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (logger == null)
|
if (routeLogger == null)
|
||||||
{
|
{
|
||||||
throw new ArgumentNullException(nameof(logger));
|
throw new ArgumentNullException(nameof(routeLogger));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (constraintLogger == null)
|
if (constraintLogger == null)
|
||||||
|
|
@ -66,23 +73,11 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
_next = next;
|
_next = next;
|
||||||
_logger = logger;
|
_trees = trees;
|
||||||
|
_logger = routeLogger;
|
||||||
_constraintLogger = constraintLogger;
|
_constraintLogger = constraintLogger;
|
||||||
|
|
||||||
Version = version;
|
var namedEntries = new Dictionary<string, TreeRouteLinkGenerationEntry>(
|
||||||
|
|
||||||
// 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>(
|
|
||||||
StringComparer.OrdinalIgnoreCase);
|
StringComparer.OrdinalIgnoreCase);
|
||||||
|
|
||||||
foreach (var entry in linkGenerationEntries)
|
foreach (var entry in linkGenerationEntries)
|
||||||
|
|
@ -96,7 +91,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
// We only need to keep one AttributeRouteLinkGenerationEntry per route template
|
// 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
|
// so in case two entries have the same name and the same template we only keep
|
||||||
// the first entry.
|
// the first entry.
|
||||||
AttributeRouteLinkGenerationEntry namedEntry = null;
|
TreeRouteLinkGenerationEntry namedEntry = null;
|
||||||
if (namedEntries.TryGetValue(entry.Name, out namedEntry) &&
|
if (namedEntries.TryGetValue(entry.Name, out namedEntry) &&
|
||||||
!namedEntry.TemplateText.Equals(entry.TemplateText, StringComparison.OrdinalIgnoreCase))
|
!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.
|
// The decision tree will take care of ordering for these entries.
|
||||||
_linkGenerationTree = new LinkGenerationDecisionTree(linkGenerationEntries.ToArray());
|
_linkGenerationTree = new LinkGenerationDecisionTree(linkGenerationEntries.ToArray());
|
||||||
|
|
||||||
|
Version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -122,67 +119,6 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public int Version { get; }
|
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 />
|
/// <inheritdoc />
|
||||||
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
public VirtualPathData GetVirtualPath(VirtualPathContext context)
|
||||||
{
|
{
|
||||||
|
|
@ -215,9 +151,232 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return null;
|
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)
|
private VirtualPathData GetVirtualPathForNamedRoute(VirtualPathContext context)
|
||||||
{
|
{
|
||||||
AttributeRouteLinkGenerationEntry entry;
|
TreeRouteLinkGenerationEntry entry;
|
||||||
if (_namedEntries.TryGetValue(context.RouteName, out entry))
|
if (_namedEntries.TryGetValue(context.RouteName, out entry))
|
||||||
{
|
{
|
||||||
var path = GenerateVirtualPath(context, entry);
|
var path = GenerateVirtualPath(context, entry);
|
||||||
|
|
@ -230,7 +389,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return null;
|
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
|
// 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
|
// 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);
|
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-*",
|
"version": "1.0.0-*",
|
||||||
"type": "build"
|
"type": "build"
|
||||||
},
|
},
|
||||||
|
"Microsoft.Extensions.HashCodeCombiner.Sources": {
|
||||||
|
"type": "build",
|
||||||
|
"version": "1.0.0-*"
|
||||||
|
},
|
||||||
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
"Microsoft.Extensions.Logging.Abstractions": "1.0.0-*",
|
||||||
"Microsoft.Extensions.MemoryPool": "1.0.0-*",
|
"Microsoft.Extensions.MemoryPool": "1.0.0-*",
|
||||||
"Microsoft.Extensions.PropertyActivator.Sources": {
|
"Microsoft.Extensions.PropertyActivator.Sources": {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectSingleEntry_NoCriteria()
|
public void SelectSingleEntry_NoCriteria()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry = CreateEntry(new { });
|
var entry = CreateEntry(new { });
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectSingleEntry_MultipleCriteria()
|
public void SelectSingleEntry_MultipleCriteria()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectSingleEntry_MultipleCriteria_AmbientValues()
|
public void SelectSingleEntry_MultipleCriteria_AmbientValues()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
|
|
@ -78,7 +78,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectSingleEntry_MultipleCriteria_Replaced()
|
public void SelectSingleEntry_MultipleCriteria_Replaced()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectSingleEntry_MultipleCriteria_AmbientValue_Ignored()
|
public void SelectSingleEntry_MultipleCriteria_AmbientValue_Ignored()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry = CreateEntry(new { controller = "Store", action = (string)null });
|
var entry = CreateEntry(new { controller = "Store", action = (string)null });
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
|
|
@ -126,7 +126,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectSingleEntry_MultipleCriteria_NoMatch()
|
public void SelectSingleEntry_MultipleCriteria_NoMatch()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
|
|
@ -146,7 +146,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectSingleEntry_MultipleCriteria_AmbientValue_NoMatch()
|
public void SelectSingleEntry_MultipleCriteria_AmbientValue_NoMatch()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry);
|
entries.Add(entry);
|
||||||
|
|
@ -168,7 +168,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectMultipleEntries_OneDoesntMatch()
|
public void SelectMultipleEntries_OneDoesntMatch()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry1);
|
entries.Add(entry1);
|
||||||
|
|
@ -193,7 +193,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectMultipleEntries_BothMatch_CriteriaSubset()
|
public void SelectMultipleEntries_BothMatch_CriteriaSubset()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry1);
|
entries.Add(entry1);
|
||||||
|
|
@ -219,7 +219,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectMultipleEntries_BothMatch_NonOverlappingCriteria()
|
public void SelectMultipleEntries_BothMatch_NonOverlappingCriteria()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entries.Add(entry1);
|
entries.Add(entry1);
|
||||||
|
|
@ -244,7 +244,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectMultipleEntries_BothMatch_OrderedByOrder()
|
public void SelectMultipleEntries_BothMatch_OrderedByOrder()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entry1.GenerationPrecedence = 0;
|
entry1.GenerationPrecedence = 0;
|
||||||
|
|
@ -271,7 +271,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectMultipleEntries_BothMatch_OrderedByPrecedence()
|
public void SelectMultipleEntries_BothMatch_OrderedByPrecedence()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entry1.GenerationPrecedence = 1;
|
entry1.GenerationPrecedence = 1;
|
||||||
|
|
@ -297,7 +297,7 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
public void SelectMultipleEntries_BothMatch_OrderedByTemplate()
|
public void SelectMultipleEntries_BothMatch_OrderedByTemplate()
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var entries = new List<AttributeRouteLinkGenerationEntry>();
|
var entries = new List<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
var entry1 = CreateEntry(new { controller = "Store", action = "Buy" });
|
||||||
entry1.TemplateText = "a";
|
entry1.TemplateText = "a";
|
||||||
|
|
@ -318,9 +318,9 @@ namespace Microsoft.AspNet.Mvc.Internal.Routing
|
||||||
Assert.Equal(entries, matches);
|
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);
|
entry.RequiredLinkValues = new RouteValueDictionary(requiredValues);
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ using Xunit;
|
||||||
|
|
||||||
namespace Microsoft.AspNet.Mvc.Routing
|
namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
public class InnerAttributeRouteTest
|
public class TreeRouterTest
|
||||||
{
|
{
|
||||||
[Theory]
|
[Theory]
|
||||||
[InlineData("template/5", "template/{parameter:int}")]
|
[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.
|
// try to route the request, the route with a higher precedence gets tried first.
|
||||||
var matchingRoutes = new[] { secondRoute, firstRoute };
|
var matchingRoutes = new[] { secondRoute, firstRoute };
|
||||||
|
|
||||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
||||||
|
|
||||||
|
|
@ -102,7 +102,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
// relative order gets tried first.
|
// relative order gets tried first.
|
||||||
var matchingRoutes = new[] { firstRoute, secondRoute };
|
var matchingRoutes = new[] { firstRoute, secondRoute };
|
||||||
|
|
||||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
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.
|
// we try to route the request, the route with the higher relative order gets tried first.
|
||||||
var matchingRoutes = new[] { firstRoute, secondRoute };
|
var matchingRoutes = new[] { firstRoute, secondRoute };
|
||||||
|
|
||||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
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.
|
// we try to route the request, the route with the higher template order gets tried first.
|
||||||
var matchingRoutes = new[] { secondRoute, firstRoute };
|
var matchingRoutes = new[] { secondRoute, firstRoute };
|
||||||
|
|
||||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
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.
|
// try to route the request, the route with a higher precedence gets tried first.
|
||||||
var matchingRoutes = new[] { firstRoute };
|
var matchingRoutes = new[] { firstRoute };
|
||||||
|
|
||||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingRoutes, linkGenerationEntries);
|
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
|
// 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.
|
// try to route the request, the route with a higher precedence gets tried first.
|
||||||
var matchingEntries = new[] { firstRoute };
|
var matchingEntries = new[] { firstRoute };
|
||||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||||
var context = CreateRouteContext(request);
|
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
|
// 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.
|
// try to route the request, the route with a higher precedence gets tried first.
|
||||||
var matchingEntries = new[] { firstRoute };
|
var matchingEntries = new[] { firstRoute };
|
||||||
var linkGenerationEntries = Enumerable.Empty<AttributeRouteLinkGenerationEntry>();
|
var linkGenerationEntries = Enumerable.Empty<TreeRouteLinkGenerationEntry>();
|
||||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||||
|
|
||||||
var context = CreateRouteContext(request);
|
var context = CreateRouteContext(request);
|
||||||
|
|
@ -547,7 +547,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
})
|
})
|
||||||
.Returns((VirtualPathData)null);
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var firstEntry = CreateGenerationEntry(firstTemplate, requiredValues: null);
|
var firstEntry = CreateGenerationEntry(firstTemplate, requiredValues: null);
|
||||||
var secondEntry = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
var secondEntry = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||||
|
|
@ -598,7 +598,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
})
|
})
|
||||||
.Returns((VirtualPathData)null);
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var entry = CreateGenerationEntry(template, requiredValues: null);
|
var entry = CreateGenerationEntry(template, requiredValues: null);
|
||||||
|
|
||||||
|
|
@ -658,7 +658,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
})
|
})
|
||||||
.Returns((VirtualPathData)null);
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 1);
|
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 1);
|
||||||
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||||
|
|
@ -704,7 +704,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
})
|
})
|
||||||
.Returns((VirtualPathData)null);
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 1);
|
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 1);
|
||||||
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||||
|
|
@ -749,7 +749,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
})
|
})
|
||||||
.Returns((VirtualPathData)null);
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 0);
|
var firstRoute = CreateGenerationEntry(firstTemplate, requiredValues: null, order: 0);
|
||||||
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
var secondRoute = CreateGenerationEntry(secondTemplate, requiredValues: null, order: 0);
|
||||||
|
|
@ -778,7 +778,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var data = new TheoryData<IEnumerable<AttributeRouteLinkGenerationEntry>>();
|
var data = new TheoryData<IEnumerable<TreeRouteLinkGenerationEntry>>();
|
||||||
data.Add(new[]
|
data.Add(new[]
|
||||||
{
|
{
|
||||||
CreateGenerationEntry("template", null, 0, "NamedEntry"),
|
CreateGenerationEntry("template", null, 0, "NamedEntry"),
|
||||||
|
|
@ -806,9 +806,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(InnerAttributeRouteTest.NamedEntriesWithDifferentTemplates))]
|
[MemberData(nameof(TreeRouterTest.NamedEntriesWithDifferentTemplates))]
|
||||||
public void AttributeRoute_CreateAttributeRoute_ThrowsIfDifferentEntriesHaveTheSameName(
|
public void AttributeRoute_CreateAttributeRoute_ThrowsIfDifferentEntriesHaveTheSameName(
|
||||||
IEnumerable<AttributeRouteLinkGenerationEntry> namedEntries)
|
IEnumerable<TreeRouteLinkGenerationEntry> namedEntries)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
string expectedExceptionMessage = "Two or more routes named 'NamedEntry' have different templates." +
|
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 next = new Mock<IRouter>().Object;
|
||||||
|
|
||||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
|
||||||
|
|
||||||
// Act
|
// Act
|
||||||
|
var builder = new TreeRouteBuilder(next, NullLogger.Instance, NullLogger.Instance);
|
||||||
var exception = Assert.Throws<ArgumentException>(
|
var exception = Assert.Throws<ArgumentException>(
|
||||||
"linkGenerationEntries",
|
"linkGenerationEntries",
|
||||||
() => new InnerAttributeRoute(
|
() =>
|
||||||
next,
|
{
|
||||||
matchingEntries,
|
foreach (var entry in namedEntries)
|
||||||
namedEntries,
|
{
|
||||||
NullLogger.Instance,
|
builder.Add(entry);
|
||||||
NullLogger.Instance,
|
}
|
||||||
version: 1));
|
|
||||||
|
return builder.Build(version: 1);
|
||||||
|
});
|
||||||
|
|
||||||
Assert.Equal(expectedExceptionMessage, exception.Message, StringComparer.OrdinalIgnoreCase);
|
Assert.Equal(expectedExceptionMessage, exception.Message, StringComparer.OrdinalIgnoreCase);
|
||||||
}
|
}
|
||||||
|
|
@ -837,7 +838,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
var data = new TheoryData<IEnumerable<AttributeRouteLinkGenerationEntry>>();
|
var data = new TheoryData<IEnumerable<TreeRouteLinkGenerationEntry>>();
|
||||||
|
|
||||||
data.Add(new[]
|
data.Add(new[]
|
||||||
{
|
{
|
||||||
|
|
@ -866,9 +867,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
[MemberData(nameof(InnerAttributeRouteTest.NamedEntriesWithTheSameTemplate))]
|
[MemberData(nameof(TreeRouterTest.NamedEntriesWithTheSameTemplate))]
|
||||||
public void AttributeRoute_GeneratesLink_ForMultipleNamedEntriesWithTheSameTemplate(
|
public void AttributeRoute_GeneratesLink_ForMultipleNamedEntriesWithTheSameTemplate(
|
||||||
IEnumerable<AttributeRouteLinkGenerationEntry> namedEntries)
|
IEnumerable<TreeRouteLinkGenerationEntry> namedEntries)
|
||||||
{
|
{
|
||||||
// Arrange
|
// Arrange
|
||||||
var expectedLink = new PathString(
|
var expectedLink = new PathString(
|
||||||
|
|
@ -885,7 +886,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
})
|
})
|
||||||
.Returns((VirtualPathData)null);
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingEntries, namedEntries);
|
var route = CreateAttributeRoute(next.Object, matchingEntries, namedEntries);
|
||||||
|
|
||||||
|
|
@ -926,7 +927,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
// if it were an unnamed route.
|
// if it were an unnamed route.
|
||||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||||
|
|
||||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||||
|
|
||||||
|
|
@ -966,7 +967,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
// if it were an unnamed route.
|
// if it were an unnamed route.
|
||||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||||
|
|
||||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||||
|
|
||||||
|
|
@ -1006,7 +1007,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
// if it were an unnamed route.
|
// if it were an unnamed route.
|
||||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||||
|
|
||||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||||
|
|
||||||
|
|
@ -1048,7 +1049,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
// if it were an unnamed route.
|
// if it were an unnamed route.
|
||||||
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
var linkGenerationEntries = new[] { namedEntry, unnamedEntry };
|
||||||
|
|
||||||
var matchingEntries = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingEntries = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
|
|
||||||
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
var route = CreateAttributeRoute(next.Object, matchingEntries, linkGenerationEntries);
|
||||||
|
|
||||||
|
|
@ -1796,12 +1797,12 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
name);
|
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 routeGroup = string.Format("{0}&&{1}", order, template);
|
||||||
var entry = new AttributeRouteMatchingEntry();
|
var entry = new TreeRouteMatchingEntry();
|
||||||
entry.Target = router;
|
entry.Target = router;
|
||||||
entry.RouteTemplate = template;
|
entry.RouteTemplate = TemplateParser.Parse(template);
|
||||||
var parsedRouteTemplate = TemplateParser.Parse(template);
|
var parsedRouteTemplate = TemplateParser.Parse(template);
|
||||||
entry.TemplateMatcher = new TemplateMatcher(
|
entry.TemplateMatcher = new TemplateMatcher(
|
||||||
parsedRouteTemplate,
|
parsedRouteTemplate,
|
||||||
|
|
@ -1812,7 +1813,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static AttributeRouteLinkGenerationEntry CreateGenerationEntry(
|
private static TreeRouteLinkGenerationEntry CreateGenerationEntry(
|
||||||
string template,
|
string template,
|
||||||
object requiredValues,
|
object requiredValues,
|
||||||
int order = 0,
|
int order = 0,
|
||||||
|
|
@ -1820,7 +1821,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
{
|
{
|
||||||
var constraintResolver = CreateConstraintResolver();
|
var constraintResolver = CreateConstraintResolver();
|
||||||
|
|
||||||
var entry = new AttributeRouteLinkGenerationEntry();
|
var entry = new TreeRouteLinkGenerationEntry();
|
||||||
entry.TemplateText = template;
|
entry.TemplateText = template;
|
||||||
entry.Template = TemplateParser.Parse(template);
|
entry.Template = TemplateParser.Parse(template);
|
||||||
|
|
||||||
|
|
@ -1858,7 +1859,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
||||||
private AttributeRouteMatchingEntry CreateMatchingEntry(string template)
|
private TreeRouteMatchingEntry CreateMatchingEntry(string template)
|
||||||
{
|
{
|
||||||
var mockConstraint = new Mock<IRouteConstraint>();
|
var mockConstraint = new Mock<IRouteConstraint>();
|
||||||
mockConstraint.Setup(c => c.Match(
|
mockConstraint.Setup(c => c.Match(
|
||||||
|
|
@ -1874,9 +1875,9 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
It.IsAny<string>()))
|
It.IsAny<string>()))
|
||||||
.Returns(mockConstraint.Object);
|
.Returns(mockConstraint.Object);
|
||||||
|
|
||||||
var entry = new AttributeRouteMatchingEntry();
|
var entry = new TreeRouteMatchingEntry();
|
||||||
entry.Target = new StubRouter();
|
entry.Target = new StubRouter();
|
||||||
entry.RouteTemplate = template;
|
entry.RouteTemplate = TemplateParser.Parse(template);
|
||||||
|
|
||||||
return entry;
|
return entry;
|
||||||
}
|
}
|
||||||
|
|
@ -1895,52 +1896,61 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
return new DefaultInlineConstraintResolver(optionsMock.Object);
|
return new DefaultInlineConstraintResolver(optionsMock.Object);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InnerAttributeRoute CreateAttributeRoute(AttributeRouteLinkGenerationEntry entry)
|
private static TreeRouter CreateAttributeRoute(TreeRouteLinkGenerationEntry entry)
|
||||||
{
|
{
|
||||||
return CreateAttributeRoute(new StubRouter(), 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 });
|
return CreateAttributeRoute(next, new[] { entry });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InnerAttributeRoute CreateAttributeRoute(params AttributeRouteLinkGenerationEntry[] entries)
|
private static TreeRouter CreateAttributeRoute(params TreeRouteLinkGenerationEntry[] entries)
|
||||||
{
|
{
|
||||||
return CreateAttributeRoute(new StubRouter(), 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(
|
return CreateAttributeRoute(
|
||||||
next,
|
next,
|
||||||
Enumerable.Empty<AttributeRouteMatchingEntry>(),
|
Enumerable.Empty<TreeRouteMatchingEntry>(),
|
||||||
entries);
|
entries);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InnerAttributeRoute CreateAttributeRoute(IRouter next, params AttributeRouteMatchingEntry[] entries)
|
private static TreeRouter CreateAttributeRoute(IRouter next, params TreeRouteMatchingEntry[] entries)
|
||||||
{
|
{
|
||||||
return CreateAttributeRoute(
|
return CreateAttributeRoute(
|
||||||
next,
|
next,
|
||||||
entries,
|
entries,
|
||||||
Enumerable.Empty<AttributeRouteLinkGenerationEntry>());
|
Enumerable.Empty<TreeRouteLinkGenerationEntry>());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InnerAttributeRoute CreateAttributeRoute(
|
private static TreeRouter CreateAttributeRoute(
|
||||||
IRouter next,
|
IRouter next,
|
||||||
IEnumerable<AttributeRouteMatchingEntry> matchingEntries,
|
IEnumerable<TreeRouteMatchingEntry> matchingEntries,
|
||||||
IEnumerable<AttributeRouteLinkGenerationEntry> generationEntries)
|
IEnumerable<TreeRouteLinkGenerationEntry> generationEntries)
|
||||||
{
|
{
|
||||||
return new InnerAttributeRoute(
|
var builder = new TreeRouteBuilder(
|
||||||
next,
|
next,
|
||||||
matchingEntries,
|
|
||||||
generationEntries,
|
|
||||||
NullLogger.Instance,
|
NullLogger.Instance,
|
||||||
NullLogger.Instance,
|
NullLogger.Instance);
|
||||||
version: 1);
|
|
||||||
|
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,
|
Action<VirtualPathContext> virtualPathCallback,
|
||||||
string firstTemplate,
|
string firstTemplate,
|
||||||
string secondTemplate)
|
string secondTemplate)
|
||||||
|
|
@ -1949,7 +1959,7 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
next.Setup(n => n.GetVirtualPath(It.IsAny<VirtualPathContext>())).Callback<VirtualPathContext>(virtualPathCallback)
|
next.Setup(n => n.GetVirtualPath(It.IsAny<VirtualPathContext>())).Callback<VirtualPathContext>(virtualPathCallback)
|
||||||
.Returns((VirtualPathData)null);
|
.Returns((VirtualPathData)null);
|
||||||
|
|
||||||
var matchingRoutes = Enumerable.Empty<AttributeRouteMatchingEntry>();
|
var matchingRoutes = Enumerable.Empty<TreeRouteMatchingEntry>();
|
||||||
var firstEntry = CreateGenerationEntry(firstTemplate, requiredValues: null);
|
var firstEntry = CreateGenerationEntry(firstTemplate, requiredValues: null);
|
||||||
var secondEntry = CreateGenerationEntry(secondTemplate, requiredValues: null);
|
var secondEntry = CreateGenerationEntry(secondTemplate, requiredValues: null);
|
||||||
|
|
||||||
|
|
@ -1959,19 +1969,23 @@ namespace Microsoft.AspNet.Mvc.Routing
|
||||||
new[] { secondEntry, firstEntry });
|
new[] { secondEntry, firstEntry });
|
||||||
}
|
}
|
||||||
|
|
||||||
private static InnerAttributeRoute CreateRoutingAttributeRoute(
|
private static TreeRouter CreateRoutingAttributeRoute(
|
||||||
ILoggerFactory loggerFactory = null,
|
ILoggerFactory loggerFactory = null,
|
||||||
params AttributeRouteMatchingEntry[] entries)
|
params TreeRouteMatchingEntry[] entries)
|
||||||
{
|
{
|
||||||
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
loggerFactory = loggerFactory ?? NullLoggerFactory.Instance;
|
||||||
|
|
||||||
return new InnerAttributeRoute(
|
var builder = new TreeRouteBuilder(
|
||||||
new StubRouter(),
|
new StubRouter(),
|
||||||
entries,
|
|
||||||
Enumerable.Empty<AttributeRouteLinkGenerationEntry>(),
|
|
||||||
loggerFactory.CreateLogger<AttributeRoute>(),
|
loggerFactory.CreateLogger<AttributeRoute>(),
|
||||||
loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName),
|
loggerFactory.CreateLogger(typeof(RouteConstraintMatcher).FullName));
|
||||||
version: 1);
|
|
||||||
|
foreach (var entry in entries)
|
||||||
|
{
|
||||||
|
builder.Add(entry);
|
||||||
|
}
|
||||||
|
|
||||||
|
return builder.Build(version: 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IReadOnlyDictionary<string, IRouteConstraint> GetRouteConstriants(
|
private static IReadOnlyDictionary<string, IRouteConstraint> GetRouteConstriants(
|
||||||
Loading…
Reference in New Issue