Merge branch 'merge/release/2.2-to-master'
This commit is contained in:
commit
dd1dcaebee
|
|
@ -3,13 +3,14 @@
|
||||||
|
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
internal readonly struct Candidate
|
internal readonly struct Candidate
|
||||||
{
|
{
|
||||||
public readonly RouteEndpoint Endpoint;
|
public readonly Endpoint Endpoint;
|
||||||
|
|
||||||
// Used to optimize out operations that modify route values.
|
// Used to optimize out operations that modify route values.
|
||||||
public readonly CandidateFlags Flags;
|
public readonly CandidateFlags Flags;
|
||||||
|
|
@ -48,7 +49,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
public readonly int Score;
|
public readonly int Score;
|
||||||
|
|
||||||
// Used in tests.
|
// Used in tests.
|
||||||
public Candidate(RouteEndpoint endpoint)
|
public Candidate(Endpoint endpoint)
|
||||||
{
|
{
|
||||||
Endpoint = endpoint;
|
Endpoint = endpoint;
|
||||||
|
|
||||||
|
|
@ -63,7 +64,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
|
|
||||||
public Candidate(
|
public Candidate(
|
||||||
RouteEndpoint endpoint,
|
Endpoint endpoint,
|
||||||
int score,
|
int score,
|
||||||
KeyValuePair<string, object>[] slots,
|
KeyValuePair<string, object>[] slots,
|
||||||
(string parameterName, int segmentIndex, int slotIndex)[] captures,
|
(string parameterName, int segmentIndex, int slotIndex)[] captures,
|
||||||
|
|
|
||||||
|
|
@ -36,7 +36,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="endpoints">The list of endpoints, sorted in descending priority order.</param>
|
/// <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>
|
/// <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;
|
Count = endpoints.Length;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
// Copyright (c) .NET Foundation. All rights reserved.
|
// Copyright (c) .NET Foundation. All rights reserved.
|
||||||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
|
||||||
|
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -8,7 +10,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public struct CandidateState
|
public struct CandidateState
|
||||||
{
|
{
|
||||||
internal CandidateState(RouteEndpoint endpoint, int score)
|
internal CandidateState(Endpoint endpoint, int score)
|
||||||
{
|
{
|
||||||
Endpoint = endpoint;
|
Endpoint = endpoint;
|
||||||
Score = score;
|
Score = score;
|
||||||
|
|
@ -20,7 +22,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the <see cref="Http.Endpoint"/>.
|
/// Gets the <see cref="Http.Endpoint"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public RouteEndpoint Endpoint { get; }
|
public Endpoint Endpoint { get; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gets the score of the <see cref="Http.Endpoint"/> within the current
|
/// Gets the score of the <see cref="Http.Endpoint"/> within the current
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
EndpointFeature feature,
|
EndpointFeature feature,
|
||||||
CandidateSet candidateSet)
|
CandidateSet candidateSet)
|
||||||
{
|
{
|
||||||
RouteEndpoint endpoint = null;
|
Endpoint endpoint = null;
|
||||||
RouteValueDictionary values = null;
|
RouteValueDictionary values = null;
|
||||||
int? foundScore = null;
|
int? foundScore = null;
|
||||||
for (var i = 0; i < candidateSet.Count; i++)
|
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
|
// If we get here it's the result of an ambiguity - we're OK with this
|
||||||
// being a littler slower and more allocatey.
|
// 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++)
|
for (var i = 0; i < candidates.Count; i++)
|
||||||
{
|
{
|
||||||
ref var state = ref candidates[i];
|
ref var state = ref candidates[i];
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
using Microsoft.AspNetCore.Routing.Patterns;
|
using Microsoft.AspNetCore.Routing.Patterns;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
private readonly EndpointSelector _selector;
|
private readonly EndpointSelector _selector;
|
||||||
private readonly MatcherPolicy[] _policies;
|
private readonly MatcherPolicy[] _policies;
|
||||||
private readonly INodeBuilderPolicy[] _nodeBuilders;
|
private readonly INodeBuilderPolicy[] _nodeBuilders;
|
||||||
private readonly RouteEndpointComparer _comparer;
|
private readonly EndpointComparer _comparer;
|
||||||
|
|
||||||
public DfaMatcherBuilder(
|
public DfaMatcherBuilder(
|
||||||
ParameterPolicyFactory parameterPolicyFactory,
|
ParameterPolicyFactory parameterPolicyFactory,
|
||||||
|
|
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
|
|
||||||
// Taking care to use _policies, which has been sorted.
|
// Taking care to use _policies, which has been sorted.
|
||||||
_nodeBuilders = _policies.OfType<INodeBuilderPolicy>().ToArray();
|
_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)
|
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
|
// Builds an array of candidates for a node, assigns a 'score' for each
|
||||||
// endpoint.
|
// endpoint.
|
||||||
internal Candidate[] CreateCandidates(IReadOnlyList<RouteEndpoint> endpoints)
|
internal Candidate[] CreateCandidates(IReadOnlyList<Endpoint> endpoints)
|
||||||
{
|
{
|
||||||
if (endpoints.Count == 0)
|
if (endpoints.Count == 0)
|
||||||
{
|
{
|
||||||
|
|
@ -367,76 +368,80 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
}
|
}
|
||||||
|
|
||||||
// internal for tests
|
// 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 assignments = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
|
||||||
var slots = new List<KeyValuePair<string, object>>();
|
var slots = new List<KeyValuePair<string, object>>();
|
||||||
var captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
|
var captures = new List<(string parameterName, int segmentIndex, int slotIndex)>();
|
||||||
(string parameterName, int segmentIndex, int slotIndex) catchAll = default;
|
(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)>();
|
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];
|
foreach (var kvp in routeEndpoint.RoutePattern.Defaults)
|
||||||
if (segment.IsSimple)
|
|
||||||
{
|
{
|
||||||
continue;
|
assignments.Add(kvp.Key, assignments.Count);
|
||||||
|
slots.Add(kvp);
|
||||||
}
|
}
|
||||||
|
|
||||||
complexSegments.Add((segment, i));
|
for (var i = 0; i < routeEndpoint.RoutePattern.PathSegments.Count; 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++)
|
|
||||||
{
|
{
|
||||||
var reference = parameterPolicyReferences[i];
|
var segment = routeEndpoint.RoutePattern.PathSegments[i];
|
||||||
var parameterPolicy = _parameterPolicyFactory.Create(parameter, reference);
|
if (!segment.IsSimple)
|
||||||
if (parameterPolicy is IRouteConstraint routeConstraint)
|
|
||||||
{
|
{
|
||||||
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(),
|
Label = parent.Label + " " + edge.State.ToString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// TODO: https://github.com/aspnet/Routing/issues/648
|
next.Matches.AddRange(edge.Endpoints);
|
||||||
next.Matches.AddRange(edge.Endpoints.Cast<RouteEndpoint>().ToArray());
|
|
||||||
nextWork.Add(next);
|
nextWork.Add(next);
|
||||||
|
|
||||||
parent.PolicyEdges.Add(edge.State, next);
|
parent.PolicyEdges.Add(edge.State, next);
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ using System.Collections.Generic;
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
|
using Microsoft.AspNetCore.Http;
|
||||||
|
|
||||||
namespace Microsoft.AspNetCore.Routing.Matching
|
namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
{
|
{
|
||||||
|
|
@ -16,7 +17,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
public DfaNode()
|
public DfaNode()
|
||||||
{
|
{
|
||||||
Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
|
Literals = new Dictionary<string, DfaNode>(StringComparer.OrdinalIgnoreCase);
|
||||||
Matches = new List<RouteEndpoint>();
|
Matches = new List<Endpoint>();
|
||||||
PolicyEdges = new Dictionary<object, DfaNode>();
|
PolicyEdges = new Dictionary<object, DfaNode>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -29,7 +30,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
// Just for diagnostics and debugging
|
// Just for diagnostics and debugging
|
||||||
public string Label { get; set; }
|
public string Label { get; set; }
|
||||||
|
|
||||||
public List<RouteEndpoint> Matches { get; }
|
public List<Endpoint> Matches { get; }
|
||||||
|
|
||||||
public Dictionary<string, DfaNode> Literals { 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)
|
private Endpoint CreateRejectionEndpoint(IEnumerable<string> httpMethods)
|
||||||
{
|
{
|
||||||
var allow = string.Join(", ", httpMethods);
|
var allow = string.Join(", ", httpMethods);
|
||||||
return new RouteEndpoint(
|
return new Endpoint(
|
||||||
(context) =>
|
(context) =>
|
||||||
{
|
{
|
||||||
context.Response.StatusCode = 405;
|
context.Response.StatusCode = 405;
|
||||||
context.Response.Headers.Add("Allow", allow);
|
context.Response.Headers.Add("Allow", allow);
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
},
|
},
|
||||||
RoutePatternFactory.Parse("/"),
|
|
||||||
0,
|
|
||||||
EndpointMetadataCollection.Empty,
|
EndpointMetadataCollection.Empty,
|
||||||
Http405EndpointDisplayName);
|
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);
|
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]
|
[Fact]
|
||||||
public void BuildDfaTree_WithPolicies_AndBranches_BothPoliciesSkipped()
|
public void BuildDfaTree_WithPolicies_AndBranches_BothPoliciesSkipped()
|
||||||
{
|
{
|
||||||
|
|
@ -968,5 +1016,35 @@ namespace Microsoft.AspNetCore.Routing.Matching
|
||||||
.ToArray();
|
.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);
|
"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
|
private class TestMetadata1
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue