Replace InnerAttributeRoute with TreeRouter

This commit is contained in:
Ajay Bhargav Baaskaran 2015-10-28 12:05:33 -07:00
parent b8d58133c3
commit b8b222b295
13 changed files with 612 additions and 258 deletions

View File

@ -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)

View File

@ -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; } }
}

View File

@ -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,

View File

@ -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)

View File

@ -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;
});
}
}
}

View File

@ -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"/>.

View File

@ -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; }

View File

@ -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);
}
}
}

View File

@ -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; }
}
}

View File

@ -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);
}
}

View File

@ -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": {

View File

@ -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;
}

View File

@ -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(