diff --git a/src/Http/Routing/src/EndpointSelectorContext.cs b/src/Http/Routing/src/EndpointSelectorContext.cs index 848cc2736649..1aed4f22276b 100644 --- a/src/Http/Routing/src/EndpointSelectorContext.cs +++ b/src/Http/Routing/src/EndpointSelectorContext.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -24,7 +24,7 @@ public sealed class EndpointSelectorContext : IEndpointFeature, IRouteValuesFeat /// public RouteValueDictionary RouteValues { - get => _routeValues; + get => _routeValues ?? (_routeValues = new RouteValueDictionary()); set { _routeValues = value; @@ -67,4 +67,4 @@ RouteData IRoutingFeature.RouteData set => throw new NotSupportedException(); } } -} \ No newline at end of file +} diff --git a/src/Http/Routing/src/Matching/DfaMatcher.cs b/src/Http/Routing/src/Matching/DfaMatcher.cs index b1dfcb36dc33..036351760a0e 100644 --- a/src/Http/Routing/src/Matching/DfaMatcher.cs +++ b/src/Http/Routing/src/Matching/DfaMatcher.cs @@ -1,4 +1,4 @@ -// Copyright (c) .NET Foundation. All rights reserved. +// 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; @@ -16,6 +16,7 @@ internal sealed class DfaMatcher : Matcher private readonly EndpointSelector _selector; private readonly DfaState[] _states; private readonly int _maxSegmentCount; + private readonly bool _isDefaultEndpointSelector; public DfaMatcher(ILogger logger, EndpointSelector selector, DfaState[] states, int maxSegmentCount) { @@ -23,6 +24,7 @@ public DfaMatcher(ILogger logger, EndpointSelector selector, DfaStat _selector = selector; _states = states; _maxSegmentCount = maxSegmentCount; + _isDefaultEndpointSelector = selector is DefaultEndpointSelector; } public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelectorContext context) @@ -53,7 +55,8 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector // FindCandidateSet will process the DFA and return a candidate set. This does // some preliminary matching of the URL (mostly the literal segments). var (candidates, policies) = FindCandidateSet(httpContext, path, segments); - if (candidates.Length == 0) + var candidateCount = candidates.Length; + if (candidateCount == 0) { if (log) { @@ -68,6 +71,23 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector Logger.CandidatesFound(_logger, path, candidates); } + var policyCount = policies.Length; + + // This is a fast path for single candidate, 0 policies and default selector + if (candidateCount == 1 && policyCount == 0 && _isDefaultEndpointSelector) + { + ref var candidate = ref candidates[0]; + + // Just strict path matching + if (candidate.Flags == Candidate.CandidateFlags.None) + { + context.Endpoint = candidate.Endpoint; + + // We're done + return Task.CompletedTask; + } + } + // At this point we have a candidate set, defined as a list of endpoints in // priority order. // @@ -83,7 +103,7 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector // `candidateSet` is the mutable state that we pass to the EndpointSelector. var candidateSet = new CandidateSet(candidates); - for (var i = 0; i < candidates.Length; i++) + for (var i = 0; i < candidateCount; i++) { // PERF: using ref here to avoid copying around big structs. // @@ -165,7 +185,7 @@ public sealed override Task MatchAsync(HttpContext httpContext, EndpointSelector } } - if (policies.Length == 0) + if (policyCount == 0) { // Perf: avoid a state machine if there are no polices return _selector.SelectAsync(httpContext, context, candidateSet);