React to IEndpointSelectorPolicy changes

This commit is contained in:
Ryan Nowak 2018-10-04 21:01:06 -07:00
parent 5bddd226a3
commit 384b814349
6 changed files with 72 additions and 91 deletions

View File

@ -47,8 +47,8 @@
<MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion> <MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreRazorTagHelpersTestingSourcesPackageVersion>
<MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion> <MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreResponseCachingAbstractionsPackageVersion>
<MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreResponseCachingPackageVersion> <MicrosoftAspNetCoreResponseCachingPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreResponseCachingPackageVersion>
<MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-a-preview3-endpoint-selector-17041</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion> <MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>2.2.0-a-preview3-matcher-policy-17051</MicrosoftAspNetCoreRoutingAbstractionsPackageVersion>
<MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-a-preview3-endpoint-selector-17041</MicrosoftAspNetCoreRoutingPackageVersion> <MicrosoftAspNetCoreRoutingPackageVersion>2.2.0-a-preview3-matcher-policy-17051</MicrosoftAspNetCoreRoutingPackageVersion>
<MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerIISIntegrationPackageVersion> <MicrosoftAspNetCoreServerIISIntegrationPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerIISIntegrationPackageVersion>
<MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerKestrelPackageVersion> <MicrosoftAspNetCoreServerKestrelPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreServerKestrelPackageVersion>
<MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreSessionPackageVersion> <MicrosoftAspNetCoreSessionPackageVersion>2.2.0-preview3-35359</MicrosoftAspNetCoreSessionPackageVersion>

View File

