Merge branch 'merge/release/2.2-to-master'
This commit is contained in:
commit
dd1dcaebee
|
|
@ -3,13 +3,14 @@
|
|||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
internal readonly struct Candidate
|
||||
{
|
||||
public readonly RouteEndpoint Endpoint;
|
||||
public readonly Endpoint Endpoint;
|
||||
|
||||
// Used to optimize out operations that modify route values.
|
||||
public readonly CandidateFlags Flags;
|
||||
|
|
@ -48,7 +49,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
public readonly int Score;
|
||||
|
||||
// Used in tests.
|
||||
public Candidate(RouteEndpoint endpoint)
|
||||
public Candidate(Endpoint endpoint)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
|
||||
|
|
@ -63,7 +64,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
}
|
||||
|
||||
public Candidate(
|
||||
RouteEndpoint endpoint,
|
||||
Endpoint endpoint,
|
||||
int score,
|
||||
KeyValuePair<string, object>[] slots,
|
||||
(string parameterName, int segmentIndex, int slotIndex)[] captures,
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
/// </summary>
|
||||
/// <param name="endpoints">The list of endpoints, sorted in descending priority order.</param>
|
||||
/// <param name="scores">The list of endpoint scores. <see cref="CandidateState.Score"/>.</param>
|
||||
public CandidateSet(RouteEndpoint[] endpoints, int[] scores)
|
||||
public CandidateSet(Endpoint[] endpoints, int[] scores)
|
||||
{
|
||||
Count = endpoints.Length;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
/// <summary>
|
||||
|
|
@ -8,7 +10,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
/// </summary>
|
||||
public struct CandidateState
|
||||
{
|
||||
internal CandidateState(RouteEndpoint endpoint, int score)
|
||||
internal CandidateState(Endpoint endpoint, int score)
|
||||
{
|
||||
Endpoint = endpoint;
|
||||
Score = score;
|
||||
|
|
@ -20,7 +22,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
/// <summary>
|
||||
/// Gets the <see cref="Http.Endpoint"/>.
|
||||
/// </summary>
|
||||
public RouteEndpoint Endpoint { get; }
|
||||
public Endpoint Endpoint { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Gets the score of the <see cref="Http.Endpoint"/> within the current
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
EndpointFeature feature,
|
||||
CandidateSet candidateSet)
|
||||
{
|
||||
RouteEndpoint endpoint = null;
|
||||
Endpoint endpoint = null;
|
||||
RouteValueDictionary values = null;
|
||||
int? foundScore = null;
|
||||
for (var i = 0; i < candidateSet.Count; i++)
|
||||
|
|
@ -96,7 +96,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
{
|
||||
// If we get here it's the result of an ambiguity - we're OK with this
|
||||
// being a littler slower and more allocatey.
|
||||
var matches = new List<RouteEndpoint>();
|
||||
var matches = new List<Endpoint>();
|
||||
for (var i = 0; i < candidates.Count; i++)
|
||||
{
|
||||
ref var state = ref candidates[i];
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Routing.Patterns;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
private readonly EndpointSelector _selector;
|
||||
private readonly MatcherPolicy[] _policies;
|
||||
private readonly INodeBuilderPolicy[] _nodeBuilders;
|
||||
private readonly RouteEndpointComparer _comparer;
|
||||
private readonly EndpointComparer _comparer;
|
||||
|
||||
public DfaMatcherBuilder(
|
||||
ParameterPolicyFactory parameterPolicyFactory,
|
||||
|
|
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
// Taking care to use _policies, which has been sorted.
|
||||
_nodeBuilders = _policies.OfType<INodeBuilderPolicy>().ToArray();
|
||||
_comparer = new RouteEndpointComparer(_policies.OfType<IEndpointComparerPolicy>().ToArray());
|
||||
_comparer = new EndpointComparer(_policies.OfType<IEndpointComparerPolicy>().ToArray());
|
||||
}
|
||||
|
||||
public override void AddEndpoint(RouteEndpoint endpoint)
|
||||
|
|
@ -337,7 +338,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
|
||||
// Builds an array of candidates for a node, assigns a 'score' for each
|
||||
// endpoint.
|
||||
internal Candidate[] CreateCandidates(IReadOnlyList<RouteEndpoint> endpoints)
|
||||
internal Candidate[] CreateCandidates(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
if (endpoints.Count == 0)
|
||||
{
|
||||
|
|
@ -367,76 +368,80 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
}
|
||||
|
||||
// internal for tests
|
||||
internal Candidate CreateCandidate(RouteEndpoint endpoint, int score)
|
||||
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;
|
||||
|
||||
foreach (var kvp in endpoint.RoutePattern.Defaults)
|
||||
{
|
||||
assignments.Add(kvp.Key, assignments.Count);
|
||||
slots.Add(kvp);
|
||||
}
|
||||
|
||||
for (var i = 0; i < endpoint.RoutePattern.PathSegments.Count; i++)
|
||||
{
|
||||
var segment = endpoint.RoutePattern.PathSegments[i];
|
||||
if (!segment.IsSimple)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
|
||||
if (parameterPart == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!assignments.TryGetValue(parameterPart.Name, out var 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);
|
||||
}
|
||||
|
||||
if (parameterPart.IsCatchAll)
|
||||
{
|
||||
catchAll = (parameterPart.Name, i, slotIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
captures.Add((parameterPart.Name, i, slotIndex));
|
||||
}
|
||||
}
|
||||
|
||||
var complexSegments = new List<(RoutePatternPathSegment pathSegment, int segmentIndex)>();
|
||||
for (var i = 0; i < endpoint.RoutePattern.PathSegments.Count; i++)
|
||||
var constraints = new List<KeyValuePair<string, IRouteConstraint>>();
|
||||
|
||||
if (endpoint is RouteEndpoint routeEndpoint)
|
||||
{
|
||||
var segment = endpoint.RoutePattern.PathSegments[i];
|
||||
if (segment.IsSimple)
|
||||
foreach (var kvp in routeEndpoint.RoutePattern.Defaults)
|
||||
{
|
||||
continue;
|
||||
assignments.Add(kvp.Key, assignments.Count);
|
||||
slots.Add(kvp);
|
||||
}
|
||||
|
||||
complexSegments.Add((segment, i));
|
||||
}
|
||||
|
||||
var constraints = new List<KeyValuePair<string, IRouteConstraint>>();
|
||||
foreach (var kvp in endpoint.RoutePattern.ParameterPolicies)
|
||||
{
|
||||
var parameter = endpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok
|
||||
var parameterPolicyReferences = kvp.Value;
|
||||
for (var i = 0; i < parameterPolicyReferences.Count; i++)
|
||||
for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
|
||||
{
|
||||
var reference = parameterPolicyReferences[i];
|
||||
var parameterPolicy = _parameterPolicyFactory.Create(parameter, reference);
|
||||
if (parameterPolicy is IRouteConstraint routeConstraint)
|
||||
var segment = routeEndpoint.RoutePattern.PathSegments[i];
|
||||
if (!segment.IsSimple)
|
||||
{
|
||||
constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
|
||||
continue;
|
||||
}
|
||||
|
||||
var parameterPart = segment.Parts[0] as RoutePatternParameterPart;
|
||||
if (parameterPart == null)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!assignments.TryGetValue(parameterPart.Name, out var 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);
|
||||
}
|
||||
|
||||
if (parameterPart.IsCatchAll)
|
||||
{
|
||||
catchAll = (parameterPart.Name, i, slotIndex);
|
||||
}
|
||||
else
|
||||
{
|
||||
captures.Add((parameterPart.Name, i, slotIndex));
|
||||
}
|
||||
}
|
||||
|
||||
for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; i++)
|
||||
{
|
||||
var segment = routeEndpoint.RoutePattern.PathSegments[i];
|
||||
if (segment.IsSimple)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
complexSegments.Add((segment, i));
|
||||
}
|
||||
|
||||
foreach (var kvp in routeEndpoint.RoutePattern.ParameterPolicies)
|
||||
{
|
||||
var parameter = routeEndpoint.RoutePattern.GetParameter(kvp.Key); // may be null, that's ok
|
||||
var parameterPolicyReferences = kvp.Value;
|
||||
for (var i = 0; i < parameterPolicyReferences.Count; i++)
|
||||
{
|
||||
var reference = parameterPolicyReferences[i];
|
||||
var parameterPolicy = _parameterPolicyFactory.Create(parameter, reference);
|
||||
if (parameterPolicy is IRouteConstraint routeConstraint)
|
||||
{
|
||||
constraints.Add(new KeyValuePair<string, IRouteConstraint>(kvp.Key, routeConstraint));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -549,8 +554,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
Label = parent.Label + " " + edge.State.ToString(),
|
||||
};
|
||||
|
||||
// TODO: https://github.com/aspnet/Routing/issues/648
|
||||
next.Matches.AddRange(edge.Endpoints.Cast<RouteEndpoint>().ToArray());
|
||||
next.Matches.AddRange(edge.Endpoints);
|
||||
nextWork.Add(next);
|
||||
|
||||
parent.PolicyEdges.Add(edge.State, next);
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
|||
using System.Diagnostics;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
public DfaNode()
|
||||
{
|
||||
Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
|
||||
Matches = new List<RouteEndpoint>();
|
||||
Matches = new List<Endpoint>();
|
||||
PolicyEdges = new Dictionary<object, DfaNode>();
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
// Just for diagnostics and debugging
|
||||
public string Label { get; set; }
|
||||
|
||||
public List<RouteEndpoint> Matches { get; }
|
||||
public List<Endpoint> Matches { get; }
|
||||
|
||||
public Dictionary<string, DfaNode> Literals { get; }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,163 @@
|
|||
// 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 Microsoft.AspNetCore.Http;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Use to sort and group Endpoints. RouteEndpoints are sorted before other implementations.
|
||||
//
|
||||
// NOTE:
|
||||
// When ordering endpoints, we compare the route templates as an absolute last resort.
|
||||
// This is used as a factor to ensure that we always have a predictable ordering
|
||||
// for tests, errors, etc.
|
||||
//
|
||||
// When we group endpoints we don't consider the route template, because we're trying
|
||||
// to group endpoints not separate them.
|
||||
//
|
||||
// TLDR:
|
||||
// IComparer implementation considers the template string as a tiebreaker.
|
||||
// IEqualityComparer implementation does not.
|
||||
// This is cool and good.
|
||||
internal class EndpointComparer : IComparer<Endpoint>, IEqualityComparer<Endpoint>
|
||||
{
|
||||
private readonly IComparer<Endpoint>[] _comparers;
|
||||
|
||||
public EndpointComparer(IEndpointComparerPolicy[] policies)
|
||||
{
|
||||
// Order, Precedence, (others)...
|
||||
_comparers = new IComparer<Endpoint>[2 + policies.Length];
|
||||
_comparers[0] = OrderComparer.Instance;
|
||||
_comparers[1] = PrecedenceComparer.Instance;
|
||||
for (var i = 0; i < policies.Length; i++)
|
||||
{
|
||||
_comparers[i + 2] = policies[i].Comparer;
|
||||
}
|
||||
}
|
||||
|
||||
public int Compare(Endpoint x, Endpoint y)
|
||||
{
|
||||
// We don't expose this publicly, and we should never call it on
|
||||
// a null endpoint.
|
||||
Debug.Assert(x != null);
|
||||
Debug.Assert(y != null);
|
||||
|
||||
var compare = CompareCore(x, y);
|
||||
|
||||
// Since we're sorting, use the route template as a last resort.
|
||||
return compare == 0 ? ComparePattern(x, y) : compare;
|
||||
}
|
||||
|
||||
private int ComparePattern(Endpoint x, Endpoint y)
|
||||
{
|
||||
// A RouteEndpoint always comes before a non-RouteEndpoint, regardless of its RawText value
|
||||
var routeEndpointX = x as RouteEndpoint;
|
||||
var routeEndpointY = y as RouteEndpoint;
|
||||
|
||||
if (routeEndpointX != null)
|
||||
{
|
||||
if (routeEndpointY != null)
|
||||
{
|
||||
return routeEndpointX.RoutePattern.RawText.CompareTo(routeEndpointY.RoutePattern.RawText);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else if (routeEndpointY != null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
public bool Equals(Endpoint x, Endpoint y)
|
||||
{
|
||||
// We don't expose this publicly, and we should never call it on
|
||||
// a null endpoint.
|
||||
Debug.Assert(x != null);
|
||||
Debug.Assert(y != null);
|
||||
|
||||
return CompareCore(x, y) == 0;
|
||||
}
|
||||
|
||||
public int GetHashCode(Endpoint obj)
|
||||
{
|
||||
// This should not be possible to call publicly.
|
||||
Debug.Fail("We don't expect this to be called.");
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
private int CompareCore(Endpoint x, Endpoint y)
|
||||
{
|
||||
for (var i = 0; i < _comparers.Length; i++)
|
||||
{
|
||||
var compare = _comparers[i].Compare(x, y);
|
||||
if (compare != 0)
|
||||
{
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private class OrderComparer : IComparer<Endpoint>
|
||||
{
|
||||
public static readonly IComparer<Endpoint> Instance = new OrderComparer();
|
||||
|
||||
public int Compare(Endpoint x, Endpoint y)
|
||||
{
|
||||
var routeEndpointX = x as RouteEndpoint;
|
||||
var routeEndpointY = y as RouteEndpoint;
|
||||
|
||||
if (routeEndpointX != null)
|
||||
{
|
||||
if (routeEndpointY != null)
|
||||
{
|
||||
return routeEndpointX.Order.CompareTo(routeEndpointY.Order);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else if (routeEndpointY != null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
private class PrecedenceComparer : IComparer<Endpoint>
|
||||
{
|
||||
public static readonly IComparer<Endpoint> Instance = new PrecedenceComparer();
|
||||
|
||||
public int Compare(Endpoint x, Endpoint y)
|
||||
{
|
||||
var routeEndpointX = x as RouteEndpoint;
|
||||
var routeEndpointY = y as RouteEndpoint;
|
||||
|
||||
if (routeEndpointX != null)
|
||||
{
|
||||
if (routeEndpointY != null)
|
||||
{
|
||||
return routeEndpointX.RoutePattern.InboundPrecedence
|
||||
.CompareTo(routeEndpointY.RoutePattern.InboundPrecedence);
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
else if (routeEndpointY != null)
|
||||
{
|
||||
return -1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -250,15 +250,13 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
private Endpoint CreateRejectionEndpoint(IEnumerable<string> httpMethods)
|
||||
{
|
||||
var allow = string.Join(", ", httpMethods);
|
||||
return new RouteEndpoint(
|
||||
return new Endpoint(
|
||||
(context) =>
|
||||
{
|
||||
context.Response.StatusCode = 405;
|
||||
context.Response.Headers.Add("Allow", allow);
|
||||
return Task.CompletedTask;
|
||||
},
|
||||
RoutePatternFactory.Parse("/"),
|
||||
0,
|
||||
EndpointMetadataCollection.Empty,
|
||||
Http405EndpointDisplayName);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,103 +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;
|
||||
using System.Diagnostics;
|
||||
|
||||
namespace Microsoft.AspNetCore.Routing.Matching
|
||||
{
|
||||
// Use to sort and group RouteEndpoints.
|
||||
//
|
||||
// NOTE:
|
||||
// When ordering endpoints, we compare the route templates as an absolute last resort.
|
||||
// This is used as a factor to ensure that we always have a predictable ordering
|
||||
// for tests, errors, etc.
|
||||
//
|
||||
// When we group endpoints we don't consider the route template, because we're trying
|
||||
// to group endpoints not separate them.
|
||||
//
|
||||
// TLDR:
|
||||
// IComparer implementation considers the template string as a tiebreaker.
|
||||
// IEqualityComparer implementation does not.
|
||||
// This is cool and good.
|
||||
internal class RouteEndpointComparer : IComparer<RouteEndpoint>, IEqualityComparer<RouteEndpoint>
|
||||
{
|
||||
private readonly IComparer<RouteEndpoint>[] _comparers;
|
||||
|
||||
public RouteEndpointComparer(IEndpointComparerPolicy[] policies)
|
||||
{
|
||||
// Order, Precedence, (others)...
|
||||
_comparers = new IComparer<RouteEndpoint>[2 + policies.Length];
|
||||
_comparers[0] = OrderComparer.Instance;
|
||||
_comparers[1] = PrecedenceComparer.Instance;
|
||||
for (var i = 0; i < policies.Length; i++)
|
||||
{
|
||||
_comparers[i + 2] = policies[i].Comparer;
|
||||
}
|
||||
}
|
||||
|
||||
public int Compare(RouteEndpoint x, RouteEndpoint y)
|
||||
{
|
||||
// We don't expose this publicly, and we should never call it on
|
||||
// a null endpoint.
|
||||
Debug.Assert(x != null);
|
||||
Debug.Assert(y != null);
|
||||
|
||||
var compare = CompareCore(x, y);
|
||||
|
||||
// Since we're sorting, use the route template as a last resort.
|
||||
return compare == 0 ? x.RoutePattern.RawText.CompareTo(y.RoutePattern.RawText) : compare;
|
||||
}
|
||||
|
||||
public bool Equals(RouteEndpoint x, RouteEndpoint y)
|
||||
{
|
||||
// We don't expose this publicly, and we should never call it on
|
||||
// a null endpoint.
|
||||
Debug.Assert(x != null);
|
||||
Debug.Assert(y != null);
|
||||
|
||||
return CompareCore(x, y) == 0;
|
||||
}
|
||||
|
||||
public int GetHashCode(RouteEndpoint obj)
|
||||
{
|
||||
// This should not be possible to call publicly.
|
||||
Debug.Fail("We don't expect this to be called.");
|
||||
throw new System.NotImplementedException();
|
||||
}
|
||||
|
||||
private int CompareCore(RouteEndpoint x, RouteEndpoint y)
|
||||
{
|
||||
for (var i = 0; i < _comparers.Length; i++)
|
||||
{
|
||||
var compare = _comparers[i].Compare(x, y);
|
||||
if (compare != 0)
|
||||
{
|
||||
return compare;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
private class OrderComparer : IComparer<RouteEndpoint>
|
||||
{
|
||||
public static readonly IComparer<RouteEndpoint> Instance = new OrderComparer();
|
||||
|
||||
public int Compare(RouteEndpoint x, RouteEndpoint y)
|
||||
{
|
||||
return x.Order.CompareTo(y.Order);
|
||||
}
|
||||
}
|
||||
|
||||
private class PrecedenceComparer : IComparer<RouteEndpoint>
|
||||
{
|
||||
public static readonly IComparer<RouteEndpoint> Instance = new PrecedenceComparer();
|
||||
|
||||
public int Compare(RouteEndpoint x, RouteEndpoint y)
|
||||
{
|
||||
return x.RoutePattern.InboundPrecedence.CompareTo(y.RoutePattern.InboundPrecedence);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -595,6 +595,54 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
Assert.Empty(test1_1.PolicyEdges);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildDfaTree_WithPolicies_AndBranches_NonRouteEndpoint()
|
||||
{
|
||||
// Arrange
|
||||
var builder = CreateDfaMatcherBuilder(new TestNonRoutePatternMatcherPolicy());
|
||||
|
||||
var endpoint1 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(0), });
|
||||
builder.AddEndpoint(endpoint1);
|
||||
|
||||
var endpoint2 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
|
||||
builder.AddEndpoint(endpoint2);
|
||||
|
||||
var endpoint3 = CreateEndpoint("/a", metadata: new object[] { new TestMetadata1(1), });
|
||||
builder.AddEndpoint(endpoint3);
|
||||
|
||||
// Act
|
||||
var root = builder.BuildDfaTree();
|
||||
|
||||
// Assert
|
||||
Assert.Empty(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.IsType<TestNonRoutePatternMatcherPolicy>(a.NodeBuilder);
|
||||
Assert.Collection(
|
||||
a.PolicyEdges.OrderBy(e => e.Key),
|
||||
e => Assert.Equal(0, e.Key),
|
||||
e => Assert.Equal(1, e.Key),
|
||||
e => Assert.Equal(int.MaxValue, e.Key));
|
||||
|
||||
var test1_0 = a.PolicyEdges[0];
|
||||
Assert.Equal(new[] { endpoint1, }, test1_0.Matches);
|
||||
Assert.Null(test1_0.NodeBuilder);
|
||||
Assert.Empty(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);
|
||||
|
||||
var nonRouteEndpoint = a.PolicyEdges[int.MaxValue];
|
||||
Assert.Equal("MaxValueEndpoint", Assert.Single(nonRouteEndpoint.Matches).DisplayName);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void BuildDfaTree_WithPolicies_AndBranches_BothPoliciesSkipped()
|
||||
{
|
||||
|
|
@ -968,5 +1016,35 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestNonRoutePatternMatcherPolicy : MatcherPolicy, IEndpointComparerPolicy, INodeBuilderPolicy
|
||||
{
|
||||
public override int Order => 100;
|
||||
|
||||
public IComparer<Endpoint> Comparer => EndpointMetadataComparer<TestMetadata1>.Default;
|
||||
|
||||
public bool AppliesToNode(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
return endpoints.Any(e => e.Metadata.GetMetadata<TestMetadata1>() != null);
|
||||
}
|
||||
|
||||
public PolicyJumpTable BuildJumpTable(int exitDestination, IReadOnlyList<PolicyJumpTableEdge> edges)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
public IReadOnlyList<PolicyNodeEdge> GetEdges(IReadOnlyList<Endpoint> endpoints)
|
||||
{
|
||||
var edges = endpoints
|
||||
.GroupBy(e => e.Metadata.GetMetadata<TestMetadata1>().State)
|
||||
.Select(g => new PolicyNodeEdge(g.Key, g.ToArray()))
|
||||
.ToList();
|
||||
|
||||
var maxValueEndpoint = new Endpoint(TestConstants.EmptyRequestDelegate, EndpointMetadataCollection.Empty, "MaxValueEndpoint");
|
||||
edges.Add(new PolicyNodeEdge(int.MaxValue, new[] { maxValueEndpoint }));
|
||||
|
||||
return edges;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -228,9 +228,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
|||
"test: " + template);
|
||||
}
|
||||
|
||||
private static RouteEndpointComparer CreateComparer(params IEndpointComparerPolicy[] policies)
|
||||
private static EndpointComparer CreateComparer(params IEndpointComparerPolicy[] policies)
|
||||
{
|
||||
return new RouteEndpointComparer(policies);
|
||||
return new EndpointComparer(policies);
|
||||
}
|
||||
|
||||
private class TestMetadata1
|
||||
|
|
|
|||
Loading…
Reference in New Issue