diff --git a/src/Microsoft.AspNetCore.Routing/EndpointConstraints/EndpointConstraintCache.cs b/src/Microsoft.AspNetCore.Routing/EndpointConstraints/EndpointConstraintCache.cs index 0728dcc9b6..1a5c4a350c 100644 --- a/src/Microsoft.AspNetCore.Routing/EndpointConstraints/EndpointConstraintCache.cs +++ b/src/Microsoft.AspNetCore.Routing/EndpointConstraints/EndpointConstraintCache.cs @@ -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; using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; @@ -50,38 +51,48 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints return GetEndpointConstraintsFromEntry(entry, httpContext, endpoint); } - if (endpoint.Metadata == null || endpoint.Metadata.Count == 0) + List items = null; + + if (endpoint.Metadata != null && endpoint.Metadata.Count > 0) { - return null; + items = endpoint.Metadata + .OfType() + .Select(m => new EndpointConstraintItem(m)) + .ToList(); } - var items = endpoint.Metadata - .OfType() - .Select(m => new EndpointConstraintItem(m)) - .ToList(); + IReadOnlyList endpointConstraints = null; - ExecuteProviders(httpContext, endpoint, items); - - var endpointConstraints = ExtractEndpointConstraints(items); - - var allEndpointConstraintsCached = true; - for (var i = 0; i < items.Count; i++) + if (items != null && items.Count > 0) { - var item = items[i]; - if (!item.IsReusable) + ExecuteProviders(httpContext, endpoint, items); + + endpointConstraints = ExtractEndpointConstraints(items); + + var allEndpointConstraintsCached = true; + for (var i = 0; i < items.Count; i++) { - item.Constraint = null; - allEndpointConstraintsCached = false; + var item = items[i]; + if (!item.IsReusable) + { + item.Constraint = null; + allEndpointConstraintsCached = false; + } } - } - if (allEndpointConstraintsCached) - { - entry = new CacheEntry(endpointConstraints); + if (allEndpointConstraintsCached) + { + entry = new CacheEntry(endpointConstraints); + } + else + { + entry = new CacheEntry(items); + } } else { - entry = new CacheEntry(items); + // No constraints + entry = new CacheEntry(); } cache.Entries.TryAdd(endpoint, entry); @@ -90,13 +101,17 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints private IReadOnlyList GetEndpointConstraintsFromEntry(CacheEntry entry, HttpContext httpContext, Endpoint endpoint) { - Debug.Assert(entry.EndpointConstraints != null || entry.Items != null); - if (entry.EndpointConstraints != null) { return entry.EndpointConstraints; } + if (entry.Items == null) + { + // Endpoint has no constraints + return null; + } + var items = new List(entry.Items.Count); for (var i = 0; i < entry.Items.Count; i++) { diff --git a/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs b/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs index 5fea074c4c..cc2d881df1 100644 --- a/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs +++ b/test/Microsoft.AspNetCore.Routing.Tests/EndpointConstraints/EndpointSelectorTests.cs @@ -210,6 +210,49 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints Assert.Same(action, actionWithConstraints); } + [Fact] + public void SelectBestCandidate_MultipleCallsNoConstraint_ReturnsEndpoint() + { + // Arrange + var noConstraint = new TestEndpoint(EndpointMetadataCollection.Empty, "noConstraint"); + + var actions = new Endpoint[] { noConstraint }; + + var selector = CreateSelector(actions); + var context = CreateHttpContext("POST"); + + // Act + var action1 = selector.SelectBestCandidate(context, actions); + var action2 = selector.SelectBestCandidate(context, actions); + + // Assert + Assert.Same(action1, noConstraint); + Assert.Same(action2, noConstraint); + } + + [Fact] + public void SelectBestCandidate_MultipleCallsNonConstraintMetadata_ReturnsEndpoint() + { + // Arrange + var noConstraint = new TestEndpoint(new EndpointMetadataCollection(new[] + { + new object(), + }), "noConstraint"); + + var actions = new Endpoint[] { noConstraint }; + + var selector = CreateSelector(actions); + var context = CreateHttpContext("POST"); + + // Act + var action1 = selector.SelectBestCandidate(context, actions); + var action2 = selector.SelectBestCandidate(context, actions); + + // Assert + Assert.Same(action1, noConstraint); + Assert.Same(action2, noConstraint); + } + [Fact] public void SelectBestCandidate_EndpointConstraintFactory_ReturnsNull() { @@ -225,10 +268,12 @@ namespace Microsoft.AspNetCore.Routing.EndpointConstraints var context = CreateHttpContext("POST"); // Act - var action = selector.SelectBestCandidate(context, actions); + var action1 = selector.SelectBestCandidate(context, actions); + var action2 = selector.SelectBestCandidate(context, actions); // Assert - Assert.Same(action, nullConstraint); + Assert.Same(action1, nullConstraint); + Assert.Same(action2, nullConstraint); } // There's a custom constraint provider registered that only understands BooleanConstraintMarker