Merge release/2.2
This commit is contained in:
commit
aadc31d9a5
|
|
@ -3,12 +3,11 @@
|
|||
|
||||
using System.Threading.Tasks;
|
||||
using BenchmarkDotNet.Attributes;
|
||||
using Microsoft.AspNetCore.Http.Features;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/Azure/azure-rest-api-specs
|
||||
public partial class MatcherAzureBenchmark : EndpointRoutingBenchmarkBase
|
||||
public class MatcherAzureBenchmark : MatcherAzureBenchmarkBase
|
||||
{
|
||||
private const int SampleCount = 100;
|
||||
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// This code was generated by the Swaggatherer
|
||||
public partial class MatcherAzureBenchmark : EndpointRoutingBenchmarkBase
|
||||
public abstract partial class MatcherAzureBenchmarkBase : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int EndpointCount = 5160;
|
||||
private protected const int EndpointCount = 5160;
|
||||
|
||||
private void SetupEndpoints()
|
||||
private protected void SetupEndpoints()
|
||||
{
|
||||
Endpoints = new RouteEndpoint[5160];
|
||||
Endpoints[0] = CreateEndpoint("/account", "GET");
|
||||
|
|
@ -5176,7 +5176,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
Endpoints[5159] = CreateEndpoint("/{tenantID}/groups/{groupObjectId}/$links/members/{memberObjectId}", "DELETE");
|
||||
}
|
||||
|
||||
private void SetupRequests()
|
||||
private protected void SetupRequests()
|
||||
{
|
||||
Requests = new HttpContext[5160];
|
||||
Requests[0] = new DefaultHttpContext();
|
||||
|
|
@ -25821,7 +25821,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
Requests[5159].Request.Path = "/67fb987d/groups/04d01a8c-6135/$links/members/71b75dbe-0076-";
|
||||
}
|
||||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
private protected Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
builder.AddEndpoint(Endpoints[0]);
|
||||
builder.AddEndpoint(Endpoints[1]);
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public class MatcherBuilderAzureBenchmark : MatcherAzureBenchmarkBase
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
_services = CreateServices();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Dfa()
|
||||
{
|
||||
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
|
||||
SetupMatcher(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public class MatcherBuilderGithubBenchmark : MatcherGithubBenchmarkBase
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
SetupEndpoints();
|
||||
|
||||
_services = CreateServices();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Dfa()
|
||||
{
|
||||
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
|
||||
SetupMatcher(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public partial class MatcherBuilderMultipleEntryBenchmark : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private IServiceProvider _services;
|
||||
|
||||
[GlobalSetup]
|
||||
public void Setup()
|
||||
{
|
||||
Endpoints = new RouteEndpoint[10];
|
||||
Endpoints[0] = CreateEndpoint("/product", "GET");
|
||||
Endpoints[1] = CreateEndpoint("/product/{id}", "GET");
|
||||
|
||||
Endpoints[2] = CreateEndpoint("/account", "GET");
|
||||
Endpoints[3] = CreateEndpoint("/account/{id}");
|
||||
Endpoints[4] = CreateEndpoint("/account/{id}", "POST");
|
||||
Endpoints[5] = CreateEndpoint("/account/{id}", "UPDATE");
|
||||
|
||||
Endpoints[6] = CreateEndpoint("/v2/account", "GET");
|
||||
Endpoints[7] = CreateEndpoint("/v2/account/{id}");
|
||||
Endpoints[8] = CreateEndpoint("/v2/account/{id}", "POST");
|
||||
Endpoints[9] = CreateEndpoint("/v2/account/{id}", "UPDATE");
|
||||
|
||||
_services = CreateServices();
|
||||
}
|
||||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
for (int i = 0; i < Endpoints.Length; i++)
|
||||
{
|
||||
builder.AddEndpoint(Endpoints[i]);
|
||||
}
|
||||
return builder.Build();
|
||||
}
|
||||
|
||||
[Benchmark]
|
||||
public void Dfa()
|
||||
{
|
||||
var builder = _services.GetRequiredService<DfaMatcherBuilder>();
|
||||
SetupMatcher(builder);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
// Generated from https://github.com/APIs-guru/openapi-directory
|
||||
// Use https://editor2.swagger.io/ to convert from yaml to json-
|
||||
public partial class MatcherGithubBenchmark : EndpointRoutingBenchmarkBase
|
||||
public class MatcherGithubBenchmark : MatcherGithubBenchmarkBase
|
||||
{
|
||||
private BarebonesMatcher _baseline;
|
||||
private Matcher _dfa;
|
||||
|
|
|
|||
|
|
@ -7,11 +7,11 @@ using Microsoft.AspNetCore.Http;
|
|||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// This code was generated by the Swaggatherer
|
||||
public partial class MatcherGithubBenchmark : EndpointRoutingBenchmarkBase
|
||||
public partial class MatcherGithubBenchmarkBase : EndpointRoutingBenchmarkBase
|
||||
{
|
||||
private const int EndpointCount = 243;
|
||||
private protected const int EndpointCount = 243;
|
||||
|
||||
private void SetupEndpoints()
|
||||
private protected void SetupEndpoints()
|
||||
{
|
||||
Endpoints = new RouteEndpoint[243];
|
||||
Endpoints[0] = CreateEndpoint("/emojis", "GET");
|
||||
|
|
@ -259,7 +259,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
Endpoints[242] = CreateEndpoint("/repos/{owner}/{repo}/{archive_format}/{path}", "GET");
|
||||
}
|
||||
|
||||
private void SetupRequests()
|
||||
private protected void SetupRequests()
|
||||
{
|
||||
Requests = new HttpContext[243];
|
||||
Requests[0] = new DefaultHttpContext();
|
||||
|
|
@ -1236,7 +1236,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
Requests[242].Request.Path = "/repos/21a74/f36c8/805b0492-b723-/680ad";
|
||||
}
|
||||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
private protected Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{
|
||||
builder.AddEndpoint(Endpoints[0]);
|
||||
builder.AddEndpoint(Endpoints[1]);
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
// 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 BenchmarkDotNet.Attributes;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
public class RouteEndpointAzureBenchmark : MatcherAzureBenchmarkBase
|
||||
{
|
||||
[Benchmark]
|
||||
public void CreateEndpoints()
|
||||
{
|
||||
SetupEndpoints();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
// Assign each node a sequential index.
|
||||
var visited = new Dictionary<DfaNode, int>();
|
||||
|
||||
var tree = builder.BuildDfaTree();
|
||||
var tree = builder.BuildDfaTree(includeLabel: true);
|
||||
|
||||
writer.WriteLine("digraph DFA {");
|
||||
tree.Visit(WriteNode);
|
||||
|
|
@ -64,9 +64,12 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
// We can safely index into visited because this is a post-order traversal,
|
||||
// all of the children of this node are already in the dictionary.
|
||||
|
||||
foreach (var literal in node.Literals)
|
||||
if (node.Literals != null)
|
||||
{
|
||||
writer.WriteLine($"{label} -> {visited[literal.Value]} [label=\"/{literal.Key}\"]");
|
||||
foreach (var literal in node.Literals)
|
||||
{
|
||||
writer.WriteLine($"{label} -> {visited[literal.Value]} [label=\"/{literal.Key}\"]");
|
||||
}
|
||||
}
|
||||
|
||||
if (node.Parameters != null)
|
||||
|
|
@ -79,9 +82,12 @@ namespace Microsoft.AspNetCore.Routing.Internal
|
|||
writer.WriteLine($"{label} -> {visited[node.CatchAll]} [label=\"/**\"]");
|
||||
}
|
||||
|
||||
foreach (var policy in node.PolicyEdges)
|
||||
if (node.PolicyEdges != null)
|
||||
{
|
||||
writer.WriteLine($"{label} -> {visited[policy.Value]} [label=\"{policy.Key}\"]");
|
||||
foreach (var policy in node.PolicyEdges)
|
||||
{
|
||||
writer.WriteLine($"{label} -> {visited[policy.Value]} [label=\"{policy.Key}\"]");
|
||||
}
|
||||
}
|
||||
|
||||
writer.WriteLine($"{label} [label=\"{node.Label}\"]");
|
||||
|
|
|
|||
|
|
@ -19,6 +19,15 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
private readonly INodeBuilderPolicy[] _nodeBuilders;
|
||||
private readonly EndpointComparer _comparer;
|
||||
|
||||
// These collections are reused when building candidates
|
||||
private readonly Dictionary<string, int> _assignments;
|
||||
private readonly List<KeyValuePair<string, object>> _slots;
|
||||
private readonly List<(string parameterName, int segmentIndex, int slotIndex)> _captures;
|
||||
private readonly List<(RoutePatternPathSegment pathSegment, int segmentIndex)> _complexSegments;
|
||||
private readonly List<KeyValuePair<string, IRouteConstraint>> _constraints;
|
||||
|
||||
private int _stateIndex;
|
||||
|
||||
public DfaMatcherBuilder(
|
||||
ParameterPolicyFactory parameterPolicyFactory,
|
||||
EndpointSelector selector,
|
||||
|
|
@ -31,6 +40,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Taking care to use _policies, which has been sorted.
|
||||
_nodeBuilders = _policies.OfType<INodeBuilderPolicy>().ToArray();
|
||||
_comparer = new EndpointComparer(_policies.OfType<IEndpointComparerPolicy>().ToArray());
|
||||
|
||||
_assignments = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
_slots = new List<KeyValuePair<string, object>>();
|
||||
_captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
|
||||
_complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
|
||||
_constraints = new List<KeyValuePair<string, IRouteConstraint>>();
|
||||
}
|
||||
|
||||
public override void AddEndpoint(RouteEndpoint endpoint)
|
||||
|
|
@ -38,7 +53,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
_endpoints.Add(endpoint);
|
||||
}
|
||||
|
||||
public DfaNode BuildDfaTree()
|
||||
public DfaNode BuildDfaTree(bool includeLabel = false)
|
||||
{
|
||||
// We build the tree by doing a BFS over the list of entries. This is important
|
||||
// because a 'parameter' node can also traverse the same paths that literal nodes
|
||||
|
|
@ -49,9 +64,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Since we're doing a BFS we will process each 'level' of the tree in stages
|
||||
// this list will hold the set of items we need to process at the current
|
||||
// stage.
|
||||
var work = new List<(RouteEndpoint endpoint, List<DfaNode> parents)>();
|
||||
var work = new List<(RouteEndpoint endpoint, List<DfaNode> parents)>(_endpoints.Count);
|
||||
List<(RouteEndpoint endpoint, List<DfaNode> parents)> previousWork = null;
|
||||
|
||||
var root = new DfaNode() { PathDepth = 0, Label = "/" };
|
||||
var root = new DfaNode() { PathDepth = 0, Label = includeLabel ? "/" : null };
|
||||
|
||||
// To prepare for this we need to compute the max depth, as well as
|
||||
// a seed list of items to process (entry, root).
|
||||
|
|
@ -63,14 +79,26 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
work.Add((endpoint, new List<DfaNode>() { root, }));
|
||||
}
|
||||
var workCount = work.Count;
|
||||
|
||||
// Now we process the entries a level at a time.
|
||||
for (var depth = 0; depth <= maxDepth; depth++)
|
||||
{
|
||||
// As we process items, collect the next set of items.
|
||||
var nextWork = new List<(RouteEndpoint endpoint, List<DfaNode> parents)>();
|
||||
List<(RouteEndpoint endpoint, List<DfaNode> parents)> nextWork;
|
||||
var nextWorkCount = 0;
|
||||
if (previousWork == null)
|
||||
{
|
||||
nextWork = new List<(RouteEndpoint endpoint, List<DfaNode> parents)>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reuse previous collection for the next collection
|
||||
// Don't clear the list so nested lists can be reused
|
||||
nextWork = previousWork;
|
||||
}
|
||||
|
||||
for (var i = 0; i < work.Count; i++)
|
||||
for (var i = 0; i < workCount; i++)
|
||||
{
|
||||
var (endpoint, parents) = work[i];
|
||||
|
||||
|
|
@ -79,12 +107,28 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
for (var j = 0; j < parents.Count; j++)
|
||||
{
|
||||
var parent = parents[j];
|
||||
parent.Matches.Add(endpoint);
|
||||
parent.AddMatch(endpoint);
|
||||
}
|
||||
}
|
||||
|
||||
// Find the parents of this edge at the current depth
|
||||
var nextParents = new List<DfaNode>();
|
||||
List<DfaNode> nextParents;
|
||||
if (nextWorkCount < nextWork.Count)
|
||||
{
|
||||
nextParents = nextWork[nextWorkCount].parents;
|
||||
nextParents.Clear();
|
||||
|
||||
nextWork[nextWorkCount] = (endpoint, nextParents);
|
||||
}
|
||||
else
|
||||
{
|
||||
nextParents = new List<DfaNode>();
|
||||
|
||||
// Add to the next set of work now so the list will be reused
|
||||
// even if there are no parents
|
||||
nextWork.Add((endpoint, nextParents));
|
||||
}
|
||||
|
||||
var segment = GetCurrentSegment(endpoint, depth);
|
||||
if (segment == null)
|
||||
{
|
||||
|
|
@ -97,15 +141,17 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var part = segment.Parts[0];
|
||||
if (segment.IsSimple && part is RoutePatternLiteralPart literalPart)
|
||||
{
|
||||
DfaNode next = null;
|
||||
var literal = literalPart.Content;
|
||||
if (!parent.Literals.TryGetValue(literal, out var next))
|
||||
if (parent.Literals == null ||
|
||||
!parent.Literals.TryGetValue(literal, out next))
|
||||
{
|
||||
next = new DfaNode()
|
||||
{
|
||||
PathDepth = parent.PathDepth + 1,
|
||||
Label = parent.Label + literal + "/",
|
||||
Label = includeLabel ? parent.Label + literal + "/" : null,
|
||||
};
|
||||
parent.Literals.Add(literal, next);
|
||||
parent.AddLiteral(literal, next);
|
||||
}
|
||||
|
||||
nextParents.Add(next);
|
||||
|
|
@ -115,7 +161,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// A catch all should traverse all literal nodes as well as parameter nodes
|
||||
// we don't need to create the parameter node here because of ordering
|
||||
// all catchalls will be processed after all parameters.
|
||||
nextParents.AddRange(parent.Literals.Values);
|
||||
if (parent.Literals != null)
|
||||
{
|
||||
nextParents.AddRange(parent.Literals.Values);
|
||||
}
|
||||
if (parent.Parameters != null)
|
||||
{
|
||||
nextParents.Add(parent.Parameters);
|
||||
|
|
@ -131,7 +180,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
parent.CatchAll = new DfaNode()
|
||||
{
|
||||
PathDepth = parent.PathDepth + 1,
|
||||
Label = parent.Label + "{*...}/",
|
||||
Label = includeLabel ? parent.Label + "{*...}/" : null,
|
||||
};
|
||||
|
||||
// The catchall node just loops.
|
||||
|
|
@ -139,7 +188,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
parent.CatchAll.CatchAll = parent.CatchAll;
|
||||
}
|
||||
|
||||
parent.CatchAll.Matches.Add(endpoint);
|
||||
parent.CatchAll.AddMatch(endpoint);
|
||||
}
|
||||
else if (segment.IsSimple && part.IsParameter)
|
||||
{
|
||||
|
|
@ -148,12 +197,15 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
parent.Parameters = new DfaNode()
|
||||
{
|
||||
PathDepth = parent.PathDepth + 1,
|
||||
Label = parent.Label + "{...}/",
|
||||
Label = includeLabel ? parent.Label + "{...}/" : null,
|
||||
};
|
||||
}
|
||||
|
||||
// A parameter should traverse all literal nodes as well as the parameter node
|
||||
nextParents.AddRange(parent.Literals.Values);
|
||||
if (parent.Literals != null)
|
||||
{
|
||||
nextParents.AddRange(parent.Literals.Values);
|
||||
}
|
||||
nextParents.Add(parent.Parameters);
|
||||
}
|
||||
else
|
||||
|
|
@ -167,23 +219,28 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
parent.Parameters = new DfaNode()
|
||||
{
|
||||
PathDepth = parent.PathDepth + 1,
|
||||
Label = parent.Label + "{...}/",
|
||||
Label = includeLabel ? parent.Label + "{...}/" : null,
|
||||
};
|
||||
}
|
||||
|
||||
nextParents.AddRange(parent.Literals.Values);
|
||||
if (parent.Literals != null)
|
||||
{
|
||||
nextParents.AddRange(parent.Literals.Values);
|
||||
}
|
||||
nextParents.Add(parent.Parameters);
|
||||
}
|
||||
}
|
||||
|
||||
if (nextParents.Count > 0)
|
||||
{
|
||||
nextWork.Add((endpoint, nextParents));
|
||||
nextWorkCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare the process the next stage.
|
||||
previousWork = work;
|
||||
work = nextWork;
|
||||
workCount = nextWorkCount;
|
||||
}
|
||||
|
||||
// Build the trees of policy nodes (like HTTP methods). Post-order traversal
|
||||
|
|
@ -216,76 +273,64 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
public override Matcher Build()
|
||||
{
|
||||
var root = BuildDfaTree();
|
||||
#if DEBUG
|
||||
var includeLabel = true;
|
||||
#else
|
||||
var includeLabel = false;
|
||||
#endif
|
||||
|
||||
var root = BuildDfaTree(includeLabel);
|
||||
|
||||
// State count is the number of nodes plus an exit state
|
||||
var stateCount = 1;
|
||||
var maxSegmentCount = 0;
|
||||
root.Visit((node) => maxSegmentCount = Math.Max(maxSegmentCount, node.PathDepth));
|
||||
root.Visit((node) =>
|
||||
{
|
||||
stateCount++;
|
||||
maxSegmentCount = Math.Max(maxSegmentCount, node.PathDepth);
|
||||
});
|
||||
_stateIndex = 0;
|
||||
|
||||
// The max segment count is the maximum path-node-depth +1. We need
|
||||
// the +1 to capture any additional content after the 'last' segment.
|
||||
maxSegmentCount++;
|
||||
|
||||
var states = new List<DfaState>();
|
||||
var tableBuilders = new List<(JumpTableBuilder pathBuilder, PolicyJumpTableBuilder policyBuilder)>();
|
||||
AddNode(root, states, tableBuilders);
|
||||
var states = new DfaState[stateCount];
|
||||
var exitDestination = stateCount - 1;
|
||||
AddNode(root, states, exitDestination);
|
||||
|
||||
var exit = states.Count;
|
||||
states.Add(new DfaState(Array.Empty<Candidate>(), null, null));
|
||||
tableBuilders.Add((new JumpTableBuilder() { DefaultDestination = exit, ExitDestination = exit, }, null));
|
||||
states[exitDestination] = new DfaState(
|
||||
Array.Empty<Candidate>(),
|
||||
JumpTableBuilder.Build(exitDestination, exitDestination, null),
|
||||
null);
|
||||
|
||||
for (var i = 0; i < tableBuilders.Count; i++)
|
||||
{
|
||||
if (tableBuilders[i].pathBuilder?.DefaultDestination == JumpTableBuilder.InvalidDestination)
|
||||
{
|
||||
tableBuilders[i].pathBuilder.DefaultDestination = exit;
|
||||
}
|
||||
|
||||
if (tableBuilders[i].pathBuilder?.ExitDestination == JumpTableBuilder.InvalidDestination)
|
||||
{
|
||||
tableBuilders[i].pathBuilder.ExitDestination = exit;
|
||||
}
|
||||
|
||||
if (tableBuilders[i].policyBuilder?.ExitDestination == JumpTableBuilder.InvalidDestination)
|
||||
{
|
||||
tableBuilders[i].policyBuilder.ExitDestination = exit;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < states.Count; i++)
|
||||
{
|
||||
states[i] = new DfaState(
|
||||
states[i].Candidates,
|
||||
tableBuilders[i].pathBuilder?.Build(),
|
||||
tableBuilders[i].policyBuilder?.Build());
|
||||
}
|
||||
|
||||
return new DfaMatcher(_selector, states.ToArray(), maxSegmentCount);
|
||||
return new DfaMatcher(_selector, states, maxSegmentCount);
|
||||
}
|
||||
|
||||
private int AddNode(
|
||||
DfaNode node,
|
||||
List<DfaState> states,
|
||||
List<(JumpTableBuilder pathBuilder, PolicyJumpTableBuilder policyBuilder)> tableBuilders)
|
||||
DfaState[] states,
|
||||
int exitDestination)
|
||||
{
|
||||
node.Matches.Sort(_comparer);
|
||||
node.Matches?.Sort(_comparer);
|
||||
|
||||
var stateIndex = states.Count;
|
||||
var currentStateIndex = _stateIndex;
|
||||
|
||||
var candidates = CreateCandidates(node.Matches);
|
||||
states.Add(new DfaState(candidates, null, null));
|
||||
var currentDefaultDestination = exitDestination;
|
||||
var currentExitDestination = exitDestination;
|
||||
(string text, int destination)[] pathEntries = null;
|
||||
PolicyJumpTableEdge[] policyEntries = null;
|
||||
|
||||
var pathBuilder = new JumpTableBuilder();
|
||||
tableBuilders.Add((pathBuilder, null));
|
||||
|
||||
foreach (var kvp in node.Literals)
|
||||
if (node.Literals != null)
|
||||
{
|
||||
if (kvp.Key == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
pathEntries = new (string text, int destination)[node.Literals.Count];
|
||||
|
||||
var transition = Transition(kvp.Value);
|
||||
pathBuilder.AddEntry(kvp.Key, transition);
|
||||
var index = 0;
|
||||
foreach (var kvp in node.Literals)
|
||||
{
|
||||
var transition = Transition(kvp.Value);
|
||||
pathEntries[index++] = (kvp.Key, transition);
|
||||
}
|
||||
}
|
||||
|
||||
if (node.Parameters != null &&
|
||||
|
|
@ -294,53 +339,75 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
// This node has a single transition to but it should accept zero-width segments
|
||||
// this can happen when a node only has catchall parameters.
|
||||
pathBuilder.DefaultDestination = Transition(node.Parameters);
|
||||
pathBuilder.ExitDestination = pathBuilder.DefaultDestination;
|
||||
currentExitDestination = currentDefaultDestination = Transition(node.Parameters);
|
||||
}
|
||||
else if (node.Parameters != null && node.CatchAll != null)
|
||||
{
|
||||
// This node has a separate transition for zero-width segments
|
||||
// this can happen when a node has both parameters and catchall parameters.
|
||||
pathBuilder.DefaultDestination = Transition(node.Parameters);
|
||||
pathBuilder.ExitDestination = Transition(node.CatchAll);
|
||||
currentDefaultDestination = Transition(node.Parameters);
|
||||
currentExitDestination = Transition(node.CatchAll);
|
||||
}
|
||||
else if (node.Parameters != null)
|
||||
{
|
||||
// This node has paramters but no catchall.
|
||||
pathBuilder.DefaultDestination = Transition(node.Parameters);
|
||||
currentDefaultDestination = Transition(node.Parameters);
|
||||
}
|
||||
else if (node.CatchAll != null)
|
||||
{
|
||||
// This node has a catchall but no parameters
|
||||
pathBuilder.DefaultDestination = Transition(node.CatchAll);
|
||||
pathBuilder.ExitDestination = pathBuilder.DefaultDestination;
|
||||
currentExitDestination = currentDefaultDestination = Transition(node.CatchAll);
|
||||
}
|
||||
|
||||
if (node.PolicyEdges.Count > 0)
|
||||
if (node.PolicyEdges != null && node.PolicyEdges.Count > 0)
|
||||
{
|
||||
var policyBuilder = new PolicyJumpTableBuilder(node.NodeBuilder);
|
||||
tableBuilders[stateIndex] = (pathBuilder, policyBuilder);
|
||||
policyEntries = new PolicyJumpTableEdge[node.PolicyEdges.Count];
|
||||
|
||||
var index = 0;
|
||||
foreach (var kvp in node.PolicyEdges)
|
||||
{
|
||||
policyBuilder.AddEntry(kvp.Key, Transition(kvp.Value));
|
||||
policyEntries[index++] = new PolicyJumpTableEdge(kvp.Key, Transition(kvp.Value));
|
||||
}
|
||||
}
|
||||
|
||||
return stateIndex;
|
||||
var candidates = CreateCandidates(node.Matches);
|
||||
states[currentStateIndex] = new DfaState(
|
||||
candidates,
|
||||
JumpTableBuilder.Build(currentDefaultDestination, currentExitDestination, pathEntries),
|
||||
BuildPolicy(currentExitDestination, node.NodeBuilder, policyEntries));
|
||||
|
||||
return currentStateIndex;
|
||||
|
||||
int Transition(DfaNode next)
|
||||
{
|
||||
// Break cycles
|
||||
return ReferenceEquals(node, next) ? stateIndex : AddNode(next, states, tableBuilders);
|
||||
if (ReferenceEquals(node, next))
|
||||
{
|
||||
return _stateIndex;
|
||||
}
|
||||
else
|
||||
{
|
||||
_stateIndex++;
|
||||
return AddNode(next, states, exitDestination);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static PolicyJumpTable BuildPolicy(int exitDestination, INodeBuilderPolicy nodeBuilder, PolicyJumpTableEdge[] policyEntries)
|
||||
{
|
||||
if (policyEntries == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return nodeBuilder.BuildJumpTable(exitDestination, policyEntries);
|
||||
}
|
||||
|
||||
// Builds an array of candidates for a node, assigns a 'score' for each
|
||||
// endpoint.
|
||||
internal Candidate[] CreateCandidates(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
if (endpoints.Count == 0)
|
||||
if (endpoints == null || endpoints.Count == 0)
|
||||
{
|
||||
return Array.Empty<Candidate>();
|
||||
}
|
||||
|
|
@ -370,20 +437,20 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// internal for tests
|
||||
internal Candidate CreateCandidate(Endpoint endpoint, int score)
|
||||
{
|
||||
var assignments = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
var slots = new List<KeyValuePair<string, object>>();
|
||||
var captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
|
||||
(string parameterName, int segmentIndex, int slotIndex) catchAll = default;
|
||||
_assignments.Clear();
|
||||
_slots.Clear();
|
||||
_captures.Clear();
|
||||
_complexSegments.Clear();
|
||||
_constraints.Clear();
|
||||
|
||||
var complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
|
||||
var constraints = new List<KeyValuePair<string, IRouteConstraint>>();
|
||||
(string parameterName, int segmentIndex, int slotIndex) catchAll = default;
|
||||
|
||||
if (endpoint is RouteEndpoint routeEndpoint)
|
||||
{
|
||||
foreach (var kvp in routeEndpoint.RoutePattern.Defaults)
|
||||
{
|
||||
assignments.Add(kvp.Key, assignments.Count);
|
||||
slots.Add(kvp);
|
||||
_assignments.Add(kvp.Key, _assignments.Count);
|
||||
_slots.Add(kvp);
|
||||
}
|
||||
|
||||
for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
|
||||
|
|
@ -400,13 +467,13 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
continue;
|
||||
}
|
||||
|
||||
if (!assignments.TryGetValue(parameterPart.Name, out var slotIndex))
|
||||
if (!_assignments.TryGetValue(parameterPart.Name, out var slotIndex))
|
||||
{
|
||||
slotIndex = assignments.Count;
|
||||
assignments.Add(parameterPart.Name, slotIndex);
|
||||
slotIndex = _assignments.Count;
|
||||
_assignments.Add(parameterPart.Name, slotIndex);
|
||||
|
||||
var hasDefaultValue = parameterPart.Default != null || parameterPart.IsCatchAll;
|
||||
slots.Add(hasDefaultValue ? new KeyValuePair<string, object>(parameterPart.Name, parameterPart.Default) : default);
|
||||
_slots.Add(hasDefaultValue ? new KeyValuePair<string, object>(parameterPart.Name, parameterPart.Default) : default);
|
||||
}
|
||||
|
||||
if (parameterPart.IsCatchAll)
|
||||
|
|
@ -415,7 +482,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
}
|
||||
else
|
||||
{
|
||||
captures.Add((parameterPart.Name, i, slotIndex));
|
||||
_captures.Add((parameterPart.Name, i, slotIndex));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -427,7 +494,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
continue;
|
||||
}
|
||||
|
||||
complexSegments.Add((segment, i));
|
||||
_complexSegments.Add((segment, i));
|
||||
}
|
||||
|
||||
foreach (var kvp in routeEndpoint.RoutePattern.ParameterPolicies)
|
||||
|
|
@ -440,7 +507,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var parameterPolicy = _parameterPolicyFactory.Create(parameter, reference);
|
||||
if (parameterPolicy is IRouteConstraint routeConstraint)
|
||||
{
|
||||
constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
|
||||
_constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -449,16 +516,17 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
return new Candidate(
|
||||
endpoint,
|
||||
score,
|
||||
slots.ToArray(),
|
||||
captures.ToArray(),
|
||||
_slots.ToArray(),
|
||||
_captures.ToArray(),
|
||||
catchAll,
|
||||
complexSegments.ToArray(),
|
||||
constraints.ToArray());
|
||||
_complexSegments.ToArray(),
|
||||
_constraints.ToArray());
|
||||
}
|
||||
|
||||
private int[] GetGroupLengths(DfaNode node)
|
||||
{
|
||||
if (node.Matches.Count == 0)
|
||||
var nodeMatches = node.Matches;
|
||||
if (nodeMatches == null || nodeMatches.Count == 0)
|
||||
{
|
||||
return Array.Empty<int>();
|
||||
}
|
||||
|
|
@ -466,16 +534,16 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var groups = new List<int>();
|
||||
|
||||
var length = 1;
|
||||
var exemplar = node.Matches[0];
|
||||
var exemplar = nodeMatches[0];
|
||||
|
||||
for (var i = 1; i < node.Matches.Count; i++)
|
||||
for (var i = 1; i < nodeMatches.Count; i++)
|
||||
{
|
||||
if (!_comparer.Equals(exemplar, node.Matches[i]))
|
||||
if (!_comparer.Equals(exemplar, nodeMatches[i]))
|
||||
{
|
||||
groups.Add(length);
|
||||
length = 0;
|
||||
|
||||
exemplar = node.Matches[i];
|
||||
exemplar = nodeMatches[i];
|
||||
}
|
||||
|
||||
length++;
|
||||
|
|
@ -517,24 +585,35 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
private void ApplyPolicies(DfaNode node)
|
||||
{
|
||||
if (node.Matches.Count == 0)
|
||||
if (node.Matches == null || node.Matches.Count == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Start with the current node as the root.
|
||||
var work = new List<DfaNode>() { node, };
|
||||
List<DfaNode> previousWork = null;
|
||||
for (var i = 0; i < _nodeBuilders.Length; i++)
|
||||
{
|
||||
var nodeBuilder = _nodeBuilders[i];
|
||||
|
||||
// Build a list of each
|
||||
var nextWork = new List<DfaNode>();
|
||||
List<DfaNode> nextWork;
|
||||
if (previousWork == null)
|
||||
{
|
||||
nextWork = new List<DfaNode>();
|
||||
}
|
||||
else
|
||||
{
|
||||
// Reuse previous collection for the next collection
|
||||
previousWork.Clear();
|
||||
nextWork = previousWork;
|
||||
}
|
||||
|
||||
for (var j = 0; j < work.Count; j++)
|
||||
{
|
||||
var parent = work[j];
|
||||
if (!nodeBuilder.AppliesToNode(parent.Matches))
|
||||
if (!nodeBuilder.AppliesToNode(parent.Matches ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>()))
|
||||
{
|
||||
// This node-builder doesn't care about this node, so add it to the list
|
||||
// to be processed by the next node-builder.
|
||||
|
|
@ -544,29 +623,34 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
// This node-builder does apply to this node, so we need to create new nodes for each edge,
|
||||
// and then attach them to the parent.
|
||||
var edges = nodeBuilder.GetEdges(parent.Matches);
|
||||
var edges = nodeBuilder.GetEdges(parent.Matches ?? (IReadOnlyList<Endpoint>)Array.Empty<Endpoint>());
|
||||
for (var k = 0; k < edges.Count; k++)
|
||||
{
|
||||
var edge = edges[k];
|
||||
|
||||
var next = new DfaNode()
|
||||
{
|
||||
Label = parent.Label + " " + edge.State.ToString(),
|
||||
// If parent label is null then labels are not being included
|
||||
Label = (parent.Label != null) ? parent.Label + " " + edge.State.ToString() : null,
|
||||
};
|
||||
|
||||
next.Matches.AddRange(edge.Endpoints);
|
||||
if (edge.Endpoints.Count > 0)
|
||||
{
|
||||
next.AddMatches(edge.Endpoints);
|
||||
}
|
||||
nextWork.Add(next);
|
||||
|
||||
parent.PolicyEdges.Add(edge.State, next);
|
||||
parent.AddPolicyEdge(edge.State, next);
|
||||
}
|
||||
|
||||
// Associate the node-builder so we can build a jump table later.
|
||||
parent.NodeBuilder = nodeBuilder;
|
||||
|
||||
// The parent no longer has matches, it's not considered a terminal node.
|
||||
parent.Matches.Clear();
|
||||
parent.Matches?.Clear();
|
||||
}
|
||||
|
||||
previousWork = work;
|
||||
work = nextWork;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,13 +14,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
[DebuggerDisplay("{DebuggerToString(),nq}")]
|
||||
internal class DfaNode
|
||||
{
|
||||
public DfaNode()
|
||||
{
|
||||
Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
|
||||
Matches = new List<Endpoint>();
|
||||
PolicyEdges = new Dictionary<object, DfaNode>();
|
||||
}
|
||||
|
||||
// The depth of the node. The depth indicates the number of segments
|
||||
// that must be processed to arrive at this node.
|
||||
//
|
||||
|
|
@ -30,9 +23,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Just for diagnostics and debugging
|
||||
public string Label { get; set; }
|
||||
|
||||
public List<Endpoint> Matches { get; }
|
||||
public List<Endpoint> Matches { get; private set; }
|
||||
|
||||
public Dictionary<string, DfaNode> Literals { get; }
|
||||
public Dictionary<string, DfaNode> Literals { get; private set; }
|
||||
|
||||
public DfaNode Parameters { get; set; }
|
||||
|
||||
|
|
@ -40,13 +33,58 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
public INodeBuilderPolicy NodeBuilder { get; set; }
|
||||
|
||||
public Dictionary<object, DfaNode> PolicyEdges { get; }
|
||||
public Dictionary<object, DfaNode> PolicyEdges { get; private set; }
|
||||
|
||||
public void AddPolicyEdge(object state, DfaNode node)
|
||||
{
|
||||
if (PolicyEdges == null)
|
||||
{
|
||||
PolicyEdges = new Dictionary<object, DfaNode>();
|
||||
}
|
||||
|
||||
PolicyEdges.Add(state, node);
|
||||
}
|
||||
|
||||
public void AddLiteral(string literal, DfaNode node)
|
||||
{
|
||||
if (Literals == null)
|
||||
{
|
||||
Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
Literals.Add(literal, node);
|
||||
}
|
||||
|
||||
public void AddMatch(Endpoint endpoint)
|
||||
{
|
||||
if (Matches == null)
|
||||
{
|
||||
Matches = new List<Endpoint>();
|
||||
}
|
||||
|
||||
Matches.Add(endpoint);
|
||||
}
|
||||
|
||||
public void AddMatches(IEnumerable<Endpoint> endpoints)
|
||||
{
|
||||
if (Matches == null)
|
||||
{
|
||||
Matches = new List<Endpoint>(endpoints);
|
||||
}
|
||||
else
|
||||
{
|
||||
Matches.AddRange(endpoints);
|
||||
}
|
||||
}
|
||||
|
||||
public void Visit(Action<DfaNode> visitor)
|
||||
{
|
||||
foreach (var kvp in Literals)
|
||||
if (Literals != null)
|
||||
{
|
||||
kvp.Value.Visit(visitor);
|
||||
foreach (var kvp in Literals)
|
||||
{
|
||||
kvp.Value.Visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
// Break cycles
|
||||
|
|
@ -61,9 +99,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
CatchAll.Visit(visitor);
|
||||
}
|
||||
|
||||
foreach (var kvp in PolicyEdges)
|
||||
if (PolicyEdges != null)
|
||||
{
|
||||
kvp.Value.Visit(visitor);
|
||||
foreach (var kvp in PolicyEdges)
|
||||
{
|
||||
kvp.Value.Visit(visitor);
|
||||
}
|
||||
}
|
||||
|
||||
visitor(this);
|
||||
|
|
@ -76,9 +117,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
builder.Append(" d:");
|
||||
builder.Append(PathDepth);
|
||||
builder.Append(" m:");
|
||||
builder.Append(Matches.Count);
|
||||
builder.Append(Matches?.Count ?? 0);
|
||||
builder.Append(" c: ");
|
||||
builder.Append(string.Join(", ", Literals.Select(kvp => $"{kvp.Key}->({FormatNode(kvp.Value)})")));
|
||||
if (Literals != null)
|
||||
{
|
||||
builder.Append(string.Join(", ", Literals.Select(kvp => $"{kvp.Key}->({FormatNode(kvp.Value)})")));
|
||||
}
|
||||
return builder.ToString();
|
||||
|
||||
// DfaNodes can be self-referential, don't traverse cycles.
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
if (endpoints[i].Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods.Any() == true)
|
||||
if (endpoints[i].Metadata.GetMetadata<IHttpMethodMetadata>()?.HttpMethods.Count > 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
|
@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
//
|
||||
// For now we're just building up the set of keys. We don't add any endpoints
|
||||
// to lists now because we don't want ordering problems.
|
||||
var allHttpMethods = new HashSet<string>(StringComparer.OrdinalIgnoreCase);
|
||||
var allHttpMethods = new List<string>();
|
||||
var edges = new Dictionary<EdgeKey, List<Endpoint>>();
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
{
|
||||
|
|
@ -120,11 +120,16 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Also if it's not the *any* method key, then track it.
|
||||
if (!string.Equals(AnyMethod, httpMethod, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
allHttpMethods.Add(httpMethod);
|
||||
if (!ContainsHttpMethod(allHttpMethods, httpMethod))
|
||||
{
|
||||
allHttpMethods.Add(httpMethod);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
allHttpMethods.Sort(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
// Now in a second loop, add endpoints to these lists. We've enumerated all of
|
||||
// the states, so we want to see which states this endpoint matches.
|
||||
for (var i = 0; i < endpoints.Count; i++)
|
||||
|
|
@ -185,14 +190,19 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
if (!edges.TryGetValue(new EdgeKey(AnyMethod, false), out var matches))
|
||||
{
|
||||
// Methods sorted for testability.
|
||||
var endpoint = CreateRejectionEndpoint(allHttpMethods.OrderBy(m => m));
|
||||
var endpoint = CreateRejectionEndpoint(allHttpMethods);
|
||||
matches = new List<Endpoint>() { endpoint, };
|
||||
edges[new EdgeKey(AnyMethod, false)] = matches;
|
||||
}
|
||||
|
||||
return edges
|
||||
.Select(kvp => new PolicyNodeEdge(kvp.Key, kvp.Value))
|
||||
.ToArray();
|
||||
var policyNodeEdges = new PolicyNodeEdge[edges.Count];
|
||||
var index = 0;
|
||||
foreach (var kvp in edges)
|
||||
{
|
||||
policyNodeEdges[index++] = new PolicyNodeEdge(kvp.Key, kvp.Value);
|
||||
}
|
||||
|
||||
return policyNodeEdges;
|
||||
|
||||
(IReadOnlyList<string> httpMethods, bool acceptCorsPreflight) GetHttpMethods(Endpoint e)
|
||||
{
|
||||
|
|
@ -209,31 +219,41 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
/// <returns></returns>
|
||||
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
|
||||
{
|
||||
var destinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
var corsPreflightDestinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
Dictionary<string, int> destinations = null;
|
||||
Dictionary<string, int> corsPreflightDestinations = null;
|
||||
for (var i = 0; i < edges.Count; i++)
|
||||
{
|
||||
// We create this data, so it's safe to cast it.
|
||||
var key = (EdgeKey)edges[i].State;
|
||||
if (key.IsCorsPreflightRequest)
|
||||
{
|
||||
if (corsPreflightDestinations == null)
|
||||
{
|
||||
corsPreflightDestinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
corsPreflightDestinations.Add(key.HttpMethod, edges[i].Destination);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (destinations == null)
|
||||
{
|
||||
destinations = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
destinations.Add(key.HttpMethod, edges[i].Destination);
|
||||
}
|
||||
}
|
||||
|
||||
int corsPreflightExitDestination = exitDestination;
|
||||
if (corsPreflightDestinations.TryGetValue(AnyMethod, out var matchesAnyVerb))
|
||||
if (corsPreflightDestinations != null && corsPreflightDestinations.TryGetValue(AnyMethod, out var matchesAnyVerb))
|
||||
{
|
||||
// If we have endpoints that match any HTTP method, use that as the exit.
|
||||
corsPreflightExitDestination = matchesAnyVerb;
|
||||
corsPreflightDestinations.Remove(AnyMethod);
|
||||
}
|
||||
|
||||
if (destinations.TryGetValue(AnyMethod, out matchesAnyVerb))
|
||||
if (destinations != null && destinations.TryGetValue(AnyMethod, out matchesAnyVerb))
|
||||
{
|
||||
// If we have endpoints that match any HTTP method, use that as the exit.
|
||||
exitDestination = matchesAnyVerb;
|
||||
|
|
@ -261,6 +281,19 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
Http405EndpointDisplayName);
|
||||
}
|
||||
|
||||
private static bool ContainsHttpMethod(List<string> httpMethods, string httpMethod)
|
||||
{
|
||||
for (var i = 0; i < httpMethods.Count; i++)
|
||||
{
|
||||
if (StringComparer.OrdinalIgnoreCase.Equals(httpMethods[i], httpMethod))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private class HttpMethodPolicyJumpTable : PolicyJumpTable
|
||||
{
|
||||
private readonly int _exitDestination;
|
||||
|
|
@ -281,7 +314,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
_corsPreflightExitDestination = corsPreflightExitDestination;
|
||||
_corsPreflightDestinations = corsPreflightDestinations;
|
||||
|
||||
_supportsCorsPreflight = _corsPreflightDestinations.Count > 0;
|
||||
_supportsCorsPreflight = _corsPreflightDestinations != null && _corsPreflightDestinations.Count > 0;
|
||||
}
|
||||
|
||||
public override int GetDestination(HttpContext httpContext)
|
||||
|
|
@ -295,12 +328,14 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
httpContext.Request.Headers.TryGetValue(AccessControlRequestMethod, out var accessControlRequestMethod) &&
|
||||
!StringValues.IsNullOrEmpty(accessControlRequestMethod))
|
||||
{
|
||||
return _corsPreflightDestinations.TryGetValue(accessControlRequestMethod, out destination)
|
||||
return _corsPreflightDestinations != null &&
|
||||
_corsPreflightDestinations.TryGetValue(accessControlRequestMethod, out destination)
|
||||
? destination
|
||||
: _corsPreflightExitDestination;
|
||||
}
|
||||
|
||||
return _destinations.TryGetValue(httpMethod, out destination) ? destination : _exitDestination;
|
||||
return _destinations != null &&
|
||||
_destinations.TryGetValue(httpMethod, out destination) ? destination : _exitDestination;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -362,7 +397,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
public override int GetHashCode()
|
||||
{
|
||||
var hash = new HashCodeCombiner();
|
||||
hash.Add(IsCorsPreflightRequest);
|
||||
hash.Add(IsCorsPreflightRequest ? 1 : 0);
|
||||
hash.Add(HttpMethod, StringComparer.Ordinal);
|
||||
return hash;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,39 +2,24 @@
|
|||
// 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.AspNetCore.Routing.Matching
|
||||
{
|
||||
internal class JumpTableBuilder
|
||||
internal static class JumpTableBuilder
|
||||
{
|
||||
public static readonly int InvalidDestination = -1;
|
||||
|
||||
private readonly List<(string text, int destination)> _entries = new List<(string text, int destination)>();
|
||||
|
||||
// The destination state when none of the text entries match.
|
||||
public int DefaultDestination { get; set; } = InvalidDestination;
|
||||
|
||||
// The destination state for a zero-length segment. This is a special
|
||||
// case because parameters don't match a zero-length segment.
|
||||
public int ExitDestination { get; set; } = InvalidDestination;
|
||||
|
||||
public void AddEntry(string text, int destination)
|
||||
public static JumpTable Build(int defaultDestination, int exitDestination, (string text, int destination)[] pathEntries)
|
||||
{
|
||||
_entries.Add((text, destination));
|
||||
}
|
||||
|
||||
public JumpTable Build()
|
||||
{
|
||||
if (DefaultDestination == InvalidDestination)
|
||||
if (defaultDestination == InvalidDestination)
|
||||
{
|
||||
var message = $"{nameof(DefaultDestination)} is not set. Please report this as a bug.";
|
||||
var message = $"{nameof(defaultDestination)} is not set. Please report this as a bug.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
if (ExitDestination == InvalidDestination)
|
||||
if (exitDestination == InvalidDestination)
|
||||
{
|
||||
var message = $"{nameof(ExitDestination)} is not set. Please report this as a bug.";
|
||||
var message = $"{nameof(exitDestination)} is not set. Please report this as a bug.";
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
|
||||
|
|
@ -49,16 +34,16 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
// We have an optimized fast path for zero entries since we don't have to
|
||||
// do any string comparisons.
|
||||
if (_entries.Count == 0)
|
||||
if (pathEntries == null || pathEntries.Length == 0)
|
||||
{
|
||||
return new ZeroEntryJumpTable(DefaultDestination, ExitDestination);
|
||||
return new ZeroEntryJumpTable(defaultDestination, exitDestination);
|
||||
}
|
||||
|
||||
// The IL Emit jump table is not faster for a single entry
|
||||
if (_entries.Count == 1)
|
||||
if (pathEntries.Length == 1)
|
||||
{
|
||||
var entry = _entries[0];
|
||||
return new SingleEntryJumpTable(DefaultDestination, ExitDestination, entry.text, entry.destination);
|
||||
var entry = pathEntries[0];
|
||||
return new SingleEntryJumpTable(defaultDestination, exitDestination, entry.text, entry.destination);
|
||||
}
|
||||
|
||||
// We choose a hard upper bound of 100 as the limit for when we switch to a dictionary
|
||||
|
|
@ -72,9 +57,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Additionally if we're on 32bit, the scalability is worse, so switch to the dictionary at 50
|
||||
// entries.
|
||||
var threshold = IntPtr.Size == 8 ? 100 : 50;
|
||||
if (_entries.Count >= threshold)
|
||||
if (pathEntries.Length >= threshold)
|
||||
{
|
||||
return new DictionaryJumpTable(DefaultDestination, ExitDestination, _entries.ToArray());
|
||||
return new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
|
||||
}
|
||||
|
||||
// If we have more than a single string, the IL emit strategy is the fastest - but we need to decide
|
||||
|
|
@ -82,18 +67,17 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
JumpTable fallback;
|
||||
|
||||
// Based on our testing a linear search is still faster than a dictionary at ten entries.
|
||||
if (_entries.Count <= 10)
|
||||
if (pathEntries.Length <= 10)
|
||||
{
|
||||
fallback = new LinearSearchJumpTable(DefaultDestination, ExitDestination, _entries.ToArray());
|
||||
fallback = new LinearSearchJumpTable(defaultDestination, exitDestination, pathEntries);
|
||||
}
|
||||
else
|
||||
{
|
||||
fallback = new DictionaryJumpTable(DefaultDestination, ExitDestination, _entries.ToArray());
|
||||
fallback = new DictionaryJumpTable(defaultDestination, exitDestination, pathEntries);
|
||||
}
|
||||
|
||||
#if IL_EMIT
|
||||
|
||||
return new ILEmitTrieJumpTable(DefaultDestination, ExitDestination, _entries.ToArray(), vectorize: null, fallback);
|
||||
return new ILEmitTrieJumpTable(defaultDestination, exitDestination, pathEntries, vectorize: null, fallback);
|
||||
#else
|
||||
return fallback;
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,32 +0,0 @@
|
|||
// 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.Collections.Generic;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
internal class PolicyJumpTableBuilder
|
||||
{
|
||||
private readonly INodeBuilderPolicy _nodeBuilder;
|
||||
private readonly List<PolicyJumpTableEdge> _entries;
|
||||
|
||||
public PolicyJumpTableBuilder(INodeBuilderPolicy nodeBuilder)
|
||||
{
|
||||
_nodeBuilder = nodeBuilder;
|
||||
_entries = new List<PolicyJumpTableEdge>();
|
||||
}
|
||||
|
||||
// The destination state for a non-match.
|
||||
public int ExitDestination { get; set; } = JumpTableBuilder.InvalidDestination;
|
||||
|
||||
public void AddEntry(object state, int destination)
|
||||
{
|
||||
_entries.Add(new PolicyJumpTableEdge(state, destination));
|
||||
}
|
||||
|
||||
public PolicyJumpTable Build()
|
||||
{
|
||||
return _nodeBuilder.BuildJumpTable(ExitDestination, _entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -2,7 +2,9 @@
|
|||
// 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.Collections.ObjectModel;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Routing.Constraints;
|
||||
using Microsoft.AspNetCore.Routing.Matching;
|
||||
|
|
@ -16,6 +18,12 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
/// </summary>
|
||||
public static class RoutePatternFactory
|
||||
{
|
||||
private static readonly IReadOnlyDictionary<string, object> EmptyDefaultsDictionary =
|
||||
new ReadOnlyDictionary<string, object>(new Dictionary<string, object>());
|
||||
|
||||
private static readonly IReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>> EmptyPoliciesDictionary =
|
||||
new ReadOnlyDictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>(new Dictionary<string, IReadOnlyList<RoutePatternParameterPolicyReference>>());
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RoutePattern"/> from its string representation.
|
||||
/// </summary>
|
||||
|
|
@ -242,8 +250,8 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
|
||||
private static RoutePattern PatternCore(
|
||||
string rawText,
|
||||
IDictionary<string, object> defaults,
|
||||
IDictionary<string, object> parameterPolicies,
|
||||
RouteValueDictionary defaults,
|
||||
RouteValueDictionary parameterPolicies,
|
||||
IEnumerable<RoutePatternPathSegment> segments)
|
||||
{
|
||||
// We want to merge the segment data with the 'out of line' defaults and parameter policies.
|
||||
|
|
@ -257,18 +265,22 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
// It's important that these two views of the data are consistent. We don't want
|
||||
// values specified out of line to have a different behavior.
|
||||
|
||||
var updatedDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
if (defaults != null)
|
||||
Dictionary<string, object> updatedDefaults = null;
|
||||
if (defaults != null && defaults.Count > 0)
|
||||
{
|
||||
updatedDefaults = new Dictionary<string, object>(defaults.Count, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var kvp in defaults)
|
||||
{
|
||||
updatedDefaults.Add(kvp.Key, kvp.Value);
|
||||
}
|
||||
}
|
||||
|
||||
var updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(StringComparer.OrdinalIgnoreCase);
|
||||
if (parameterPolicies != null)
|
||||
Dictionary<string, List<RoutePatternParameterPolicyReference>> updatedParameterPolicies = null;
|
||||
if (parameterPolicies != null && parameterPolicies.Count > 0)
|
||||
{
|
||||
updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(parameterPolicies.Count, StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
foreach (var kvp in parameterPolicies)
|
||||
{
|
||||
updatedParameterPolicies.Add(kvp.Key, new List<RoutePatternParameterPolicyReference>()
|
||||
|
|
@ -280,7 +292,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
}
|
||||
}
|
||||
|
||||
var parameters = new List<RoutePatternParameterPart>();
|
||||
List<RoutePatternParameterPart> parameters = null;
|
||||
var updatedSegments = segments.ToArray();
|
||||
for (var i = 0; i < updatedSegments.Length; i++)
|
||||
{
|
||||
|
|
@ -291,6 +303,11 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
{
|
||||
if (segment.Parts[j] is RoutePatternParameterPart parameter)
|
||||
{
|
||||
if (parameters == null)
|
||||
{
|
||||
parameters = new List<RoutePatternParameterPart>();
|
||||
}
|
||||
|
||||
parameters.Add(parameter);
|
||||
}
|
||||
}
|
||||
|
|
@ -298,9 +315,11 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
|
||||
return new RoutePattern(
|
||||
rawText,
|
||||
updatedDefaults,
|
||||
updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray()),
|
||||
parameters,
|
||||
updatedDefaults ?? EmptyDefaultsDictionary,
|
||||
updatedParameterPolicies != null
|
||||
? updatedParameterPolicies.ToDictionary(kvp => kvp.Key, kvp => (IReadOnlyList<RoutePatternParameterPolicyReference>)kvp.Value.ToArray())
|
||||
: EmptyPoliciesDictionary,
|
||||
(IReadOnlyList<RoutePatternParameterPart>)parameters ?? Array.Empty<RoutePatternParameterPart>(),
|
||||
updatedSegments);
|
||||
|
||||
RoutePatternPathSegment VisitSegment(RoutePatternPathSegment segment)
|
||||
|
|
@ -341,7 +360,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
var parameter = (RoutePatternParameterPart)part;
|
||||
var @default = parameter.Default;
|
||||
|
||||
if (updatedDefaults.TryGetValue(parameter.Name, out var newDefault))
|
||||
if (updatedDefaults != null && updatedDefaults.TryGetValue(parameter.Name, out var newDefault))
|
||||
{
|
||||
if (parameter.Default != null && !Equals(newDefault, parameter.Default))
|
||||
{
|
||||
|
|
@ -360,12 +379,23 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
|
||||
if (parameter.Default != null)
|
||||
{
|
||||
if (updatedDefaults == null)
|
||||
{
|
||||
updatedDefaults = new Dictionary<string, object>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
updatedDefaults[parameter.Name] = parameter.Default;
|
||||
}
|
||||
|
||||
if (!updatedParameterPolicies.TryGetValue(parameter.Name, out var parameterConstraints) &&
|
||||
List<RoutePatternParameterPolicyReference> parameterConstraints = null;
|
||||
if ((updatedParameterPolicies == null || !updatedParameterPolicies.TryGetValue(parameter.Name, out parameterConstraints)) &&
|
||||
parameter.ParameterPolicies.Count > 0)
|
||||
{
|
||||
if (updatedParameterPolicies == null)
|
||||
{
|
||||
updatedParameterPolicies = new Dictionary<string, List<RoutePatternParameterPolicyReference>>(StringComparer.OrdinalIgnoreCase);
|
||||
}
|
||||
|
||||
parameterConstraints = new List<RoutePatternParameterPolicyReference>();
|
||||
updatedParameterPolicies.Add(parameter.Name, parameterConstraints);
|
||||
}
|
||||
|
|
@ -391,6 +421,7 @@ namespace Microsoft.AspNetCore.Routing.Patterns
|
|||
parameter.EncodeSlashes);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="RoutePatternPathSegment"/> from the provided collection
|
||||
/// of parts.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Assert
|
||||
Assert.Same(endpoint, Assert.Single(root.Matches));
|
||||
Assert.Null(root.Parameters);
|
||||
Assert.Empty(root.Literals);
|
||||
Assert.Null(root.Literals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -46,21 +46,21 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
Assert.Equal("a", next.Key);
|
||||
|
||||
var a = next.Value;
|
||||
Assert.Empty(a.Matches);
|
||||
Assert.Null(a.Matches);
|
||||
Assert.Null(a.Parameters);
|
||||
|
||||
next = Assert.Single(a.Literals);
|
||||
Assert.Equal("b", next.Key);
|
||||
|
||||
var b = next.Value;
|
||||
Assert.Empty(b.Matches);
|
||||
Assert.Null(b.Matches);
|
||||
Assert.Null(b.Parameters);
|
||||
|
||||
next = Assert.Single(b.Literals);
|
||||
|
|
@ -69,7 +69,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var c = next.Value;
|
||||
Assert.Same(endpoint, Assert.Single(c.Matches));
|
||||
Assert.Null(c.Parameters);
|
||||
Assert.Empty(c.Literals);
|
||||
Assert.Null(c.Literals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -85,21 +85,21 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Empty(root.Literals);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Literals);
|
||||
|
||||
var a = root.Parameters;
|
||||
Assert.Empty(a.Matches);
|
||||
Assert.Empty(a.Literals);
|
||||
Assert.Null(a.Matches);
|
||||
Assert.Null(a.Literals);
|
||||
|
||||
var b = a.Parameters;
|
||||
Assert.Empty(b.Matches);
|
||||
Assert.Empty(b.Literals);
|
||||
Assert.Null(b.Matches);
|
||||
Assert.Null(b.Literals);
|
||||
|
||||
var c = b.Parameters;
|
||||
Assert.Same(endpoint, Assert.Single(c.Matches));
|
||||
Assert.Null(c.Parameters);
|
||||
Assert.Empty(c.Literals);
|
||||
Assert.Null(c.Literals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -115,21 +115,21 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Empty(root.Literals);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Literals);
|
||||
|
||||
var a = root.Parameters;
|
||||
|
||||
// The catch all can match a path like '/a'
|
||||
Assert.Same(endpoint, Assert.Single(a.Matches));
|
||||
Assert.Empty(a.Literals);
|
||||
Assert.Null(a.Literals);
|
||||
Assert.Null(a.Parameters);
|
||||
|
||||
// Catch-all nodes include an extra transition that loops to process
|
||||
// extra segments.
|
||||
var catchAll = a.CatchAll;
|
||||
Assert.Same(endpoint, Assert.Single(catchAll.Matches));
|
||||
Assert.Empty(catchAll.Literals);
|
||||
Assert.Null(catchAll.Literals);
|
||||
Assert.Same(catchAll, catchAll.Parameters);
|
||||
Assert.Same(catchAll, catchAll.CatchAll);
|
||||
}
|
||||
|
|
@ -148,13 +148,13 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
// Assert
|
||||
Assert.Same(endpoint, Assert.Single(root.Matches));
|
||||
Assert.Empty(root.Literals);
|
||||
Assert.Null(root.Literals);
|
||||
|
||||
// Catch-all nodes include an extra transition that loops to process
|
||||
// extra segments.
|
||||
var catchAll = root.CatchAll;
|
||||
Assert.Same(endpoint, Assert.Single(catchAll.Matches));
|
||||
Assert.Empty(catchAll.Literals);
|
||||
Assert.Null(catchAll.Literals);
|
||||
Assert.Same(catchAll, catchAll.Parameters);
|
||||
}
|
||||
|
||||
|
|
@ -174,19 +174,19 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
Assert.Equal("a", next.Key);
|
||||
|
||||
var a = next.Value;
|
||||
Assert.Empty(a.Matches);
|
||||
Assert.Null(a.Matches);
|
||||
|
||||
Assert.Equal(2, a.Literals.Count);
|
||||
|
||||
var b1 = a.Literals["b1"];
|
||||
Assert.Empty(b1.Matches);
|
||||
Assert.Null(b1.Matches);
|
||||
Assert.Null(b1.Parameters);
|
||||
|
||||
next = Assert.Single(b1.Literals);
|
||||
|
|
@ -195,10 +195,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var c1 = next.Value;
|
||||
Assert.Same(endpoint1, Assert.Single(c1.Matches));
|
||||
Assert.Null(c1.Parameters);
|
||||
Assert.Empty(c1.Literals);
|
||||
Assert.Null(c1.Literals);
|
||||
|
||||
var b2 = a.Literals["b2"];
|
||||
Assert.Empty(b2.Matches);
|
||||
Assert.Null(b2.Matches);
|
||||
Assert.Null(b2.Parameters);
|
||||
|
||||
next = Assert.Single(b2.Literals);
|
||||
|
|
@ -207,7 +207,59 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var c2 = next.Value;
|
||||
Assert.Same(endpoint2, Assert.Single(c2.Matches));
|
||||
Assert.Null(c2.Parameters);
|
||||
Assert.Empty(c2.Literals);
|
||||
Assert.Null(c2.Literals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildDfaTree_MultipleEndpoint_LiteralDifferentCase()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateDfaMatcherBuilder();
|
||||
|
||||
var endpoint1 = CreateEndpoint("a/b1/c");
|
||||
builder.AddEndpoint(endpoint1);
|
||||
|
||||
var endpoint2 = CreateEndpoint("A/b2/c");
|
||||
builder.AddEndpoint(endpoint2);
|
||||
|
||||
// Act
|
||||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
Assert.Equal("a", next.Key);
|
||||
|
||||
var a = next.Value;
|
||||
Assert.Null(a.Matches);
|
||||
|
||||
Assert.Equal(2, a.Literals.Count);
|
||||
|
||||
var b1 = a.Literals["b1"];
|
||||
Assert.Null(b1.Matches);
|
||||
Assert.Null(b1.Parameters);
|
||||
|
||||
next = Assert.Single(b1.Literals);
|
||||
Assert.Equal("c", next.Key);
|
||||
|
||||
var c1 = next.Value;
|
||||
Assert.Same(endpoint1, Assert.Single(c1.Matches));
|
||||
Assert.Null(c1.Parameters);
|
||||
Assert.Null(c1.Literals);
|
||||
|
||||
var b2 = a.Literals["b2"];
|
||||
Assert.Null(b2.Matches);
|
||||
Assert.Null(b2.Parameters);
|
||||
|
||||
next = Assert.Single(b2.Literals);
|
||||
Assert.Equal("c", next.Key);
|
||||
|
||||
var c2 = next.Value;
|
||||
Assert.Same(endpoint2, Assert.Single(c2.Matches));
|
||||
Assert.Null(c2.Parameters);
|
||||
Assert.Null(c2.Literals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -226,20 +278,20 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
Assert.Equal("a", next.Key);
|
||||
|
||||
var a = next.Value;
|
||||
Assert.Empty(a.Matches);
|
||||
Assert.Null(a.Matches);
|
||||
|
||||
next = Assert.Single(a.Literals);
|
||||
Assert.Equal("b", next.Key);
|
||||
|
||||
var b = next.Value;
|
||||
Assert.Empty(b.Matches);
|
||||
Assert.Null(b.Matches);
|
||||
Assert.Null(b.Parameters);
|
||||
|
||||
next = Assert.Single(b.Literals);
|
||||
|
|
@ -251,10 +303,10 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
e => Assert.Same(endpoint1, e),
|
||||
e => Assert.Same(endpoint2, e));
|
||||
Assert.Null(c1.Parameters);
|
||||
Assert.Empty(c1.Literals);
|
||||
Assert.Null(c1.Literals);
|
||||
|
||||
var b2 = a.Parameters;
|
||||
Assert.Empty(b2.Matches);
|
||||
Assert.Null(b2.Matches);
|
||||
Assert.Null(b2.Parameters);
|
||||
|
||||
next = Assert.Single(b2.Literals);
|
||||
|
|
@ -263,7 +315,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var c2 = next.Value;
|
||||
Assert.Same(endpoint2, Assert.Single(c2.Matches));
|
||||
Assert.Null(c2.Parameters);
|
||||
Assert.Empty(c2.Literals);
|
||||
Assert.Null(c2.Literals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -282,18 +334,18 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
Assert.Equal("a", next.Key);
|
||||
|
||||
var a = next.Value;
|
||||
Assert.Empty(a.Matches);
|
||||
Assert.Empty(a.Literals);
|
||||
Assert.Null(a.Matches);
|
||||
Assert.Null(a.Literals);
|
||||
|
||||
var b = a.Parameters;
|
||||
Assert.Empty(b.Matches);
|
||||
Assert.Null(b.Matches);
|
||||
Assert.Null(b.Parameters);
|
||||
|
||||
next = Assert.Single(b.Literals);
|
||||
|
|
@ -305,7 +357,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
e => Assert.Same(endpoint1, e),
|
||||
e => Assert.Same(endpoint2, e));
|
||||
Assert.Null(c.Parameters);
|
||||
Assert.Empty(c.Literals);
|
||||
Assert.Null(c.Literals);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -324,7 +376,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -349,7 +401,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
e => Assert.Same(endpoint1, e),
|
||||
e => Assert.Same(endpoint2, e));
|
||||
Assert.Null(c1.Parameters);
|
||||
Assert.Empty(c1.Literals);
|
||||
Assert.Null(c1.Literals);
|
||||
|
||||
var catchAll = a.CatchAll;
|
||||
Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
|
||||
|
|
@ -373,7 +425,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -381,7 +433,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
var a = next.Value;
|
||||
Assert.Same(endpoint2, Assert.Single(a.Matches));
|
||||
Assert.Empty(a.Literals);
|
||||
Assert.Null(a.Literals);
|
||||
|
||||
var b1 = a.Parameters;
|
||||
Assert.Same(endpoint2, Assert.Single(a.Matches));
|
||||
|
|
@ -396,7 +448,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
e => Assert.Same(endpoint1, e),
|
||||
e => Assert.Same(endpoint2, e));
|
||||
Assert.Null(c1.Parameters);
|
||||
Assert.Empty(c1.Literals);
|
||||
Assert.Null(c1.Literals);
|
||||
|
||||
var catchAll = a.CatchAll;
|
||||
Assert.Same(endpoint2, Assert.Single(catchAll.Matches));
|
||||
|
|
@ -417,7 +469,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -440,7 +492,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var test2_true = test1_0.PolicyEdges[true];
|
||||
Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
|
||||
Assert.Null(test2_true.NodeBuilder);
|
||||
Assert.Empty(test2_true.PolicyEdges);
|
||||
Assert.Null(test2_true.PolicyEdges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -462,7 +514,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -486,7 +538,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var test2_true = test1_0.PolicyEdges[true];
|
||||
Assert.Same(endpoint1, Assert.Single(test2_true.Matches));
|
||||
Assert.Null(test2_true.NodeBuilder);
|
||||
Assert.Empty(test2_true.PolicyEdges);
|
||||
Assert.Null(test2_true.PolicyEdges);
|
||||
|
||||
var test1_1 = a.PolicyEdges[1];
|
||||
Assert.Empty(test1_1.Matches);
|
||||
|
|
@ -499,12 +551,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
test2_true = test1_1.PolicyEdges[true];
|
||||
Assert.Same(endpoint2, Assert.Single(test2_true.Matches));
|
||||
Assert.Null(test2_true.NodeBuilder);
|
||||
Assert.Empty(test2_true.PolicyEdges);
|
||||
Assert.Null(test2_true.PolicyEdges);
|
||||
|
||||
var test2_false = test1_1.PolicyEdges[false];
|
||||
Assert.Same(endpoint3, Assert.Single(test2_false.Matches));
|
||||
Assert.Null(test2_false.NodeBuilder);
|
||||
Assert.Empty(test2_false.PolicyEdges);
|
||||
Assert.Null(test2_false.PolicyEdges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -526,7 +578,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -543,12 +595,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var test2_true = a.PolicyEdges[true];
|
||||
Assert.Equal(new[] { endpoint1, endpoint2, }, test2_true.Matches);
|
||||
Assert.Null(test2_true.NodeBuilder);
|
||||
Assert.Empty(test2_true.PolicyEdges);
|
||||
Assert.Null(test2_true.PolicyEdges);
|
||||
|
||||
var test2_false = a.PolicyEdges[false];
|
||||
Assert.Equal(new[] { endpoint3, }, test2_false.Matches);
|
||||
Assert.Null(test2_false.NodeBuilder);
|
||||
Assert.Empty(test2_false.PolicyEdges);
|
||||
Assert.Null(test2_false.PolicyEdges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -570,7 +622,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -587,12 +639,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var test1_0 = a.PolicyEdges[0];
|
||||
Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
|
||||
Assert.Null(test1_0.NodeBuilder);
|
||||
Assert.Empty(test1_0.PolicyEdges);
|
||||
Assert.Null(test1_0.PolicyEdges);
|
||||
|
||||
var test1_1 = a.PolicyEdges[1];
|
||||
Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
|
||||
Assert.Null(test1_1.NodeBuilder);
|
||||
Assert.Empty(test1_1.PolicyEdges);
|
||||
Assert.Null(test1_1.PolicyEdges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
@ -614,7 +666,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -632,12 +684,12 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var test1_0 = a.PolicyEdges[0];
|
||||
Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
|
||||
Assert.Null(test1_0.NodeBuilder);
|
||||
Assert.Empty(test1_0.PolicyEdges);
|
||||
Assert.Null(test1_0.PolicyEdges);
|
||||
|
||||
var test1_1 = a.PolicyEdges[1];
|
||||
Assert.Equal(new[] { endpoint2, endpoint3, }, test1_1.Matches);
|
||||
Assert.Null(test1_1.NodeBuilder);
|
||||
Assert.Empty(test1_1.PolicyEdges);
|
||||
Assert.Null(test1_1.PolicyEdges);
|
||||
|
||||
var nonRouteEndpoint = a.PolicyEdges[int.MaxValue];
|
||||
Assert.Equal("MaxValueEndpoint", Assert.Single(nonRouteEndpoint.Matches).DisplayName);
|
||||
|
|
@ -662,7 +714,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(root.Matches);
|
||||
Assert.Null(root.Matches);
|
||||
Assert.Null(root.Parameters);
|
||||
|
||||
var next = Assert.Single(root.Literals);
|
||||
|
|
@ -671,7 +723,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
var a = next.Value;
|
||||
Assert.Equal(new[] { endpoint1, endpoint2, endpoint3, }, a.Matches);
|
||||
Assert.Null(a.NodeBuilder);
|
||||
Assert.Empty(a.PolicyEdges);
|
||||
Assert.Null(a.PolicyEdges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
|
|
|
|||
|
|
@ -74,21 +74,21 @@ namespace Microsoft.AspNetCore.Routing
|
|||
// This code was generated by the Swaggatherer
|
||||
public partial class GeneratedBenchmark : EndpointRoutingBenchmarkBase
|
||||
{{
|
||||
private const int EndpointCount = {3};
|
||||
private protected const int EndpointCount = {3};
|
||||
|
||||
private void SetupEndpoints()
|
||||
private protected void SetupEndpoints()
|
||||
{{
|
||||
Endpoints = new RouteEndpoint[{3}];
|
||||
{0}
|
||||
}}
|
||||
|
||||
private void SetupRequests()
|
||||
private protected void SetupRequests()
|
||||
{{
|
||||
Requests = new HttpContext[{3}];
|
||||
{1}
|
||||
}}
|
||||
|
||||
private Matcher SetupMatcher(MatcherBuilder builder)
|
||||
private protected Matcher SetupMatcher(MatcherBuilder builder)
|
||||
{{
|
||||
{2}
|
||||
return builder.Build();
|
||||
|
|
|
|||
Loading…
Reference in New Issue