@ -150,10 +150,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal
{ {
return null; return null;
} }
var actionConstraints = new IActionConstraint[count]; var actionConstraints = new IActionConstraint[count];
var actionConstraintIndex = 0; var actionConstraintIndex = 0;
for (int i = 0; i < items.Count; i++) for (var i = 0; i < items.Count; i++)
{ {
var actionConstraint = items[i].Constraint; var actionConstraint = items[i].Constraint;
if (actionConstraint != null) if (actionConstraint != null)
@ -168,7 +168,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
internal class InnerCache internal class InnerCache
{ {
private readonly ActionDescriptorCollection _actions; private readonly ActionDescriptorCollection _actions;
private bool? _hasActionConstraints;
public InnerCache(ActionDescriptorCollection actions) public InnerCache(ActionDescriptorCollection actions)
{ {
@ -179,61 +178,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal
new ConcurrentDictionary<ActionDescriptor, CacheEntry>(); new ConcurrentDictionary<ActionDescriptor, CacheEntry>();
public int Version => _actions.Version; public int Version => _actions.Version;
public bool HasActionConstraints
{
get
{
// This is a safe race-condition, since it always transitions from null to non-null.
// All writers will always get the same result.
if (_hasActionConstraints == null)
{
var found = false;
for (var i = 0; i < _actions.Items.Count; i++)
{
var action = _actions.Items[i];
if (action.ActionConstraints?.Count > 0 && HasSignificantActionConstraint(action))
{
// We need to check for some specific action constraint implementations.
// We've implemented consumes, and HTTP method support inside endpoint routing, so
// we don't need to run an 'action constraint phase' if those are the only constraints.
found = true;
break;
}
}
_hasActionConstraints = found;
bool HasSignificantActionConstraint(ActionDescriptor action)
{
for (var i = 0; i < action.ActionConstraints.Count; i++)
{
var actionConstraint = action.ActionConstraints[i];
if (actionConstraint.GetType() == typeof(HttpMethodActionConstraint))
{
// This one is OK, we implement this in endpoint routing.
}
else if (actionConstraint.GetType().FullName == "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsHttpMethodActionConstraint")
{
// This one is OK, we implement this in endpoint routing.
}
else if (actionConstraint.GetType() == typeof(ConsumesAttribute))
{
// This one is OK, we implement this in endpoint routing.
}
else
{
return true;
}
}
return false;
}
}
return _hasActionConstraints.Value;
}
}
} }
internal readonly struct CacheEntry internal readonly struct CacheEntry

View File

@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints;
using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Mvc.Internal;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.Logging;
namespace Microsoft.AspNetCore.Mvc.Routing namespace Microsoft.AspNetCore.Mvc.Routing
{ {
@ -24,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// We need to be able to run IActionConstraints on Endpoints that aren't associated // We need to be able to run IActionConstraints on Endpoints that aren't associated
// with an action. This is a sentinel value we use when the endpoint isn't from MVC. // with an action. This is a sentinel value we use when the endpoint isn't from MVC.
internal static readonly ActionDescriptor NonAction = new ActionDescriptor(); internal static readonly ActionDescriptor NonAction = new ActionDescriptor();
private readonly ActionConstraintCache _actionConstraintCache; private readonly ActionConstraintCache _actionConstraintCache;
public ActionConstraintMatcherPolicy(ActionConstraintCache actionConstraintCache) public ActionConstraintMatcherPolicy(ActionConstraintCache actionConstraintCache)
@ -35,27 +34,59 @@ namespace Microsoft.AspNetCore.Mvc.Routing
// Run really late. // Run really late.
public override int Order => 100000; public override int Order => 100000;
// Internal for testing public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
internal bool ShouldRunActionConstraints => _actionConstraintCache.CurrentCache.HasActionConstraints;
public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet)
{ {
// PERF: we can skip over action constraints if there aren't any app-wide. if (endpoints == null)
//
// Running action constraints (or just checking for them) in a candidate set
// is somewhat expensive compared to other routing operations. This should only
// happen if user-code adds action constraints.
if (ShouldRunActionConstraints)
{ {
ApplyActionConstraints(httpContext, candidateSet); throw new ArgumentNullException(nameof(endpoints));
} }
return Task.CompletedTask; // We can skip over action constraints when they aren't any for this set
// of endpoints. This happens once on startup so it removes this component
// from the code path in most scenarios.
for (var i = 0; i < endpoints.Count; i++)
{
var endpoint = endpoints[i];
var action = endpoint.Metadata.GetMetadata<ActionDescriptor>();
if (action?.ActionConstraints?.Count > 0 && HasSignificantActionConstraint(action))
{
// We need to check for some specific action constraint implementations.
// We've implemented consumes, and HTTP method support inside endpoint routing, so
// we don't need to run an 'action constraint phase' if those are the only constraints.
return true;
}
}
return false;
bool HasSignificantActionConstraint(ActionDescriptor a)
{
for (var i = 0; i < a.ActionConstraints.Count; i++)
{
var actionConstraint = a.ActionConstraints[i];
if (actionConstraint.GetType() == typeof(HttpMethodActionConstraint))
{
// This one is OK, we implement this in endpoint routing.
}
else if (actionConstraint.GetType().FullName == "Microsoft.AspNetCore.Mvc.Cors.Internal.CorsHttpMethodActionConstraint")
{
// This one is OK, we implement this in endpoint routing.
}
else if (actionConstraint.GetType() == typeof(ConsumesAttribute))
{
// This one is OK, we implement this in endpoint routing.
}
else
{
return true;
}
}
return false;
}
} }
private void ApplyActionConstraints( public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet)
HttpContext httpContext,
CandidateSet candidateSet)
{ {
var finalMatches = EvaluateActionConstraints(httpContext, candidateSet); var finalMatches = EvaluateActionConstraints(httpContext, candidateSet);
@ -74,6 +105,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
candidateSet.SetValidity(finalMatches[i].index, true); candidateSet.SetValidity(finalMatches[i].index, true);
} }
} }
return Task.CompletedTask;
} }
// This is almost the same as the code in ActionSelector, but we can't really share the logic // This is almost the same as the code in ActionSelector, but we can't really share the logic
@ -171,8 +204,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var endpointsWithConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); var endpointsWithConstraint = new List<(int index, ActionSelectorCandidate candidate)>();
var endpointsWithoutConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); var endpointsWithoutConstraint = new List<(int index, ActionSelectorCandidate candidate)>();
var constraintContext = new ActionConstraintContext(); var constraintContext = new ActionConstraintContext
constraintContext.Candidates = items.Select(i => i.candidate).ToArray(); {
Candidates = items.Select(i => i.candidate).ToArray()
};
// Perf: Avoid allocations // Perf: Avoid allocations
for (var i = 0; i < items.Count; i++) for (var i = 0; i < items.Count; i++)

