This commit is contained in:
Ryan Nowak 2018-09-29 18:42:38 -07:00
parent a2f404f4d5
commit b7939328b8
5 changed files with 62 additions and 27 deletions

View File

@ -80,7 +80,7 @@ namespace Microsoft.AspNetCore.Routing
private static void SetFeatures(HttpContext httpContext, EndpointSelectorContext context)
{
// For back-compat EndpointRouteValuesFeature implements IEndpointFeature,
// For back-compat EndpointSelectorContext implements IEndpointFeature,
// IRouteValuesFeature and IRoutingFeature
httpContext.Features.Set<IRoutingFeature>(context);
httpContext.Features.Set<IRouteValuesFeature>(context);

View File

@ -18,8 +18,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
{
private const int BitVectorSize = 32;
// Cannot be readonly because we need to modify it in place.
private BitVector32 _validity;
private BitArray _largeCapactityValidity;
private readonly BitArray _largeCapactityValidity;
// We inline storage for 4 candidates here to avoid allocations in common
// cases. There's no real reason why 4 is important, it just seemed like
@ -33,7 +34,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
/// <summary>
/// <para>
/// Initializes a new instances of the candidate set structure with the provided data.
/// Initializes a new instances of the <see cref="CandidateSet"/> class with the provided <paramref name="endpoints"/>,
/// <paramref name="values"/>, and <paramref name="scores"/>.
/// </para>
/// <para>
/// The constructor is provided to enable unit tests of implementations of <see cref="EndpointSelector"/>
@ -45,6 +47,26 @@ namespace Microsoft.AspNetCore.Routing.Matching
/// <param name="scores">The list of endpoint scores. <see cref="CandidateState.Score"/>.</param>
public CandidateSet(Endpoint[] endpoints, RouteValueDictionary[] values, int[] scores)
{
if (endpoints == null)
{
throw new ArgumentNullException(nameof(endpoints));
}
if (values == null)
{
throw new ArgumentNullException(nameof(values));
}
if (scores == null)
{
throw new ArgumentNullException(nameof(scores));
}
if (endpoints.Length != values.Length || endpoints.Length != scores.Length)
{
throw new ArgumentException($"The provided {nameof(endpoints)}, {nameof(values)}, and {nameof(scores)} must have the same length.");
}
Count = endpoints.Length;
switch (endpoints.Length)
@ -53,37 +75,37 @@ namespace Microsoft.AspNetCore.Routing.Matching
return;
case 1:
_state0 = new CandidateState(endpoints[0], score: scores[0]);
_state0 = new CandidateState(endpoints[0], values[0], scores[0]);
break;
case 2:
_state0 = new CandidateState(endpoints[0], score: scores[0]);
_state1 = new CandidateState(endpoints[1], score: scores[1]);
_state0 = new CandidateState(endpoints[0], values[0], scores[0]);
_state1 = new CandidateState(endpoints[1], values[1], scores[1]);
break;
case 3:
_state0 = new CandidateState(endpoints[0], score: scores[0]);
_state1 = new CandidateState(endpoints[1], score: scores[1]);
_state2 = new CandidateState(endpoints[2], score: scores[2]);
_state0 = new CandidateState(endpoints[0], values[0], scores[0]);
_state1 = new CandidateState(endpoints[1], values[1], scores[1]);
_state2 = new CandidateState(endpoints[2], values[2], scores[2]);
break;
case 4:
_state0 = new CandidateState(endpoints[0], score: scores[0]);
_state1 = new CandidateState(endpoints[1], score: scores[1]);
_state2 = new CandidateState(endpoints[2], score: scores[2]);
_state3 = new CandidateState(endpoints[3], score: scores[3]);
_state0 = new CandidateState(endpoints[0], values[0], scores[0]);
_state1 = new CandidateState(endpoints[1], values[1], scores[1]);
_state2 = new CandidateState(endpoints[2], values[2], scores[2]);
_state3 = new CandidateState(endpoints[3], values[3], scores[3]);
break;
default:
_state0 = new CandidateState(endpoints[0], score: scores[0]);
_state1 = new CandidateState(endpoints[1], score: scores[1]);
_state2 = new CandidateState(endpoints[2], score: scores[2]);
_state3 = new CandidateState(endpoints[3], score: scores[3]);
_state0 = new CandidateState(endpoints[0], values[0], scores[0]);
_state1 = new CandidateState(endpoints[1], values[1], scores[1]);
_state2 = new CandidateState(endpoints[2], values[2], scores[2]);
_state3 = new CandidateState(endpoints[3], values[3], scores[3]);
_additionalCandidates = new CandidateState[endpoints.Length - 4];
for (var i = 4; i < endpoints.Length; i++)
{
_additionalCandidates[i - 4] = new CandidateState(endpoints[i], score: scores[i]);
_additionalCandidates[i - 4] = new CandidateState(endpoints[i], values[i], scores[i]);
}
break;
}
@ -161,7 +183,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
/// Gets the count of candidates in the set.
/// </summary>
public int Count { get; }
/// <summary>
/// Gets the <see cref="CandidateState"/> associated with the candidate <see cref="Endpoint"/>
/// at <paramref name="index"/>.
@ -206,10 +228,14 @@ namespace Microsoft.AspNetCore.Routing.Matching
}
/// <summary>
/// Gets or sets a value which indicates where the <see cref="Http.Endpoint"/> is considered
/// a valid candiate for the current request. Set this value to <c>false</c> to exclude an
/// <see cref="Http.Endpoint"/> from consideration.
/// Gets a value which indicates where the <see cref="Http.Endpoint"/> is considered
/// a valid candiate for the current request.
/// </summary>
/// <param name="index">The candidate index.</param>
/// <returns>
/// <c>true</c> if the candidate at position <paramref name="index"/> is considered value
/// for the current request, otherwise <c>false</c>.
/// </returns>
public bool IsValidCandidate(int index)
{
// Friendliness for inlining

View File

@ -6,7 +6,7 @@ using Microsoft.AspNetCore.Http;
namespace Microsoft.AspNetCore.Routing.Matching
{
/// <summary>
/// The mutable state associated with a candidate in a <see cref="CandidateSet"/>.
/// The state associated with a candidate in a <see cref="CandidateSet"/>.
/// </summary>
public struct CandidateState
{
@ -47,7 +47,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
public int Score { get; }
/// <summary>
/// Gets or sets the <see cref="RouteValueDictionary"/> associated with the
/// Gets <see cref="RouteValueDictionary"/> associated with the
/// <see cref="Http.Endpoint"/> and the current request.
/// </summary>
public RouteValueDictionary Values { get; internal set; }

View File

@ -116,7 +116,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
var isValid = candidateSet.IsValidCandidate(i);
if (isValid && foundScore == null)
{
// This is the first match we've seen - speculatively assign it.
endpoint = state.Endpoint;
values = state.Values;

View File

@ -76,8 +76,17 @@ namespace Microsoft.AspNetCore.Routing.Matching
endpoints[i] = CreateEndpoint($"/{i}");
}
var values = new RouteValueDictionary[count];
for (var i = 0; i < endpoints.Length; i++)
{
values[i] = new RouteValueDictionary()
{
{ "i", i }
};
}
// Act
var candidateSet = new CandidateSet(endpoints, new RouteValueDictionary[count], Enumerable.Range(0, count).ToArray());
var candidateSet = new CandidateSet(endpoints, values, Enumerable.Range(0, count).ToArray());
// Assert
for (var i = 0; i < candidateSet.Count; i++)
@ -86,7 +95,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
Assert.True(candidateSet.IsValidCandidate(i));
Assert.Same(endpoints[i], state.Endpoint);
Assert.Equal(i, state.Score);
Assert.Null(state.Values);
Assert.NotNull(state.Values);
Assert.Equal(i, state.Values["i"]);
candidateSet.SetValidity(i, false);
Assert.False(candidateSet.IsValidCandidate(i));