Improvements for IEndpointSelectorPolicy

These changes are based on our discussion earlier this week. Adding
async, and making it possible to short circuit, which should be better
aligned with the requirments of versioning.
This commit is contained in:
Ryan Nowak 2018-08-22 18:07:17 -07:00
parent e90e670ac8
commit a0aa61fd10
3 changed files with 73 additions and 7 deletions

View File

@ -24,16 +24,30 @@ namespace Microsoft.AspNetCore.Routing.Matching
_selectorPolicies = matcherPolicies.OrderBy(p => p.Order).OfType<IEndpointSelectorPolicy>().ToArray();
}
public override Task SelectAsync(
public override async Task SelectAsync(
HttpContext httpContext,
EndpointFeature feature,
CandidateSet candidateSet)
{
var selectorPolicies = _selectorPolicies;
for (var i = 0; i < _selectorPolicies.Length; i++)
{
_selectorPolicies[i].Apply(httpContext, candidateSet);
await selectorPolicies[i].ApplyAsync(httpContext, feature, candidateSet);
if (feature.Endpoint != null)
{
// This is a short circuit, the selector chose an endpoint.
return;
}
}
ProcessFinalCandidates(httpContext, feature, candidateSet);
}
private static void ProcessFinalCandidates(
HttpContext httpContext,
EndpointFeature feature,
CandidateSet candidateSet)
{
RouteEndpoint endpoint = null;
RouteValueDictionary values = null;
int? foundScore = null;
@ -76,8 +90,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
feature.Endpoint = endpoint;
feature.RouteValues = values;
}
return Task.CompletedTask;
}
private static void ReportAmbiguity(CandidateSet candidates)

View File

@ -2,6 +2,7 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using Microsoft.AspNetCore.Http;
using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Routing.Matching
{
@ -19,12 +20,21 @@ namespace Microsoft.AspNetCore.Routing.Matching
/// <param name="httpContext">
/// The <see cref="HttpContext"/> associated with the current request.
/// </param>
/// <param name="feature">
/// The <see cref="EndpointFeature"/> associated with the current request.
/// </param>
/// <param name="candidates">The <see cref="CandidateSet"/>.</param>
/// <remarks>
/// <para>
/// Implementations of <see cref="IEndpointSelectorPolicy"/> should implement this method
/// and filter the set of candidates in the <paramref name="candidates"/> by setting
/// <see cref="CandidateState.IsValidCandidate"/> to <c>false</c> where desired.
/// </para>
/// <para>
/// To signal an error condition, set <see cref="EndpointFeature.Endpoint"/> to an
/// <see cref="Endpoint"/> value that will produce the desired error when executed.
/// </para>
/// </remarks>
void Apply(HttpContext httpContext, CandidateSet candidates);
Task ApplyAsync(HttpContext httpContext, EndpointFeature feature, CandidateSet candidates);
}
}

View File

@ -1,6 +1,7 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
@ -184,10 +185,11 @@ test: /test3", ex.Message);
var policy = new Mock<MatcherPolicy>();
policy
.As<IEndpointSelectorPolicy>()
.Setup(p => p.Apply(It.IsAny<HttpContext>(), It.IsAny<CandidateSet>()))
.Callback<HttpContext, CandidateSet>((c, cs) =>
.Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointFeature>(), It.IsAny<CandidateSet>()))
.Returns<HttpContext, EndpointFeature, CandidateSet>((c, f, cs) =>
{
cs[1].IsValidCandidate = false;
return Task.CompletedTask;
});
candidateSet[0].IsValidCandidate = false;
@ -204,6 +206,48 @@ test: /test3", ex.Message);
Assert.Same(endpoints[2], feature.Endpoint);
}
[Fact]
public async Task SelectAsync_RunsEndpointSelectorPolicies_CanShortCircuit()
{
// Arrange
var endpoints = new RouteEndpoint[] { CreateEndpoint("/test1"), CreateEndpoint("/test2"), CreateEndpoint("/test3"), };
var scores = new int[] { 0, 0, 1 };
var candidateSet = CreateCandidateSet(endpoints, scores);
var policy1 = new Mock<MatcherPolicy>();
policy1
.As<IEndpointSelectorPolicy>()
.Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointFeature>(), It.IsAny<CandidateSet>()))
.Returns<HttpContext, EndpointFeature, CandidateSet>((c, f, cs) =>
{
f.Endpoint = cs[0].Endpoint;
return Task.CompletedTask;
});
// This should never run, it's after policy1 which short circuits
var policy2 = new Mock<MatcherPolicy>();
policy2
.SetupGet(p => p.Order)
.Returns(1000);
policy2
.As<IEndpointSelectorPolicy>()
.Setup(p => p.ApplyAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointFeature>(), It.IsAny<CandidateSet>()))
.Throws(new InvalidOperationException());
candidateSet[0].IsValidCandidate = false;
candidateSet[1].IsValidCandidate = true;
candidateSet[2].IsValidCandidate = true;
var (httpContext, feature) = CreateContext();
var selector = CreateSelector(policy1.Object, policy2.Object);
// Act
await selector.SelectAsync(httpContext, feature, candidateSet);
// Assert
Assert.Same(endpoints[0], feature.Endpoint);
}
private static (HttpContext httpContext, EndpointFeature feature) CreateContext()
{
var feature = new EndpointFeature();