From 384b814349ff4bcfce36b4e02ad29874e5462eca Mon Sep 17 00:00:00 2001 From: Ryan Nowak Date: Thu, 4 Oct 2018 21:01:06 -0700 Subject: [PATCH] React to IEndpointSelectorPolicy changes --- build/dependencies.props | 4 +- .../Internal/ActionConstraintCache.cs | 60 +-------------- .../Routing/ActionConstraintMatcherPolicy.cs | 75 ++++++++++++++----- .../Routing/ConsumesMatcherPolicy.cs | 4 +- .../Routing/ConsumesMatcherPolicyTest.cs | 12 +-- .../ActionConstraintMatcherPolicyTest.cs | 8 +- 6 files changed, 72 insertions(+), 91 deletions(-) diff --git a/build/dependencies.props b/build/dependencies.props index c8c3ed64f1..aca99b4e4b 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -47,8 +47,8 @@ 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 - 2.2.0-a-preview3-endpoint-selector-17041 - 2.2.0-a-preview3-endpoint-selector-17041 + 2.2.0-a-preview3-matcher-policy-17051 + 2.2.0-a-preview3-matcher-policy-17051 2.2.0-preview3-35359 2.2.0-preview3-35359 2.2.0-preview3-35359 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs index e8bf938aef..384d48bffa 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Internal/ActionConstraintCache.cs @@ -150,10 +150,10 @@ namespace Microsoft.AspNetCore.Mvc.Internal { return null; } - + var actionConstraints = new IActionConstraint[count]; var actionConstraintIndex = 0; - for (int i = 0; i < items.Count; i++) + for (var i = 0; i < items.Count; i++) { var actionConstraint = items[i].Constraint; if (actionConstraint != null) @@ -168,7 +168,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal internal class InnerCache { private readonly ActionDescriptorCollection _actions; - private bool? _hasActionConstraints; public InnerCache(ActionDescriptorCollection actions) { @@ -179,61 +178,6 @@ namespace Microsoft.AspNetCore.Mvc.Internal new ConcurrentDictionary(); 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 diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs index 488b15646b..d31e06daf4 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ActionConstraintMatcherPolicy.cs @@ -11,7 +11,6 @@ using Microsoft.AspNetCore.Mvc.ActionConstraints; using Microsoft.AspNetCore.Mvc.Internal; using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing.Matching; -using Microsoft.Extensions.Logging; 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 // with an action. This is a sentinel value we use when the endpoint isn't from MVC. internal static readonly ActionDescriptor NonAction = new ActionDescriptor(); - + private readonly ActionConstraintCache _actionConstraintCache; public ActionConstraintMatcherPolicy(ActionConstraintCache actionConstraintCache) @@ -35,27 +34,59 @@ namespace Microsoft.AspNetCore.Mvc.Routing // Run really late. public override int Order => 100000; - // Internal for testing - internal bool ShouldRunActionConstraints => _actionConstraintCache.CurrentCache.HasActionConstraints; - - public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet) + public bool AppliesToEndpoints(IReadOnlyList endpoints) { - // PERF: we can skip over action constraints if there aren't any app-wide. - // - // 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) + if (endpoints == null) { - 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(); + 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( - HttpContext httpContext, - CandidateSet candidateSet) + + public Task ApplyAsync(HttpContext httpContext, EndpointSelectorContext context, CandidateSet candidateSet) { var finalMatches = EvaluateActionConstraints(httpContext, candidateSet); @@ -74,6 +105,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing 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 @@ -171,8 +204,10 @@ namespace Microsoft.AspNetCore.Mvc.Routing var endpointsWithConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); var endpointsWithoutConstraint = new List<(int index, ActionSelectorCandidate candidate)>(); - var constraintContext = new ActionConstraintContext(); - constraintContext.Candidates = items.Select(i => i.candidate).ToArray(); + var constraintContext = new ActionConstraintContext + { + Candidates = items.Select(i => i.candidate).ToArray() + }; // Perf: Avoid allocations for (var i = 0; i < items.Count; i++) diff --git a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs index 65dd1d117b..c0f0dd35bd 100644 --- a/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs +++ b/src/Microsoft.AspNetCore.Mvc.Core/Routing/ConsumesMatcherPolicy.cs @@ -23,7 +23,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing public IComparer Comparer { get; } = new ConsumesMetadataEndpointComparer(); - public bool AppliesToNode(IReadOnlyList endpoints) + public bool AppliesToEndpoints(IReadOnlyList endpoints) { if (endpoints == null) { @@ -173,7 +173,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing return new ConsumesPolicyJumpTable(exitDestination, ordered); } - private int GetScore(MediaType mediaType) + private int GetScore(in MediaType mediaType) { // Higher score == lower priority - see comments on MediaType. if (mediaType.MatchesAllTypes) diff --git a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs index cad3edc0b9..b0a59ce678 100644 --- a/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Core.Test/Routing/ConsumesMatcherPolicyTest.cs @@ -16,7 +16,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing public class ConsumesMatcherPolicyTest { [Fact] - public void AppliesToNode_EndpointWithoutMetadata_ReturnsFalse() + public void AppliesToEndpoints_EndpointWithoutMetadata_ReturnsFalse() { // Arrange var endpoints = new[] { CreateEndpoint("/", null), }; @@ -24,14 +24,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing var policy = CreatePolicy(); // Act - var result = policy.AppliesToNode(endpoints); + var result = policy.AppliesToEndpoints(endpoints); // Assert Assert.False(result); } [Fact] - public void AppliesToNode_EndpointWithoutContentTypes_ReturnsFalse() + public void AppliesToEndpoints_EndpointWithoutContentTypes_ReturnsFalse() { // Arrange var endpoints = new[] @@ -42,14 +42,14 @@ namespace Microsoft.AspNetCore.Mvc.Routing var policy = CreatePolicy(); // Act - var result = policy.AppliesToNode(endpoints); + var result = policy.AppliesToEndpoints(endpoints); // Assert Assert.False(result); } [Fact] - public void AppliesToNode_EndpointHasContentTypes_ReturnsTrue() + public void AppliesToEndpoints_EndpointHasContentTypes_ReturnsTrue() { // Arrange var endpoints = new[] @@ -61,7 +61,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing var policy = CreatePolicy(); // Act - var result = policy.AppliesToNode(endpoints); + var result = policy.AppliesToEndpoints(endpoints); // Assert Assert.True(result); diff --git a/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs index 5210180832..b21072b853 100644 --- a/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs +++ b/test/Microsoft.AspNetCore.Mvc.Test/Routing/ActionConstraintMatcherPolicyTest.cs @@ -338,7 +338,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing } [Fact] - public void ShouldRunActionConstraints_IgnoresIgnorableConstraints() + public void AppliesToEndpoints_IgnoresIgnorableConstraints() { // Arrange var actions = new ActionDescriptor[] @@ -369,11 +369,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing }, }, }; + var endpoints = actions.Select(CreateEndpoint).ToArray(); var selector = CreateSelector(actions); // Act - var result = selector.ShouldRunActionConstraints; + var result = selector.AppliesToEndpoints(endpoints); // Assert Assert.False(result); @@ -397,11 +398,12 @@ namespace Microsoft.AspNetCore.Mvc.Routing }, }, }; + var endpoints = actions.Select(CreateEndpoint).ToArray(); var selector = CreateSelector(actions); // Act - var result = selector.ShouldRunActionConstraints; + var result = selector.AppliesToEndpoints(endpoints); // Assert Assert.True(result);