View File

@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
public IComparer<Endpoint> Comparer { get; } = new ConsumesMetadataEndpointComparer(); public IComparer<Endpoint> Comparer { get; } = new ConsumesMetadataEndpointComparer();
public bool AppliesToNode(IReadOnlyList<Endpoint> endpoints) public bool AppliesToEndpoints(IReadOnlyList<Endpoint> endpoints)
{ {
if (endpoints == null) if (endpoints == null)
{ {
@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
return new ConsumesPolicyJumpTable(exitDestination, ordered); return new ConsumesPolicyJumpTable(exitDestination, ordered);
} }
private int GetScore(MediaType mediaType) private int GetScore(in MediaType mediaType)
{ {
// Higher score == lower priority - see comments on MediaType. // Higher score == lower priority - see comments on MediaType.
if (mediaType.MatchesAllTypes) if (mediaType.MatchesAllTypes)

View File

@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
public class ConsumesMatcherPolicyTest public class ConsumesMatcherPolicyTest
{ {
[Fact] [Fact]
public void AppliesToNode_EndpointWithoutMetadata_ReturnsFalse() public void AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse()
{ {
// Arrange // Arrange
var endpoints = new[] { CreateEndpoint("/", null), }; var endpoints = new[] { CreateEndpoint("/", null), };
@ -24,14 +24,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var policy = CreatePolicy(); var policy = CreatePolicy();
// Act // Act
var result = policy.AppliesToNode(endpoints); var result = policy.AppliesToEndpoints(endpoints);
// Assert // Assert
Assert.False(result); Assert.False(result);
} }
[Fact] [Fact]
public void AppliesToNode_EndpointWithoutContentTypes_ReturnsFalse() public void AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsFalse()
{ {
// Arrange // Arrange
var endpoints = new[] var endpoints = new[]
@ -42,14 +42,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var policy = CreatePolicy(); var policy = CreatePolicy();
// Act // Act
var result = policy.AppliesToNode(endpoints); var result = policy.AppliesToEndpoints(endpoints);
// Assert // Assert
Assert.False(result); Assert.False(result);
} }
[Fact] [Fact]
public void AppliesToNode_EndpointHasContentTypes_ReturnsTrue() public void AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue()
{ {
// Arrange // Arrange
var endpoints = new[] var endpoints = new[]
@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
var policy = CreatePolicy(); var policy = CreatePolicy();
// Act // Act
var result = policy.AppliesToNode(endpoints); var result = policy.AppliesToEndpoints(endpoints);
// Assert // Assert
Assert.True(result); Assert.True(result);

View File

@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
} }
[Fact] [Fact]
public void ShouldRunActionConstraints_IgnoresIgnorableConstraints() public void AppliesToEndpoints_IgnoresIgnorableConstraints()
{ {
// Arrange // Arrange
var actions = new ActionDescriptor[] var actions = new ActionDescriptor[]
@ -369,11 +369,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing
}, },
}, },
}; };
var endpoints = actions.Select(CreateEndpoint).ToArray();
var selector = CreateSelector(actions); var selector = CreateSelector(actions);
// Act // Act
var result = selector.ShouldRunActionConstraints; var result = selector.AppliesToEndpoints(endpoints);
// Assert // Assert
Assert.False(result); Assert.False(result);
@ -397,11 +398,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing
}, },
}, },
}; };
var endpoints = actions.Select(CreateEndpoint).ToArray();
var selector = CreateSelector(actions); var selector = CreateSelector(actions);
// Act // Act
var result = selector.ShouldRunActionConstraints; var result = selector.AppliesToEndpoints(endpoints);
// Assert // Assert
Assert.True(result); Assert.True(result);