diff --git a/src/Http/Routing/perf/EndpointRoutingBenchmarkBase.cs b/src/Http/Routing/perf/EndpointRoutingBenchmarkBase.cs index 3c6fabf7af..0af2021a72 100644 --- a/src/Http/Routing/perf/EndpointRoutingBenchmarkBase.cs +++ b/src/Http/Routing/perf/EndpointRoutingBenchmarkBase.cs @@ -131,9 +131,10 @@ namespace Microsoft.AspNetCore.Routing protected (HttpContext httpContext, RouteValueDictionary ambientValues) CreateCurrentRequestContext( object ambientValues = null) { - + var feature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary(ambientValues) }; var context = new DefaultHttpContext(); - var feature = new EndpointSelectorContext(context) { RouteValues = new RouteValueDictionary(ambientValues) }; + context.Features.Set(feature); + context.Features.Set(feature); return (context, feature.RouteValues); } diff --git a/src/Http/Routing/perf/Matching/MatcherSingleEntryBenchmark.cs b/src/Http/Routing/perf/Matching/MatcherSingleEntryBenchmark.cs index a9b1b1c067..412ffdaef3 100644 --- a/src/Http/Routing/perf/Matching/MatcherSingleEntryBenchmark.cs +++ b/src/Http/Routing/perf/Matching/MatcherSingleEntryBenchmark.cs @@ -18,6 +18,8 @@ namespace Microsoft.AspNetCore.Routing.Matching private Matcher _route; private Matcher _tree; + private EndpointSelectorContext _feature; + [GlobalSetup] public void Setup() { @@ -33,6 +35,8 @@ namespace Microsoft.AspNetCore.Routing.Matching _dfa = SetupMatcher(CreateDfaMatcherBuilder()); _route = SetupMatcher(new RouteMatcherBuilder()); _tree = SetupMatcher(new TreeRouterMatcherBuilder()); + + _feature = new EndpointSelectorContext(); } private Matcher SetupMatcher(MatcherBuilder builder) @@ -44,8 +48,8 @@ namespace Microsoft.AspNetCore.Routing.Matching [Benchmark(Baseline = true)] public async Task Baseline() { + var feature = _feature; var httpContext = Requests[0]; - var feature = new EndpointSelectorContext(httpContext); await _baseline.MatchAsync(httpContext, feature); Validate(httpContext, Endpoints[0], feature.Endpoint); @@ -54,8 +58,8 @@ namespace Microsoft.AspNetCore.Routing.Matching [Benchmark] public async Task Dfa() { + var feature = _feature; var httpContext = Requests[0]; - var feature = new EndpointSelectorContext(httpContext); await _dfa.MatchAsync(httpContext, feature); Validate(httpContext, Endpoints[0], feature.Endpoint); @@ -64,11 +68,12 @@ namespace Microsoft.AspNetCore.Routing.Matching [Benchmark] public async Task LegacyTreeRouter() { - var httpContext = Requests[0]; - var feature = new EndpointSelectorContext(httpContext); + var feature = _feature; - //// This is required to make the legacy router implementation work with global routing. - //httpContext.Features.Set(feature); + var httpContext = Requests[0]; + + // This is required to make the legacy router implementation work with global routing. + httpContext.Features.Set(feature); await _tree.MatchAsync(httpContext, feature); Validate(httpContext, Endpoints[0], feature.Endpoint); @@ -77,14 +82,14 @@ namespace Microsoft.AspNetCore.Routing.Matching [Benchmark] public async Task LegacyRouter() { + var feature = _feature; var httpContext = Requests[0]; - var feature = new EndpointSelectorContext(httpContext); // This is required to make the legacy router implementation work with global routing. - //httpContext.Features.Set(feature); + httpContext.Features.Set(feature); await _route.MatchAsync(httpContext, feature); Validate(httpContext, Endpoints[0], feature.Endpoint); } } -} +} \ No newline at end of file diff --git a/src/Http/Routing/perf/Matching/TrivialMatcher.cs b/src/Http/Routing/perf/Matching/TrivialMatcher.cs index 2c4a5fd173..3e00ce1999 100644 --- a/src/Http/Routing/perf/Matching/TrivialMatcher.cs +++ b/src/Http/Routing/perf/Matching/TrivialMatcher.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; @@ -30,6 +30,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + var path = httpContext.Request.Path.Value; if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase)) { diff --git a/src/Http/Routing/src/EndpointRoutingMiddleware.cs b/src/Http/Routing/src/EndpointRoutingMiddleware.cs index 043dbe87bc..02b089dbca 100644 --- a/src/Http/Routing/src/EndpointRoutingMiddleware.cs +++ b/src/Http/Routing/src/EndpointRoutingMiddleware.cs @@ -56,7 +56,7 @@ namespace Microsoft.AspNetCore.Routing public Task Invoke(HttpContext httpContext) { - var feature = new EndpointSelectorContext(httpContext); + var feature = new EndpointSelectorContext(); // There's an inherent race condition between waiting for init and accessing the matcher // this is OK because once `_matcher` is initialized, it will not be set to null again. @@ -97,6 +97,8 @@ namespace Microsoft.AspNetCore.Routing { // Set the endpoint feature only on success. This means we won't overwrite any // existing state for related features unless we did something. + SetFeatures(httpContext, feature); + Log.MatchSuccess(_logger, feature); } else @@ -107,6 +109,15 @@ namespace Microsoft.AspNetCore.Routing return _next(httpContext); } + private static void SetFeatures(HttpContext httpContext, EndpointSelectorContext context) + { + // For back-compat EndpointSelectorContext implements IEndpointFeature, + // IRouteValuesFeature and IRoutingFeature + httpContext.Features.Set(context); + httpContext.Features.Set(context); + httpContext.Features.Set(context); + } + // Initialization is async to avoid blocking threads while reflection and things // of that nature take place. // diff --git a/src/Http/Routing/src/EndpointSelectorContext.cs b/src/Http/Routing/src/EndpointSelectorContext.cs index ecd63dffaf..1aed4f2227 100644 --- a/src/Http/Routing/src/EndpointSelectorContext.cs +++ b/src/Http/Routing/src/EndpointSelectorContext.cs @@ -3,37 +3,20 @@ using System; using Microsoft.AspNetCore.Http; -using Microsoft.AspNetCore.Http.Endpoints; +using Microsoft.AspNetCore.Http.Features; namespace Microsoft.AspNetCore.Routing { - public struct EndpointSelectorContext + public sealed class EndpointSelectorContext : IEndpointFeature, IRouteValuesFeature, IRoutingFeature { - private HttpContext _httpContext; - - public EndpointSelectorContext(HttpContext httpContext) - { - _httpContext = httpContext; - } + private RouteData _routeData; + private RouteValueDictionary _routeValues; /// /// Gets or sets the selected for the current /// request. /// - public Endpoint Endpoint - { - get - { - return _httpContext.GetEndpoint(); - } - set - { - if (value != null) - { - _httpContext.SetEndpoint(value); - } - } - } + public Endpoint Endpoint { get; set; } /// /// Gets or sets the associated with the currrent @@ -41,14 +24,47 @@ namespace Microsoft.AspNetCore.Routing /// public RouteValueDictionary RouteValues { - get - { - return _httpContext.Request.RouteValues; - } + get => _routeValues ?? (_routeValues = new RouteValueDictionary()); set { - _httpContext.Request.RouteValues = value; + _routeValues = value; + + // RouteData will be created next get with new Values + _routeData = null; } } + + /// + /// Gets or sets the for the current request. + /// + /// + /// The setter is not implemented. Use to set the route values. + /// + RouteData IRoutingFeature.RouteData + { + get + { + if (_routeData == null) + { + _routeData = _routeValues == null ? new RouteData() : new RouteData(_routeValues); + + // Note: DataTokens won't update if someone else overwrites the Endpoint + // after route values has been set. This seems find since endpoints are a new + // feature and DataTokens are for back-compat. + var dataTokensMetadata = Endpoint?.Metadata.GetMetadata(); + if (dataTokensMetadata != null) + { + var dataTokens = _routeData.DataTokens; + foreach (var kvp in dataTokensMetadata.DataTokens) + { + _routeData.DataTokens.Add(kvp.Key, kvp.Value); + } + } + } + + return _routeData; + } + set => throw new NotSupportedException(); + } } } diff --git a/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs b/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs index 33b6bc0324..113e0a4f01 100644 --- a/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs +++ b/src/Http/Routing/src/Matching/DefaultEndpointSelector.cs @@ -21,6 +21,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (candidateSet == null) { throw new ArgumentNullException(nameof(candidateSet)); diff --git a/src/Http/Routing/src/Matching/DfaMatcher.cs b/src/Http/Routing/src/Matching/DfaMatcher.cs index 7312459dde..036351760a 100644 --- a/src/Http/Routing/src/Matching/DfaMatcher.cs +++ b/src/Http/Routing/src/Matching/DfaMatcher.cs @@ -34,6 +34,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + // All of the logging we do here is at level debug, so we can get away with doing a single check. var log = _logger.IsEnabled(LogLevel.Debug); diff --git a/src/Http/Routing/src/Matching/HostMatcherPolicy.cs b/src/Http/Routing/src/Matching/HostMatcherPolicy.cs index 4f52eb3932..f99afdcfbd 100644 --- a/src/Http/Routing/src/Matching/HostMatcherPolicy.cs +++ b/src/Http/Routing/src/Matching/HostMatcherPolicy.cs @@ -77,6 +77,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (candidates == null) { throw new ArgumentNullException(nameof(candidates)); diff --git a/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs b/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs index 4a6d17344f..3111c55a67 100644 --- a/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs +++ b/src/Http/Routing/src/Matching/HttpMethodMatcherPolicy.cs @@ -94,6 +94,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + if (candidates == null) { throw new ArgumentNullException(nameof(candidates)); diff --git a/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs b/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs index 41e8c55e64..ef05217153 100644 --- a/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs +++ b/src/Http/Routing/test/UnitTests/DefaultLinkGeneratorTest.cs @@ -635,12 +635,12 @@ namespace Microsoft.AspNetCore.Routing var linkGenerator = CreateLinkGenerator(endpointControllerAction, endpointController, endpointEmpty, endpointControllerActionParameter); - var httpContext = CreateHttpContext(); - // This sets data on the feature directly in the HttpContext - var context = new EndpointSelectorContext(httpContext) + var context = new EndpointSelectorContext() { RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", }) }; + var httpContext = CreateHttpContext(); + httpContext.Features.Set(context); var values = new RouteValueDictionary(); for (int i = 0; i < routeNames.Length; i++) @@ -678,11 +678,12 @@ namespace Microsoft.AspNetCore.Routing var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin); - var httpContext = CreateHttpContext(); - var context = new EndpointSelectorContext(httpContext) + var context = new EndpointSelectorContext() { RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", }) }; + var httpContext = CreateHttpContext(); + httpContext.Features.Set(context); var values = new RouteValueDictionary(); for (int i = 0; i < routeNames.Length; i++) @@ -720,7 +721,9 @@ namespace Microsoft.AspNetCore.Routing var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin); + var context = new EndpointSelectorContext(); var httpContext = CreateHttpContext(); + httpContext.Features.Set(context); var values = new RouteValueDictionary(); for (int i = 0; i < routeNames.Length; i++) diff --git a/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs index 9c1d59cfb2..17832a6b48 100644 --- a/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs +++ b/src/Http/Routing/test/UnitTests/EndpointMiddlewareTest.cs @@ -45,10 +45,10 @@ namespace Microsoft.AspNetCore.Routing var httpContext = new DefaultHttpContext(); httpContext.RequestServices = new ServiceProvider(); - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = null, - }; + }); RequestDelegate next = (c) => { @@ -77,10 +77,10 @@ namespace Microsoft.AspNetCore.Routing return Task.CompletedTask; }; - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint(endpointFunc, EndpointMetadataCollection.Empty, "Test"), - }; + }); RequestDelegate next = (c) => { @@ -108,10 +108,10 @@ namespace Microsoft.AspNetCore.Routing RequestServices = new ServiceProvider() }; - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), - }; + }); var middleware = new EndpointMiddleware(NullLogger.Instance, _ => Task.CompletedTask, RouteOptions); @@ -131,10 +131,10 @@ namespace Microsoft.AspNetCore.Routing RequestServices = new ServiceProvider() }; - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), - }; + }); httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true; @@ -155,11 +155,10 @@ namespace Microsoft.AspNetCore.Routing RequestServices = new ServiceProvider() }; - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), - }; - + }); var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true }); var middleware = new EndpointMiddleware(NullLogger.Instance, _ => Task.CompletedTask, routeOptions); @@ -179,10 +178,10 @@ namespace Microsoft.AspNetCore.Routing RequestServices = new ServiceProvider() }; - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), - }; + }); var middleware = new EndpointMiddleware(NullLogger.Instance, _ => Task.CompletedTask, RouteOptions); @@ -202,10 +201,10 @@ namespace Microsoft.AspNetCore.Routing RequestServices = new ServiceProvider() }; - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), - }; + }); httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true; @@ -226,11 +225,10 @@ namespace Microsoft.AspNetCore.Routing RequestServices = new ServiceProvider() }; - new EndpointSelectorContext(httpContext) + httpContext.Features.Set(new EndpointSelectorContext() { Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of()), "Test"), - }; - + }); var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true }); var middleware = new EndpointMiddleware(NullLogger.Instance, _ => Task.CompletedTask, routeOptions); diff --git a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs index 0dc1331316..97f33a2fbe 100644 --- a/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs +++ b/src/Http/Routing/test/UnitTests/EndpointRoutingMiddlewareTest.cs @@ -132,8 +132,12 @@ namespace Microsoft.AspNetCore.Routing private HttpContext CreateHttpContext() { + var context = new EndpointSelectorContext(); + var httpContext = new DefaultHttpContext(); - var context = new EndpointSelectorContext(httpContext); + httpContext.Features.Set(context); + httpContext.Features.Set(context); + httpContext.RequestServices = new TestServiceProvider(); return httpContext; diff --git a/src/Http/Routing/test/UnitTests/EndpointSelectorContextTest.cs b/src/Http/Routing/test/UnitTests/EndpointSelectorContextTest.cs index ed102cf26c..05ca8850da 100644 --- a/src/Http/Routing/test/UnitTests/EndpointSelectorContextTest.cs +++ b/src/Http/Routing/test/UnitTests/EndpointSelectorContextTest.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.Linq; @@ -9,52 +9,51 @@ using Xunit; namespace Microsoft.AspNetCore.Routing { - // The IRoutingFeature is TBD, we need a way to make it lazy. public class EndpointSelectorContextTest { - //[Fact] - //public void RouteData_CanIntializeDataTokens_WithMetadata() - //{ - // // Arrange - // var expected = new RouteValueDictionary(new { foo = 17, bar = "hello", }); + [Fact] + public void RouteData_CanIntializeDataTokens_WithMetadata() + { + // Arrange + var expected = new RouteValueDictionary(new { foo = 17, bar = "hello", }); - // var context = new EndpointSelectorContext() - // { - // Endpoint = new RouteEndpoint( - // TestConstants.EmptyRequestDelegate, - // RoutePatternFactory.Parse("/"), - // 0, - // new EndpointMetadataCollection(new DataTokensMetadata(expected)), - // "test"), - // }; + var context = new EndpointSelectorContext() + { + Endpoint = new RouteEndpoint( + TestConstants.EmptyRequestDelegate, + RoutePatternFactory.Parse("/"), + 0, + new EndpointMetadataCollection(new DataTokensMetadata(expected)), + "test"), + }; - // // Act - // var routeData = ((IRoutingFeature)context).RouteData; + // Act + var routeData = ((IRoutingFeature)context).RouteData; - // // Assert - // Assert.NotSame(expected, routeData.DataTokens); - // Assert.Equal(expected.OrderBy(kvp => kvp.Key), routeData.DataTokens.OrderBy(kvp => kvp.Key)); - //} + // Assert + Assert.NotSame(expected, routeData.DataTokens); + Assert.Equal(expected.OrderBy(kvp => kvp.Key), routeData.DataTokens.OrderBy(kvp => kvp.Key)); + } - //[Fact] - //public void RouteData_DataTokensIsEmpty_WithoutMetadata() - //{ - // // Arrange - // var context = new EndpointSelectorContext() - // { - // Endpoint = new RouteEndpoint( - // TestConstants.EmptyRequestDelegate, - // RoutePatternFactory.Parse("/"), - // 0, - // new EndpointMetadataCollection(), - // "test"), - // }; + [Fact] + public void RouteData_DataTokensIsEmpty_WithoutMetadata() + { + // Arrange + var context = new EndpointSelectorContext() + { + Endpoint = new RouteEndpoint( + TestConstants.EmptyRequestDelegate, + RoutePatternFactory.Parse("/"), + 0, + new EndpointMetadataCollection(), + "test"), + }; - // // Act - // var routeData = ((IRoutingFeature)context).RouteData; + // Act + var routeData = ((IRoutingFeature)context).RouteData; - // // Assert - // Assert.Empty(routeData.DataTokens); - //} + // Assert + Assert.Empty(routeData.DataTokens); + } } } diff --git a/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs b/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs index d887710288..e2a9382281 100644 --- a/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/LinkGeneratorEndpointNameExtensionsTest.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.Linq; @@ -26,11 +26,12 @@ namespace Microsoft.AspNetCore.Routing var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); - var httpContext = CreateHttpContext(); - var context = new EndpointSelectorContext(httpContext) + var context = new EndpointSelectorContext() { RouteValues = new RouteValueDictionary(new { p = "5", }) }; + var httpContext = CreateHttpContext(); + httpContext.Features.Set(context); httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); var values = new { query = "some?query", }; diff --git a/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs b/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs index 56f8e7f4ea..5f8d509d54 100644 --- a/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.cs +++ b/src/Http/Routing/test/UnitTests/LinkGeneratorRouteValuesAddressExtensionsTest.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.Linq; @@ -32,11 +32,12 @@ namespace Microsoft.AspNetCore.Routing var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); - var httpContext = CreateHttpContext(); - var context = new EndpointSelectorContext(httpContext) + var context = new EndpointSelectorContext() { RouteValues = new RouteValueDictionary(new { action = "Index", }) }; + var httpContext = CreateHttpContext(); + httpContext.Features.Set(context); httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); // Act diff --git a/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs b/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs index fbb0654f8b..19ded38c8b 100644 --- a/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs +++ b/src/Http/Routing/test/UnitTests/LinkGeneratorTestBase.cs @@ -19,11 +19,13 @@ namespace Microsoft.AspNetCore.Routing { var httpContext = new DefaultHttpContext(); - var context = new EndpointSelectorContext(httpContext) + var context = new EndpointSelectorContext { RouteValues = new RouteValueDictionary(ambientValues) }; + httpContext.Features.Set(context); + httpContext.Features.Set(context); return httpContext; } diff --git a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs index 87eecf6b0f..f1501b423f 100644 --- a/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.cs +++ b/src/Http/Routing/test/UnitTests/Matching/BarebonesMatcher.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; @@ -27,6 +27,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + var path = httpContext.Request.Path.Value; for (var i = 0; i < Matchers.Length; i++) { @@ -128,4 +133,4 @@ namespace Microsoft.AspNetCore.Routing.Matching } } } -} +} \ No newline at end of file diff --git a/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs b/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs index f1ce9dacbe..9d67c126d6 100644 --- a/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/DefaultEndpointSelectorTest.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; @@ -174,8 +174,10 @@ namespace Microsoft.AspNetCore.Routing.Matching private static (HttpContext httpContext, EndpointSelectorContext context) CreateContext() { + var context = new EndpointSelectorContext(); var httpContext = new DefaultHttpContext(); - var context = new EndpointSelectorContext(httpContext); + httpContext.Features.Set(context); + httpContext.Features.Set(context); return (httpContext, context); } diff --git a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs index a238bc0b39..e82f870043 100644 --- a/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/DfaMatcherTest.cs @@ -633,7 +633,7 @@ namespace Microsoft.AspNetCore.Routing.Matching var (httpContext, context) = CreateContext(); httpContext.Request.Path = "/test/17"; - + // Act await matcher.MatchAsync(httpContext, context); @@ -727,8 +727,11 @@ namespace Microsoft.AspNetCore.Routing.Matching private (HttpContext httpContext, EndpointSelectorContext context) CreateContext() { + var context = new EndpointSelectorContext(); + var httpContext = new DefaultHttpContext(); - var context = new EndpointSelectorContext(httpContext); + httpContext.Features.Set(context); + httpContext.Features.Set(context); return (httpContext, context); } diff --git a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs index d707084908..c812ad8d4c 100644 --- a/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs +++ b/src/Http/Routing/test/UnitTests/Matching/HostMatcherPolicyIntegrationTestBase.cs @@ -303,7 +303,9 @@ namespace Microsoft.AspNetCore.Routing.Matching httpContext.Request.Path = path; httpContext.Request.Scheme = scheme; - var context = new EndpointSelectorContext(httpContext); + var context = new EndpointSelectorContext(); + httpContext.Features.Set(context); + httpContext.Features.Set(context); return (httpContext, context); } diff --git a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs index ce561c1f8d..258e9b0a92 100644 --- a/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs +++ b/src/Http/Routing/test/UnitTests/Matching/HttpMethodMatcherPolicyIntegrationTestBase.cs @@ -352,7 +352,9 @@ namespace Microsoft.AspNetCore.Routing.Matching httpContext.Request.Headers[AccessControlRequestMethod] = httpMethod; } - var context = new EndpointSelectorContext(httpContext); + var context = new EndpointSelectorContext(); + httpContext.Features.Set(context); + httpContext.Features.Set(context); return (httpContext, context); } diff --git a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs index 98a08f0b1f..c7615bc19c 100644 --- a/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.cs +++ b/src/Http/Routing/test/UnitTests/Matching/MatcherConformanceTest.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; @@ -21,10 +21,13 @@ namespace Microsoft.AspNetCore.Routing.Matching httpContext.Request.Path = path; httpContext.RequestServices = CreateServices(); - var context = new EndpointSelectorContext(httpContext) + var context = new EndpointSelectorContext() { RouteValues = new RouteValueDictionary() }; + httpContext.Features.Set(context); + httpContext.Features.Set(context); + return (httpContext, context); } diff --git a/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs b/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs index 24b4a006cb..39eaee6cdd 100644 --- a/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.cs +++ b/src/Http/Routing/test/UnitTests/Matching/RouteMatcher.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,6 +24,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + var routeContext = new RouteContext(httpContext); await _inner.RouteAsync(routeContext); diff --git a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs index 734ed64f03..da068f5198 100644 --- a/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs +++ b/src/Http/Routing/test/UnitTests/Matching/RouteMatcherBuilder.cs @@ -1,11 +1,10 @@ -// 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; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.TestObjects; @@ -96,7 +95,12 @@ namespace Microsoft.AspNetCore.Routing.Matching public async Task RouteAsync(RouteContext routeContext) { - var context = new EndpointSelectorContext(routeContext.HttpContext); + var context = (EndpointSelectorContext)routeContext.HttpContext.Features.Get(); + + // This is needed due to a quirk of our tests - they reuse the endpoint feature + // across requests. + context.Endpoint = null; + await _selector.SelectAsync(routeContext.HttpContext, context, new CandidateSet(_candidates, _values, _scores)); if (context.Endpoint != null) { diff --git a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs index ec1c60105a..c7477e4611 100644 --- a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.cs +++ b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcher.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; @@ -26,6 +26,11 @@ namespace Microsoft.AspNetCore.Routing.Matching throw new ArgumentNullException(nameof(httpContext)); } + if (context == null) + { + throw new ArgumentNullException(nameof(context)); + } + var routeContext = new RouteContext(httpContext); await _inner.RouteAsync(routeContext); diff --git a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs index e464422592..5d4e25dfff 100644 --- a/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs +++ b/src/Http/Routing/test/UnitTests/Matching/TreeRouterMatcherBuilder.cs @@ -99,7 +99,11 @@ namespace Microsoft.AspNetCore.Routing.Matching public async Task RouteAsync(RouteContext routeContext) { - var context = new EndpointSelectorContext(routeContext.HttpContext); + var context = (EndpointSelectorContext)routeContext.HttpContext.Features.Get(); + + // This is needed due to a quirk of our tests - they reuse the endpoint feature. + context.Endpoint = null; + await _selector.SelectAsync(routeContext.HttpContext, context, new CandidateSet(_candidates, _values, _scores)); if (context.Endpoint != null) { diff --git a/src/Http/Routing/test/testassets/RoutingSandbox/Program.cs b/src/Http/Routing/test/testassets/RoutingSandbox/Program.cs index 4f2d60bc76..35e4e44c01 100644 --- a/src/Http/Routing/test/testassets/RoutingSandbox/Program.cs +++ b/src/Http/Routing/test/testassets/RoutingSandbox/Program.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; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs index fabaf7c0c4..a917555871 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.FeatureCollection.cs @@ -10,7 +10,6 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; -using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; @@ -19,18 +18,16 @@ using Microsoft.Net.Http.Headers; namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { internal partial class HttpProtocol : IHttpRequestFeature, - IHttpResponseFeature, - IResponseBodyPipeFeature, - IRequestBodyPipeFeature, - IHttpUpgradeFeature, - IHttpConnectionFeature, - IHttpRequestLifetimeFeature, - IHttpRequestIdentifierFeature, - IHttpBodyControlFeature, - IHttpMaxRequestBodySizeFeature, - IHttpResponseStartFeature, - IEndpointFeature, - IRouteValuesFeature + IHttpResponseFeature, + IResponseBodyPipeFeature, + IRequestBodyPipeFeature, + IHttpUpgradeFeature, + IHttpConnectionFeature, + IHttpRequestLifetimeFeature, + IHttpRequestIdentifierFeature, + IHttpBodyControlFeature, + IHttpMaxRequestBodySizeFeature, + IHttpResponseStartFeature { // NOTE: When feature interfaces are added to or removed from this HttpProtocol class implementation, // then the list of `implementedFeatures` in the generated code project MUST also be updated. @@ -261,24 +258,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http } } - Endpoint IEndpointFeature.Endpoint - { - get; - set; - } - - RouteValueDictionary IRouteValuesFeature.RouteValues - { - get - { - return _routeValues ??= new RouteValueDictionary(); - } - set - { - _routeValues = value; - } - } - protected void ResetHttp1Features() { _currentIHttpMinRequestBodyDataRateFeature = this; diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs index aaeffffe1b..bd0edf1357 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.Generated.cs @@ -21,8 +21,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private static readonly Type IServiceProvidersFeatureType = typeof(IServiceProvidersFeature); private static readonly Type IHttpRequestLifetimeFeatureType = typeof(IHttpRequestLifetimeFeature); private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature); - private static readonly Type IRouteValuesFeatureType = typeof(IRouteValuesFeature); - private static readonly Type IEndpointFeatureType = typeof(IEndpointFeature); private static readonly Type IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature); private static readonly Type IQueryFeatureType = typeof(IQueryFeature); private static readonly Type IFormFeatureType = typeof(IFormFeature); @@ -49,8 +47,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private object _currentIServiceProvidersFeature; private object _currentIHttpRequestLifetimeFeature; private object _currentIHttpConnectionFeature; - private object _currentIRouteValuesFeature; - private object _currentIEndpointFeature; private object _currentIHttpAuthenticationFeature; private object _currentIQueryFeature; private object _currentIFormFeature; @@ -86,8 +82,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http _currentIHttpMaxRequestBodySizeFeature = this; _currentIHttpBodyControlFeature = this; _currentIHttpResponseStartFeature = this; - _currentIRouteValuesFeature = this; - _currentIEndpointFeature = this; _currentIServiceProvidersFeature = null; _currentIHttpAuthenticationFeature = null; @@ -189,14 +183,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { feature = _currentIHttpConnectionFeature; } - else if (key == IRouteValuesFeatureType) - { - feature = _currentIRouteValuesFeature; - } - else if (key == IEndpointFeatureType) - { - feature = _currentIEndpointFeature; - } else if (key == IHttpAuthenticationFeatureType) { feature = _currentIHttpAuthenticationFeature; @@ -309,14 +295,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _currentIHttpConnectionFeature = value; } - else if (key == IRouteValuesFeatureType) - { - _currentIRouteValuesFeature = value; - } - else if (key == IEndpointFeatureType) - { - _currentIEndpointFeature = value; - } else if (key == IHttpAuthenticationFeatureType) { _currentIHttpAuthenticationFeature = value; @@ -427,14 +405,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { feature = (TFeature)_currentIHttpConnectionFeature; } - else if (typeof(TFeature) == typeof(IRouteValuesFeature)) - { - feature = (TFeature)_currentIRouteValuesFeature; - } - else if (typeof(TFeature) == typeof(IEndpointFeature)) - { - feature = (TFeature)_currentIEndpointFeature; - } else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) { feature = (TFeature)_currentIHttpAuthenticationFeature; @@ -551,14 +521,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { _currentIHttpConnectionFeature = feature; } - else if (typeof(TFeature) == typeof(IRouteValuesFeature)) - { - _currentIRouteValuesFeature = feature; - } - else if (typeof(TFeature) == typeof(IEndpointFeature)) - { - _currentIEndpointFeature = feature; - } else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) { _currentIHttpAuthenticationFeature = feature; @@ -667,14 +629,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http { yield return new KeyValuePair(IHttpConnectionFeatureType, _currentIHttpConnectionFeature); } - if (_currentIRouteValuesFeature != null) - { - yield return new KeyValuePair(IRouteValuesFeatureType, _currentIRouteValuesFeature); - } - if (_currentIEndpointFeature != null) - { - yield return new KeyValuePair(IEndpointFeatureType, _currentIEndpointFeature); - } if (_currentIHttpAuthenticationFeature != null) { yield return new KeyValuePair(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature); diff --git a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs index c721b2661a..e7d2dd154b 100644 --- a/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs +++ b/src/Servers/Kestrel/Core/src/Internal/Http/HttpProtocol.cs @@ -17,7 +17,6 @@ using Microsoft.AspNetCore.Hosting.Server; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Internal; -using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Primitives; @@ -65,7 +64,6 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http private readonly HttpConnectionContext _context; private DefaultHttpContext _httpContext; - private RouteValueDictionary _routeValues; protected string _methodText = null; private string _scheme = null; diff --git a/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs b/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs index 1be680d38a..f61df14ff2 100644 --- a/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs +++ b/src/Servers/Kestrel/tools/CodeGenerator/HttpProtocolFeatureCollection.cs @@ -19,8 +19,6 @@ namespace CodeGenerator "IServiceProvidersFeature", "IHttpRequestLifetimeFeature", "IHttpConnectionFeature", - "IRouteValuesFeature", - "IEndpointFeature" }; var commonFeatures = new[] @@ -72,9 +70,7 @@ namespace CodeGenerator "IHttpConnectionFeature", "IHttpMaxRequestBodySizeFeature", "IHttpBodyControlFeature", - "IHttpResponseStartFeature", - "IRouteValuesFeature", - "IEndpointFeature" + "IHttpResponseStartFeature" }; var usings = $@"