Make endpoint routing allocation free in common scenarios (#9774)

- This change tries to remove the EndpointSelectoContext allocation by making it a wrapper struct over the HttpContext. Unlike before, the HttpContext gets mutated once any component in the routnig pipeline sets a non null endpoint. This used to happen after the processing was complete.
- This change also implements the IRouteValuesFeature and IEndpointFeature in HttpProtocol to avoid the feature allocation and feature collection version churn.
- We also set the IRouteValuesFeature in IRouter based scenarios.
- Since we're not implementing IRoutingFeature in endpoint routing anymore
we can just create the RouteData inside MVC. We do this by polyfilling RouteData when using endpoint routing inside of MVC
- Implement GetRouteValue in terms of IRouteValuesFeature
- Noop if the EndpointRoutingMiddleware if an endpoint is already set
- Added tests
This commit is contained in:
David Fowler 2019-04-28 21:29:08 -07:00 committed by GitHub
parent cc8af6d2c4
commit 0d77594d17
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
47 changed files with 325 additions and 370 deletions

View File

@ -3,6 +3,7 @@
using System; using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
@ -23,8 +24,8 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
var routingFeature = httpContext.Features[typeof(IRoutingFeature)] as IRoutingFeature; var routingFeature = httpContext.Features.Get<IRoutingFeature>();
return routingFeature?.RouteData; return routingFeature?.RouteData ?? new RouteData(httpContext.Request.RouteValues);
} }
/// <summary> /// <summary>
@ -46,8 +47,7 @@ namespace Microsoft.AspNetCore.Routing
throw new ArgumentNullException(nameof(key)); throw new ArgumentNullException(nameof(key));
} }
var routingFeature = httpContext.Features[typeof(IRoutingFeature)] as IRoutingFeature; return httpContext.Features.Get<IRouteValuesFeature>()?.RouteValues[key];
return routingFeature?.RouteData.Values[key];
} }
} }
} }

View File

@ -7,14 +7,12 @@ using System.Runtime.CompilerServices;
using System.Text; using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.Template; using Microsoft.AspNetCore.Routing.Template;
using Microsoft.AspNetCore.Routing.Tree; using Microsoft.AspNetCore.Routing.Tree;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions; using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.ObjectPool;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
@ -47,7 +45,7 @@ namespace Microsoft.AspNetCore.Routing
return CreateServices().GetRequiredService<DfaMatcherBuilder>(); return CreateServices().GetRequiredService<DfaMatcherBuilder>();
} }
private protected static int[] SampleRequests(int endpointCount, int count) private protected static int[] SampleRequests(int endpointCount, int count)
{ {
// This isn't very high tech, but it's at least regular distribution. // This isn't very high tech, but it's at least regular distribution.
// We sort the route templates by precedence, so this should result in // We sort the route templates by precedence, so this should result in
@ -131,12 +129,10 @@ namespace Microsoft.AspNetCore.Routing
protected (HttpContext httpContext, RouteValueDictionary ambientValues) CreateCurrentRequestContext( protected (HttpContext httpContext, RouteValueDictionary ambientValues) CreateCurrentRequestContext(
object ambientValues = null) object ambientValues = null)
{ {
var feature = new EndpointSelectorContext { RouteValues = new RouteValueDictionary(ambientValues) };
var context = new DefaultHttpContext(); var context = new DefaultHttpContext();
context.Features.Set<IEndpointFeature>(feature); context.Request.RouteValues = new RouteValueDictionary(ambientValues);
context.Features.Set<IRouteValuesFeature>(feature);
return (context, feature.RouteValues); return (context, context.Request.RouteValues);
} }
protected void CreateOutboundRouteEntry(TreeRouteBuilder treeRouteBuilder, RouteEndpoint endpoint) protected void CreateOutboundRouteEntry(TreeRouteBuilder treeRouteBuilder, RouteEndpoint endpoint)

View File

@ -11,15 +11,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
// Just like TechEmpower Plaintext // Just like TechEmpower Plaintext
public partial class MatcherSingleEntryBenchmark : EndpointRoutingBenchmarkBase public partial class MatcherSingleEntryBenchmark : EndpointRoutingBenchmarkBase
{ {
private const int SampleCount = 100;
private BarebonesMatcher _baseline; private BarebonesMatcher _baseline;
private Matcher _dfa; private Matcher _dfa;
private Matcher _route; private Matcher _route;
private Matcher _tree; private Matcher _tree;
private EndpointSelectorContext _feature;
[GlobalSetup] [GlobalSetup]
public void Setup() public void Setup()
{ {
@ -35,8 +31,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
_dfa = SetupMatcher(CreateDfaMatcherBuilder()); _dfa = SetupMatcher(CreateDfaMatcherBuilder());
_route = SetupMatcher(new RouteMatcherBuilder()); _route = SetupMatcher(new RouteMatcherBuilder());
_tree = SetupMatcher(new TreeRouterMatcherBuilder()); _tree = SetupMatcher(new TreeRouterMatcherBuilder());
_feature = new EndpointSelectorContext();
} }
private Matcher SetupMatcher(MatcherBuilder builder) private Matcher SetupMatcher(MatcherBuilder builder)
@ -48,8 +42,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
[Benchmark(Baseline = true)] [Benchmark(Baseline = true)]
public async Task Baseline() public async Task Baseline()
{ {
var feature = _feature;
var httpContext = Requests[0]; var httpContext = Requests[0];
var feature = new EndpointSelectorContext(httpContext);
await _baseline.MatchAsync(httpContext, feature); await _baseline.MatchAsync(httpContext, feature);
Validate(httpContext, Endpoints[0], feature.Endpoint); Validate(httpContext, Endpoints[0], feature.Endpoint);
@ -58,8 +52,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
[Benchmark] [Benchmark]
public async Task Dfa() public async Task Dfa()
{ {
var feature = _feature;
var httpContext = Requests[0]; var httpContext = Requests[0];
var feature = new EndpointSelectorContext(httpContext);
await _dfa.MatchAsync(httpContext, feature); await _dfa.MatchAsync(httpContext, feature);
Validate(httpContext, Endpoints[0], feature.Endpoint); Validate(httpContext, Endpoints[0], feature.Endpoint);
@ -68,12 +62,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
[Benchmark] [Benchmark]
public async Task LegacyTreeRouter() public async Task LegacyTreeRouter()
{ {
var feature = _feature;
var httpContext = Requests[0]; 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<IEndpointFeature>(feature);
await _tree.MatchAsync(httpContext, feature); await _tree.MatchAsync(httpContext, feature);
Validate(httpContext, Endpoints[0], feature.Endpoint); Validate(httpContext, Endpoints[0], feature.Endpoint);
@ -82,14 +72,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
[Benchmark] [Benchmark]
public async Task LegacyRouter() public async Task LegacyRouter()
{ {
var feature = _feature;
var httpContext = Requests[0]; 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<IEndpointFeature>(feature);
await _route.MatchAsync(httpContext, feature); await _route.MatchAsync(httpContext, feature);
Validate(httpContext, Endpoints[0], feature.Endpoint); Validate(httpContext, Endpoints[0], feature.Endpoint);
} }
} }
} }

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -30,11 +30,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase)) if (string.Equals(_endpoint.RoutePattern.RawText, path, StringComparison.OrdinalIgnoreCase))
{ {

View File

@ -88,11 +88,12 @@ namespace Microsoft.AspNetCore.Routing
public EndpointNameMetadata(string endpointName) { } public EndpointNameMetadata(string endpointName) { }
public string EndpointName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } } public string EndpointName { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } }
} }
public sealed partial class EndpointSelectorContext : Microsoft.AspNetCore.Http.Features.IEndpointFeature, Microsoft.AspNetCore.Http.Features.IRouteValuesFeature, Microsoft.AspNetCore.Routing.IRoutingFeature [System.Runtime.InteropServices.StructLayoutAttribute(System.Runtime.InteropServices.LayoutKind.Sequential)]
public readonly partial struct EndpointSelectorContext
{ {
public EndpointSelectorContext() { } private readonly object _dummy;
public Microsoft.AspNetCore.Http.Endpoint Endpoint { [System.Runtime.CompilerServices.CompilerGeneratedAttribute]get { throw null; } [System.Runtime.CompilerServices.CompilerGeneratedAttribute]set { } } public EndpointSelectorContext(Microsoft.AspNetCore.Http.HttpContext httpContext) { throw null; }
Microsoft.AspNetCore.Routing.RouteData Microsoft.AspNetCore.Routing.IRoutingFeature.RouteData { get { throw null; } set { } } public Microsoft.AspNetCore.Http.Endpoint Endpoint { get { throw null; } set { } }
public Microsoft.AspNetCore.Routing.RouteValueDictionary RouteValues { get { throw null; } set { } } public Microsoft.AspNetCore.Routing.RouteValueDictionary RouteValues { get { throw null; } set { } }
} }
[System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=false, Inherited=false)] [System.AttributeUsageAttribute(System.AttributeTargets.Class | System.AttributeTargets.Method, AllowMultiple=false, Inherited=false)]

View File

@ -6,7 +6,6 @@ using System.Runtime.CompilerServices;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
@ -56,7 +55,14 @@ namespace Microsoft.AspNetCore.Routing
public Task Invoke(HttpContext httpContext) public Task Invoke(HttpContext httpContext)
{ {
var feature = new EndpointSelectorContext(); var feature = new EndpointSelectorContext(httpContext);
// There's already an endpoint, skip maching completely
if (feature.Endpoint != null)
{
Log.MatchSkipped(_logger, feature.Endpoint);
return _next(httpContext);
}
// There's an inherent race condition between waiting for init and accessing the matcher // 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. // this is OK because once `_matcher` is initialized, it will not be set to null again.
@ -93,31 +99,19 @@ namespace Microsoft.AspNetCore.Routing
[MethodImpl(MethodImplOptions.AggressiveInlining)] [MethodImpl(MethodImplOptions.AggressiveInlining)]
private Task SetRoutingAndContinue(HttpContext httpContext, EndpointSelectorContext feature) private Task SetRoutingAndContinue(HttpContext httpContext, EndpointSelectorContext feature)
{ {
if (feature.Endpoint != null) // If there was no mutation of the endpoint then log failure
{ if (feature.Endpoint is null)
// 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
{ {
Log.MatchFailure(_logger); Log.MatchFailure(_logger);
} }
else
{
Log.MatchSuccess(_logger, feature);
}
return _next(httpContext); return _next(httpContext);
} }
private static void SetFeatures(HttpContext httpContext, EndpointSelectorContext context)
{
// For back-compat EndpointSelectorContext implements IEndpointFeature,
// IRouteValuesFeature and IRoutingFeature
httpContext.Features.Set<IRoutingFeature>(context);
httpContext.Features.Set<IRouteValuesFeature>(context);
httpContext.Features.Set<IEndpointFeature>(context);
}
// Initialization is async to avoid blocking threads while reflection and things // Initialization is async to avoid blocking threads while reflection and things
// of that nature take place. // of that nature take place.
// //
@ -185,6 +179,11 @@ namespace Microsoft.AspNetCore.Routing
new EventId(2, "MatchFailure"), new EventId(2, "MatchFailure"),
"Request did not match any endpoints"); "Request did not match any endpoints");
private static readonly Action<ILogger, string, Exception> _matchingSkipped = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(3, "MatchingSkipped"),
"Endpoint '{EndpointName}' already set, skipping route matching.");
public static void MatchSuccess(ILogger logger, EndpointSelectorContext context) public static void MatchSuccess(ILogger logger, EndpointSelectorContext context)
{ {
_matchSuccess(logger, context.Endpoint.DisplayName, null); _matchSuccess(logger, context.Endpoint.DisplayName, null);
@ -194,6 +193,11 @@ namespace Microsoft.AspNetCore.Routing
{ {
_matchFailure(logger, null); _matchFailure(logger, null);
} }
public static void MatchSkipped(ILogger logger, Endpoint endpoint)
{
_matchingSkipped(logger, endpoint.DisplayName, null);
}
} }
} }
} }

View File

@ -3,68 +3,49 @@
using System; using System;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Endpoints;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
public sealed class EndpointSelectorContext : IEndpointFeature, IRouteValuesFeature, IRoutingFeature public readonly struct EndpointSelectorContext
{ {
private RouteData _routeData; private readonly HttpContext _httpContext;
private RouteValueDictionary _routeValues;
public EndpointSelectorContext(HttpContext httpContext)
{
_httpContext = httpContext ?? throw new ArgumentNullException(nameof(httpContext));
}
/// <summary> /// <summary>
/// Gets or sets the selected <see cref="Http.Endpoint"/> for the current /// Gets or sets the selected <see cref="Http.Endpoint"/> for the current
/// request. /// request.
/// </summary> /// </summary>
public Endpoint Endpoint { get; set; } public Endpoint Endpoint
{
get
{
return _httpContext.GetEndpoint();
}
set
{
_httpContext.SetEndpoint(value);
}
}
/// <summary> /// <summary>
/// Gets or sets the <see cref="RouteValueDictionary"/> associated with the currrent /// Gets or sets the <see cref="RouteValueDictionary"/> associated with the currrent
/// request. /// request.
/// </summary> /// </summary>
public RouteValueDictionary RouteValues public RouteValueDictionary RouteValues
{
get => _routeValues ?? (_routeValues = new RouteValueDictionary());
set
{
_routeValues = value;
// RouteData will be created next get with new Values
_routeData = null;
}
}
/// <summary>
/// Gets or sets the <see cref="RouteData"/> for the current request.
/// </summary>
/// <remarks>
/// The setter is not implemented. Use <see cref="RouteValues"/> to set the route values.
/// </remarks>
RouteData IRoutingFeature.RouteData
{ {
get get
{ {
if (_routeData == null) return _httpContext.Request.RouteValues;
{ }
_routeData = _routeValues == null ? new RouteData() : new RouteData(_routeValues); set
{
// Note: DataTokens won't update if someone else overwrites the Endpoint _httpContext.Request.RouteValues = value;
// 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<IDataTokensMetadata>();
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();
} }
} }
} }

View File

@ -21,11 +21,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidateSet == null) if (candidateSet == null)
{ {
throw new ArgumentNullException(nameof(candidateSet)); throw new ArgumentNullException(nameof(candidateSet));

View File

@ -34,11 +34,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); 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. // 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); var log = _logger.IsEnabled(LogLevel.Debug);

View File

@ -77,11 +77,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidates == null) if (candidates == null)
{ {
throw new ArgumentNullException(nameof(candidates)); throw new ArgumentNullException(nameof(candidates));

View File

@ -94,11 +94,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidates == null) if (candidates == null)
{ {
throw new ArgumentNullException(nameof(candidates)); throw new ArgumentNullException(nameof(candidates));

View File

@ -40,11 +40,15 @@ namespace Microsoft.AspNetCore.Builder
} }
else else
{ {
httpContext.Features[typeof(IRoutingFeature)] = new RoutingFeature() var routingFeature = new RoutingFeature()
{ {
RouteData = context.RouteData, RouteData = context.RouteData
}; };
// Set the RouteValues on the current request, this is to keep the IRouteValuesFeature inline with the IRoutingFeature
httpContext.Request.RouteValues = context.RouteData.Values;
httpContext.Features.Set<IRoutingFeature>(routingFeature);
await context.Handler(context.HttpContext); await context.Handler(context.HttpContext);
} }
} }

View File

@ -635,12 +635,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator(endpointControllerAction, endpointController, endpointEmpty, endpointControllerActionParameter); var linkGenerator = CreateLinkGenerator(endpointControllerAction, endpointController, endpointEmpty, endpointControllerActionParameter);
var context = new EndpointSelectorContext()
{
RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", })
};
var httpContext = CreateHttpContext(); var httpContext = CreateHttpContext();
httpContext.Features.Set<IRouteValuesFeature>(context); httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index" });
var values = new RouteValueDictionary(); var values = new RouteValueDictionary();
for (int i = 0; i < routeNames.Length; i++) for (int i = 0; i < routeNames.Length; i++)
@ -678,12 +674,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin); var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin);
var context = new EndpointSelectorContext()
{
RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", })
};
var httpContext = CreateHttpContext(); var httpContext = CreateHttpContext();
httpContext.Features.Set<IRouteValuesFeature>(context); httpContext.Request.RouteValues = new RouteValueDictionary(new { controller = "Home", action = "Index", });
var values = new RouteValueDictionary(); var values = new RouteValueDictionary();
for (int i = 0; i < routeNames.Length; i++) for (int i = 0; i < routeNames.Length; i++)
@ -721,9 +713,7 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin); var linkGenerator = CreateLinkGenerator(homeIndex, homeLogin);
var context = new EndpointSelectorContext();
var httpContext = CreateHttpContext(); var httpContext = CreateHttpContext();
httpContext.Features.Set<IRouteValuesFeature>(context);
var values = new RouteValueDictionary(); var values = new RouteValueDictionary();
for (int i = 0; i < routeNames.Length; i++) for (int i = 0; i < routeNames.Length; i++)

View File

@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Cors.Infrastructure; using Microsoft.AspNetCore.Cors.Infrastructure;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Options; using Microsoft.Extensions.Options;
@ -44,12 +45,8 @@ namespace Microsoft.AspNetCore.Routing
// Arrange // Arrange
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.RequestServices = new ServiceProvider(); httpContext.RequestServices = new ServiceProvider();
httpContext.SetEndpoint(null);
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext()
{
Endpoint = null,
});
RequestDelegate next = (c) => RequestDelegate next = (c) =>
{ {
return Task.CompletedTask; return Task.CompletedTask;
@ -77,11 +74,8 @@ namespace Microsoft.AspNetCore.Routing
return Task.CompletedTask; return Task.CompletedTask;
}; };
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(endpointFunc, EndpointMetadataCollection.Empty, "Test"));
{
Endpoint = new Endpoint(endpointFunc, EndpointMetadataCollection.Empty, "Test"),
});
RequestDelegate next = (c) => RequestDelegate next = (c) =>
{ {
return Task.CompletedTask; return Task.CompletedTask;
@ -108,10 +102,7 @@ namespace Microsoft.AspNetCore.Routing
RequestServices = new ServiceProvider() RequestServices = new ServiceProvider()
}; };
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions); var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
@ -131,10 +122,7 @@ namespace Microsoft.AspNetCore.Routing
RequestServices = new ServiceProvider() RequestServices = new ServiceProvider()
}; };
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true; httpContext.Items[EndpointMiddleware.AuthorizationMiddlewareInvokedKey] = true;
@ -155,10 +143,8 @@ namespace Microsoft.AspNetCore.Routing
RequestServices = new ServiceProvider() RequestServices = new ServiceProvider()
}; };
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true }); var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions); var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions);
@ -178,10 +164,7 @@ namespace Microsoft.AspNetCore.Routing
RequestServices = new ServiceProvider() RequestServices = new ServiceProvider()
}; };
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"));
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"),
});
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions); var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, RouteOptions);
@ -201,10 +184,7 @@ namespace Microsoft.AspNetCore.Routing
RequestServices = new ServiceProvider() RequestServices = new ServiceProvider()
}; };
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"));
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<ICorsMetadata>()), "Test"),
});
httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true; httpContext.Items[EndpointMiddleware.CorsMiddlewareInvokedKey] = true;
@ -225,10 +205,8 @@ namespace Microsoft.AspNetCore.Routing
RequestServices = new ServiceProvider() RequestServices = new ServiceProvider()
}; };
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"));
{
Endpoint = new Endpoint(_ => Task.CompletedTask, new EndpointMetadataCollection(Mock.Of<IAuthorizeData>()), "Test"),
});
var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true }); var routeOptions = Options.Create(new RouteOptions { SuppressCheckForUnhandledSecurityMetadata = true });
var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions); var middleware = new EndpointMiddleware(NullLogger<EndpointMiddleware>.Instance, _ => Task.CompletedTask, routeOptions);

View File

@ -2,17 +2,16 @@
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Routing.TestObjects;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions; using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Microsoft.Extensions.Options;
using Moq; using Moq;
using Xunit; using Xunit;
@ -36,6 +35,24 @@ namespace Microsoft.AspNetCore.Routing
Assert.NotNull(endpointFeature); Assert.NotNull(endpointFeature);
} }
[Fact]
public async Task Invoke_SkipsRouting_IfEndpointSet()
{
// Arrange
var httpContext = CreateHttpContext();
httpContext.SetEndpoint(new Endpoint(c => Task.CompletedTask, new EndpointMetadataCollection(), "myapp"));
var middleware = CreateMiddleware();
// Act
await middleware.Invoke(httpContext);
// Assert
var endpoint = httpContext.GetEndpoint();
Assert.NotNull(endpoint);
Assert.Equal("myapp", endpoint.DisplayName);
}
[Fact] [Fact]
public async Task Invoke_OnCall_WritesToConfiguredLogger() public async Task Invoke_OnCall_WritesToConfiguredLogger()
{ {
@ -132,25 +149,22 @@ namespace Microsoft.AspNetCore.Routing
private HttpContext CreateHttpContext() private HttpContext CreateHttpContext()
{ {
var context = new EndpointSelectorContext(); var httpContext = new DefaultHttpContext
{
var httpContext = new DefaultHttpContext(); RequestServices = new TestServiceProvider()
httpContext.Features.Set<IEndpointFeature>(context); };
httpContext.Features.Set<IRouteValuesFeature>(context);
httpContext.RequestServices = new TestServiceProvider();
return httpContext; return httpContext;
} }
private EndpointRoutingMiddleware CreateMiddleware( private EndpointRoutingMiddleware CreateMiddleware(
Logger<EndpointRoutingMiddleware> logger = null, Logger<EndpointRoutingMiddleware> logger = null,
MatcherFactory matcherFactory = null) MatcherFactory matcherFactory = null,
RequestDelegate next = null)
{ {
RequestDelegate next = (c) => Task.FromResult<object>(null); next ??= c => Task.CompletedTask;
logger ??= new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance);
logger = logger ?? new Logger<EndpointRoutingMiddleware>(NullLoggerFactory.Instance); matcherFactory ??= new TestMatcherFactory(true);
matcherFactory = matcherFactory ?? new TestMatcherFactory(true);
var middleware = new EndpointRoutingMiddleware( var middleware = new EndpointRoutingMiddleware(
matcherFactory, matcherFactory,

View File

@ -1,9 +1,8 @@
// 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Xunit; using Xunit;
@ -12,48 +11,43 @@ namespace Microsoft.AspNetCore.Routing
public class EndpointSelectorContextTest public class EndpointSelectorContextTest
{ {
[Fact] [Fact]
public void RouteData_CanIntializeDataTokens_WithMetadata() public void SettingEndpointSetsEndpointOnHttpContext()
{ {
// Arrange var httpContext = new DefaultHttpContext();
var expected = new RouteValueDictionary(new { foo = 17, bar = "hello", }); var ep = new RouteEndpoint(
var context = new EndpointSelectorContext()
{
Endpoint = new RouteEndpoint(
TestConstants.EmptyRequestDelegate,
RoutePatternFactory.Parse("/"),
0,
new EndpointMetadataCollection(new DataTokensMetadata(expected)),
"test"),
};
// 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));
}
[Fact]
public void RouteData_DataTokensIsEmpty_WithoutMetadata()
{
// Arrange
var context = new EndpointSelectorContext()
{
Endpoint = new RouteEndpoint(
TestConstants.EmptyRequestDelegate, TestConstants.EmptyRequestDelegate,
RoutePatternFactory.Parse("/"), RoutePatternFactory.Parse("/"),
0, 0,
new EndpointMetadataCollection(), new EndpointMetadataCollection(),
"test"), "test");
new EndpointSelectorContext(httpContext)
{
Endpoint = ep,
}; };
// Act // Assert
var routeData = ((IRoutingFeature)context).RouteData; var endpoint = httpContext.GetEndpoint();
Assert.NotNull(endpoint);
Assert.Same(ep, endpoint);
}
[Fact]
public void SettingRouteValuesSetRouteValuesHttpContext()
{
var httpContext = new DefaultHttpContext();
var routeValues = new RouteValueDictionary(new { A = "1" });
new EndpointSelectorContext(httpContext)
{
RouteValues = routeValues
};
// Assert // Assert
Assert.Empty(routeData.DataTokens); Assert.NotNull(httpContext.Request.RouteValues);
Assert.Same(routeValues, httpContext.Request.RouteValues);
Assert.Single(httpContext.Request.RouteValues);
Assert.Equal("1", httpContext.Request.RouteValues["A"]);
} }
} }
} }

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq; using System.Linq;
@ -26,12 +26,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
var context = new EndpointSelectorContext()
{
RouteValues = new RouteValueDictionary(new { p = "5", })
};
var httpContext = CreateHttpContext(); var httpContext = CreateHttpContext();
httpContext.Features.Set<IRouteValuesFeature>(context); httpContext.Request.RouteValues = new RouteValueDictionary(new { p = "5", });
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
var values = new { query = "some?query", }; var values = new { query = "some?query", };

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System.Linq; using System.Linq;
@ -32,12 +32,8 @@ namespace Microsoft.AspNetCore.Routing
var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2); var linkGenerator = CreateLinkGenerator(endpoint1, endpoint2);
var context = new EndpointSelectorContext()
{
RouteValues = new RouteValueDictionary(new { action = "Index", })
};
var httpContext = CreateHttpContext(); var httpContext = CreateHttpContext();
httpContext.Features.Set<IRouteValuesFeature>(context); httpContext.Request.RouteValues = new RouteValueDictionary(new { action = "Index", });
httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?"); httpContext.Request.PathBase = new PathString("/Foo/Bar?encodeme?");
// Act // Act

View File

@ -18,14 +18,7 @@ namespace Microsoft.AspNetCore.Routing
protected HttpContext CreateHttpContext(object ambientValues = null) protected HttpContext CreateHttpContext(object ambientValues = null)
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Request.RouteValues = new RouteValueDictionary(ambientValues);
var context = new EndpointSelectorContext
{
RouteValues = new RouteValueDictionary(ambientValues)
};
httpContext.Features.Set<IEndpointFeature>(context);
httpContext.Features.Set<IRouteValuesFeature>(context);
return httpContext; return httpContext;
} }

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -27,11 +27,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var path = httpContext.Request.Path.Value; var path = httpContext.Request.Path.Value;
for (var i = 0; i < Matchers.Length; i++) for (var i = 0; i < Matchers.Length; i++)
{ {
@ -133,4 +128,4 @@ namespace Microsoft.AspNetCore.Routing.Matching
} }
} }
} }
} }

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -174,12 +174,8 @@ namespace Microsoft.AspNetCore.Routing.Matching
private static (HttpContext httpContext, EndpointSelectorContext context) CreateContext() private static (HttpContext httpContext, EndpointSelectorContext context) CreateContext()
{ {
var context = new EndpointSelectorContext();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IEndpointFeature>(context); return (httpContext, new EndpointSelectorContext(httpContext));
httpContext.Features.Set<IRouteValuesFeature>(context);
return (httpContext, context);
} }
private static RouteEndpoint CreateEndpoint(string template) private static RouteEndpoint CreateEndpoint(string template)

View File

@ -403,7 +403,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var endpointSelector = new Mock<EndpointSelector>(); var endpointSelector = new Mock<EndpointSelector>();
endpointSelector endpointSelector
.Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointSelectorContext>(), It.IsAny<CandidateSet>())) .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointSelectorContext>(), It.IsAny<CandidateSet>()))
.Callback<HttpContext, IEndpointFeature, CandidateSet>((c, f, cs) => .Callback<HttpContext, EndpointSelectorContext, CandidateSet>((c, f, cs) =>
{ {
Assert.Equal(2, cs.Count); Assert.Equal(2, cs.Count);
@ -449,7 +449,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var endpointSelector = new Mock<EndpointSelector>(); var endpointSelector = new Mock<EndpointSelector>();
endpointSelector endpointSelector
.Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointSelectorContext>(), It.IsAny<CandidateSet>())) .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointSelectorContext>(), It.IsAny<CandidateSet>()))
.Callback<HttpContext, IEndpointFeature, CandidateSet>((c, f, cs) => .Callback<HttpContext, EndpointSelectorContext, CandidateSet>((c, f, cs) =>
{ {
Assert.Equal(2, cs.Count); Assert.Equal(2, cs.Count);
@ -496,7 +496,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var endpointSelector = new Mock<EndpointSelector>(); var endpointSelector = new Mock<EndpointSelector>();
endpointSelector endpointSelector
.Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointSelectorContext>(), It.IsAny<CandidateSet>())) .Setup(s => s.SelectAsync(It.IsAny<HttpContext>(), It.IsAny<EndpointSelectorContext>(), It.IsAny<CandidateSet>()))
.Callback<HttpContext, IEndpointFeature, CandidateSet>((c, f, cs) => .Callback<HttpContext, EndpointSelectorContext, CandidateSet>((c, f, cs) =>
{ {
Assert.Equal(2, cs.Count); Assert.Equal(2, cs.Count);
@ -727,7 +727,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
var (httpContext, context) = CreateContext(); var (httpContext, context) = CreateContext();
httpContext.Request.Path = "/test/17"; httpContext.Request.Path = "/test/17";
// Act // Act
await matcher.MatchAsync(httpContext, context); await matcher.MatchAsync(httpContext, context);
@ -821,13 +821,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
private (HttpContext httpContext, EndpointSelectorContext context) CreateContext() private (HttpContext httpContext, EndpointSelectorContext context) CreateContext()
{ {
var context = new EndpointSelectorContext();
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Features.Set<IEndpointFeature>(context);
httpContext.Features.Set<IRouteValuesFeature>(context);
return (httpContext, context); return (httpContext, new EndpointSelectorContext(httpContext));
} }
} }
} }

View File

@ -303,11 +303,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
httpContext.Request.Path = path; httpContext.Request.Path = path;
httpContext.Request.Scheme = scheme; httpContext.Request.Scheme = scheme;
var context = new EndpointSelectorContext(); return (httpContext, new EndpointSelectorContext(httpContext));
httpContext.Features.Set<IEndpointFeature>(context);
httpContext.Features.Set<IRouteValuesFeature>(context);
return (httpContext, context);
} }
internal RouteEndpoint CreateEndpoint( internal RouteEndpoint CreateEndpoint(

View File

@ -352,11 +352,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
httpContext.Request.Headers[AccessControlRequestMethod] = httpMethod; httpContext.Request.Headers[AccessControlRequestMethod] = httpMethod;
} }
var context = new EndpointSelectorContext(); return (httpContext, new EndpointSelectorContext(httpContext));
httpContext.Features.Set<IEndpointFeature>(context);
httpContext.Features.Set<IRouteValuesFeature>(context);
return (httpContext, context);
} }
internal RouteEndpoint CreateEndpoint( internal RouteEndpoint CreateEndpoint(

View File

@ -53,7 +53,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new XunitException($"Was expected to match '{expected.DisplayName}' but did not match."); throw new XunitException($"Was expected to match '{expected.DisplayName}' but did not match.");
} }
var actualValues = httpContext.Features.Get<IRouteValuesFeature>().RouteValues; var actualValues = httpContext.Request.RouteValues;
if (actualValues == null) if (actualValues == null)
{ {

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -20,15 +20,7 @@ namespace Microsoft.AspNetCore.Routing.Matching
httpContext.Request.Method = "TEST"; httpContext.Request.Method = "TEST";
httpContext.Request.Path = path; httpContext.Request.Path = path;
httpContext.RequestServices = CreateServices(); httpContext.RequestServices = CreateServices();
return (httpContext, new EndpointSelectorContext(httpContext));
var context = new EndpointSelectorContext()
{
RouteValues = new RouteValueDictionary()
};
httpContext.Features.Set<IEndpointFeature>(context);
httpContext.Features.Set<IRouteValuesFeature>(context);
return (httpContext, context);
} }
// The older routing implementations retrieve services when they first execute. // The older routing implementations retrieve services when they first execute.

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -24,11 +24,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var routeContext = new RouteContext(httpContext); var routeContext = new RouteContext(httpContext);
await _inner.RouteAsync(routeContext); await _inner.RouteAsync(routeContext);

View File

@ -1,10 +1,11 @@
// 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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing.Patterns; using Microsoft.AspNetCore.Routing.Patterns;
using Microsoft.AspNetCore.Routing.TestObjects; using Microsoft.AspNetCore.Routing.TestObjects;
@ -95,10 +96,9 @@ namespace Microsoft.AspNetCore.Routing.Matching
public async Task RouteAsync(RouteContext routeContext) public async Task RouteAsync(RouteContext routeContext)
{ {
var context = (EndpointSelectorContext)routeContext.HttpContext.Features.Get<IEndpointFeature>(); var context = new EndpointSelectorContext(routeContext.HttpContext);
// This is needed due to a quirk of our tests - they reuse the endpoint feature // This is needed due to a quirk of our tests - they reuse the endpoint feature.
// across requests.
context.Endpoint = null; context.Endpoint = null;
await _selector.SelectAsync(routeContext.HttpContext, context, new CandidateSet(_candidates, _values, _scores)); await _selector.SelectAsync(routeContext.HttpContext, context, new CandidateSet(_candidates, _values, _scores));

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;
@ -26,11 +26,6 @@ namespace Microsoft.AspNetCore.Routing.Matching
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
var routeContext = new RouteContext(httpContext); var routeContext = new RouteContext(httpContext);
await _inner.RouteAsync(routeContext); await _inner.RouteAsync(routeContext);

View File

@ -99,11 +99,11 @@ namespace Microsoft.AspNetCore.Routing.Matching
public async Task RouteAsync(RouteContext routeContext) public async Task RouteAsync(RouteContext routeContext)
{ {
var context = (EndpointSelectorContext)routeContext.HttpContext.Features.Get<IEndpointFeature>(); var context = new EndpointSelectorContext(routeContext.HttpContext);
// This is needed due to a quirk of our tests - they reuse the endpoint feature. // This is needed due to a quirk of our tests - they reuse the endpoint feature.
context.Endpoint = null; context.Endpoint = null;
await _selector.SelectAsync(routeContext.HttpContext, context, new CandidateSet(_candidates, _values, _scores)); await _selector.SelectAsync(routeContext.HttpContext, context, new CandidateSet(_candidates, _values, _scores));
if (context.Endpoint != null) if (context.Endpoint != null)
{ {

View File

@ -5,13 +5,65 @@ using System;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging.Abstractions;
using Microsoft.Extensions.Logging.Testing; using Microsoft.Extensions.Logging.Testing;
using Moq;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Routing namespace Microsoft.AspNetCore.Routing
{ {
public class RouterMiddlewareTest public class RouterMiddlewareTest
{ {
[Fact]
public async Task RoutingFeatureSetInIRouter()
{
// Arrange
var services = new ServiceCollection();
services.AddLogging();
var httpContext = new DefaultHttpContext
{
RequestServices = services.BuildServiceProvider()
};
httpContext.Request.Path = "/foo/10";
var routeHandlerExecuted = false;
var handler = new RouteHandler(context =>
{
routeHandlerExecuted = true;
var routingFeature = context.Features.Get<IRoutingFeature>();
Assert.NotNull(routingFeature);
Assert.NotNull(context.Features.Get<IRouteValuesFeature>());
Assert.Single(routingFeature.RouteData.Values);
Assert.Single(context.Request.RouteValues);
Assert.True(routingFeature.RouteData.Values.ContainsKey("id"));
Assert.True(context.Request.RouteValues.ContainsKey("id"));
Assert.Equal("10", routingFeature.RouteData.Values["id"]);
Assert.Equal("10", context.Request.RouteValues["id"]);
Assert.Equal("10", context.GetRouteValue("id"));
Assert.Same(routingFeature.RouteData, context.GetRouteData());
return Task.CompletedTask;
});
var route = new Route(handler, "/foo/{id}", Mock.Of<IInlineConstraintResolver>());
var middleware = new RouterMiddleware(context => Task.CompletedTask, NullLoggerFactory.Instance, route);
// Act
await middleware.Invoke(httpContext);
// Assert
Assert.True(routeHandlerExecuted);
}
[Fact] [Fact]
public async Task Invoke_LogsCorrectValues_WhenNotHandled() public async Task Invoke_LogsCorrectValues_WhenNotHandled()
{ {

View File

@ -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. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System; using System;

View File

@ -173,7 +173,7 @@ namespace RoutingWebSite
app.UseRouting(); app.UseRouting();
app.UseEndpoints(endpoints => app.UseEndpoints(endpoints =>
{ {
endpoints.MapGet("api/get/{id}", (context) => context.Response.WriteAsync($"{name} - API Get {context.GetRouteData().Values["id"]}")); endpoints.MapGet("api/get/{id}", (context) => context.Response.WriteAsync($"{name} - API Get {context.Request.RouteValues["id"]}"));
}); });
} }
} }

View File

@ -286,7 +286,9 @@ namespace Microsoft.AspNetCore.Mvc.Routing
RequestDelegate requestDelegate = (context) => RequestDelegate requestDelegate = (context) =>
{ {
var routeData = context.GetRouteData(); var routeData = new RouteData();
routeData.PushState(router: null, context.Request.RouteValues, dataTokens);
var actionContext = new ActionContext(context, routeData, action); var actionContext = new ActionContext(context, routeData, action);
if (invokerFactory == null) if (invokerFactory == null)

View File

@ -60,11 +60,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidates == null) if (candidates == null)
{ {
throw new ArgumentNullException(nameof(candidates)); throw new ArgumentNullException(nameof(candidates));

View File

@ -66,12 +66,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
{ {
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidates == null) if (candidates == null)
{ {
throw new ArgumentNullException(nameof(candidates)); throw new ArgumentNullException(nameof(candidates));

View File

@ -532,7 +532,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
}; };
var candidates = CreateCandidateSet(endpoints); var candidates = CreateCandidateSet(endpoints);
var context = new EndpointSelectorContext();
var httpContext = new DefaultHttpContext() var httpContext = new DefaultHttpContext()
{ {
Request = Request =
@ -540,6 +539,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
ContentType = "application/json", ContentType = "application/json",
}, },
}; };
var context = new EndpointSelectorContext(httpContext);
var policy = CreatePolicy(); var policy = CreatePolicy();
@ -562,7 +562,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
}; };
var candidates = CreateCandidateSet(endpoints); var candidates = CreateCandidateSet(endpoints);
var context = new EndpointSelectorContext();
var httpContext = new DefaultHttpContext() var httpContext = new DefaultHttpContext()
{ {
Request = Request =
@ -570,6 +569,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
ContentType = "application/json", ContentType = "application/json",
}, },
}; };
var context = new EndpointSelectorContext(httpContext);
var policy = CreatePolicy(); var policy = CreatePolicy();
@ -592,7 +592,6 @@ namespace Microsoft.AspNetCore.Mvc.Routing
}; };
var candidates = CreateCandidateSet(endpoints); var candidates = CreateCandidateSet(endpoints);
var context = new EndpointSelectorContext();
var httpContext = new DefaultHttpContext() var httpContext = new DefaultHttpContext()
{ {
Request = Request =
@ -600,6 +599,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
ContentType = "application/json", ContentType = "application/json",
}, },
}; };
var context = new EndpointSelectorContext(httpContext);
var policy = CreatePolicy(); var policy = CreatePolicy();

View File

@ -209,14 +209,7 @@ namespace Microsoft.AspNetCore.Routing
private HttpContext CreateHttpContext(object ambientValues = null) private HttpContext CreateHttpContext(object ambientValues = null)
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Request.RouteValues = new RouteValueDictionary(ambientValues);
var feature = new EndpointSelectorContext
{
RouteValues = new RouteValueDictionary(ambientValues)
};
httpContext.Features.Set<IEndpointFeature>(feature);
httpContext.Features.Set<IRouteValuesFeature>(feature);
return httpContext; return httpContext;
} }
} }

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Endpoints;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing; using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Routing.Matching; using Microsoft.AspNetCore.Routing.Matching;
@ -59,12 +60,8 @@ namespace Microsoft.AspNetCore.Mvc.Routing
routeName: "OrdersApi"); routeName: "OrdersApi");
var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 }); var urlHelper = CreateUrlHelper(new[] { endpoint1, endpoint2 });
// Set the endpoint feature and current context just as a normal request to MVC app would be urlHelper.ActionContext.HttpContext.SetEndpoint(endpoint1);
var endpointFeature = new EndpointSelectorContext(); urlHelper.ActionContext.HttpContext.Request.RouteValues = new RouteValueDictionary
urlHelper.ActionContext.HttpContext.Features.Set<IEndpointFeature>(endpointFeature);
urlHelper.ActionContext.HttpContext.Features.Set<IRouteValuesFeature>(endpointFeature);
endpointFeature.Endpoint = endpoint1;
endpointFeature.RouteValues = new RouteValueDictionary
{ {
["controller"] = "Orders", ["controller"] = "Orders",
["action"] = "GetById", ["action"] = "GetById",
@ -149,13 +146,7 @@ namespace Microsoft.AspNetCore.Mvc.Routing
protected override IUrlHelper CreateUrlHelper(ActionContext actionContext) protected override IUrlHelper CreateUrlHelper(ActionContext actionContext)
{ {
var httpContext = actionContext.HttpContext; var httpContext = actionContext.HttpContext;
httpContext.Features.Set<IEndpointFeature>(new EndpointSelectorContext() httpContext.SetEndpoint(new Endpoint(context => Task.CompletedTask, EndpointMetadataCollection.Empty, null));
{
Endpoint = new Endpoint(
context => Task.CompletedTask,
EndpointMetadataCollection.Empty,
null)
});
var urlHelperFactory = httpContext.RequestServices.GetRequiredService<IUrlHelperFactory>(); var urlHelperFactory = httpContext.RequestServices.GetRequiredService<IUrlHelperFactory>();
var urlHelper = urlHelperFactory.GetUrlHelper(actionContext); var urlHelper = urlHelperFactory.GetUrlHelper(actionContext);

View File

@ -207,14 +207,7 @@ namespace Microsoft.AspNetCore.Routing
private HttpContext CreateHttpContext(object ambientValues = null) private HttpContext CreateHttpContext(object ambientValues = null)
{ {
var httpContext = new DefaultHttpContext(); var httpContext = new DefaultHttpContext();
httpContext.Request.RouteValues = new RouteValueDictionary(ambientValues);
var feature = new EndpointSelectorContext
{
RouteValues = new RouteValueDictionary(ambientValues)
};
httpContext.Features.Set<IEndpointFeature>(feature);
httpContext.Features.Set<IRouteValuesFeature>(feature);
return httpContext; return httpContext;
} }
} }

View File

@ -68,11 +68,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidates == null) if (candidates == null)
{ {
throw new ArgumentNullException(nameof(candidates)); throw new ArgumentNullException(nameof(candidates));

View File

@ -58,11 +58,6 @@ namespace Microsoft.AspNetCore.Mvc.RazorPages.Infrastructure
throw new ArgumentNullException(nameof(httpContext)); throw new ArgumentNullException(nameof(httpContext));
} }
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (candidates == null) if (candidates == null)
{ {
throw new ArgumentNullException(nameof(candidates)); throw new ArgumentNullException(nameof(candidates));

View File

@ -10,6 +10,7 @@ using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Server.Kestrel.Core.Features; using Microsoft.AspNetCore.Server.Kestrel.Core.Features;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal; using Microsoft.AspNetCore.Server.Kestrel.Transport.Abstractions.Internal;
@ -18,16 +19,18 @@ using Microsoft.Net.Http.Headers;
namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
internal partial class HttpProtocol : IHttpRequestFeature, internal partial class HttpProtocol : IHttpRequestFeature,
IHttpResponseFeature, IHttpResponseFeature,
IResponseBodyPipeFeature, IResponseBodyPipeFeature,
IRequestBodyPipeFeature, IRequestBodyPipeFeature,
IHttpUpgradeFeature, IHttpUpgradeFeature,
IHttpConnectionFeature, IHttpConnectionFeature,
IHttpRequestLifetimeFeature, IHttpRequestLifetimeFeature,
IHttpRequestIdentifierFeature, IHttpRequestIdentifierFeature,
IHttpBodyControlFeature, IHttpBodyControlFeature,
IHttpMaxRequestBodySizeFeature, IHttpMaxRequestBodySizeFeature,
IHttpResponseStartFeature IHttpResponseStartFeature,
IEndpointFeature,
IRouteValuesFeature
{ {
// NOTE: When feature interfaces are added to or removed from this HttpProtocol class implementation, // 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. // then the list of `implementedFeatures` in the generated code project MUST also be updated.
@ -258,6 +261,18 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
} }
} }
Endpoint IEndpointFeature.Endpoint
{
get => _endpoint;
set => _endpoint = value;
}
RouteValueDictionary IRouteValuesFeature.RouteValues
{
get => _routeValues ??= new RouteValueDictionary();
set => _routeValues = value;
}
protected void ResetHttp1Features() protected void ResetHttp1Features()
{ {
_currentIHttpMinRequestBodyDataRateFeature = this; _currentIHttpMinRequestBodyDataRateFeature = this;

View File

@ -21,6 +21,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private static readonly Type IServiceProvidersFeatureType = typeof(IServiceProvidersFeature); private static readonly Type IServiceProvidersFeatureType = typeof(IServiceProvidersFeature);
private static readonly Type IHttpRequestLifetimeFeatureType = typeof(IHttpRequestLifetimeFeature); private static readonly Type IHttpRequestLifetimeFeatureType = typeof(IHttpRequestLifetimeFeature);
private static readonly Type IHttpConnectionFeatureType = typeof(IHttpConnectionFeature); 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 IHttpAuthenticationFeatureType = typeof(IHttpAuthenticationFeature);
private static readonly Type IQueryFeatureType = typeof(IQueryFeature); private static readonly Type IQueryFeatureType = typeof(IQueryFeature);
private static readonly Type IFormFeatureType = typeof(IFormFeature); private static readonly Type IFormFeatureType = typeof(IFormFeature);
@ -47,6 +49,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private object _currentIServiceProvidersFeature; private object _currentIServiceProvidersFeature;
private object _currentIHttpRequestLifetimeFeature; private object _currentIHttpRequestLifetimeFeature;
private object _currentIHttpConnectionFeature; private object _currentIHttpConnectionFeature;
private object _currentIRouteValuesFeature;
private object _currentIEndpointFeature;
private object _currentIHttpAuthenticationFeature; private object _currentIHttpAuthenticationFeature;
private object _currentIQueryFeature; private object _currentIQueryFeature;
private object _currentIFormFeature; private object _currentIFormFeature;
@ -82,6 +86,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
_currentIHttpMaxRequestBodySizeFeature = this; _currentIHttpMaxRequestBodySizeFeature = this;
_currentIHttpBodyControlFeature = this; _currentIHttpBodyControlFeature = this;
_currentIHttpResponseStartFeature = this; _currentIHttpResponseStartFeature = this;
_currentIRouteValuesFeature = this;
_currentIEndpointFeature = this;
_currentIServiceProvidersFeature = null; _currentIServiceProvidersFeature = null;
_currentIHttpAuthenticationFeature = null; _currentIHttpAuthenticationFeature = null;
@ -183,6 +189,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
feature = _currentIHttpConnectionFeature; feature = _currentIHttpConnectionFeature;
} }
else if (key == IRouteValuesFeatureType)
{
feature = _currentIRouteValuesFeature;
}
else if (key == IEndpointFeatureType)
{
feature = _currentIEndpointFeature;
}
else if (key == IHttpAuthenticationFeatureType) else if (key == IHttpAuthenticationFeatureType)
{ {
feature = _currentIHttpAuthenticationFeature; feature = _currentIHttpAuthenticationFeature;
@ -295,6 +309,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
_currentIHttpConnectionFeature = value; _currentIHttpConnectionFeature = value;
} }
else if (key == IRouteValuesFeatureType)
{
_currentIRouteValuesFeature = value;
}
else if (key == IEndpointFeatureType)
{
_currentIEndpointFeature = value;
}
else if (key == IHttpAuthenticationFeatureType) else if (key == IHttpAuthenticationFeatureType)
{ {
_currentIHttpAuthenticationFeature = value; _currentIHttpAuthenticationFeature = value;
@ -405,6 +427,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
feature = (TFeature)_currentIHttpConnectionFeature; 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)) else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature))
{ {
feature = (TFeature)_currentIHttpAuthenticationFeature; feature = (TFeature)_currentIHttpAuthenticationFeature;
@ -521,6 +551,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
_currentIHttpConnectionFeature = feature; _currentIHttpConnectionFeature = feature;
} }
else if (typeof(TFeature) == typeof(IRouteValuesFeature))
{
_currentIRouteValuesFeature = feature;
}
else if (typeof(TFeature) == typeof(IEndpointFeature))
{
_currentIEndpointFeature = feature;
}
else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature)) else if (typeof(TFeature) == typeof(IHttpAuthenticationFeature))
{ {
_currentIHttpAuthenticationFeature = feature; _currentIHttpAuthenticationFeature = feature;
@ -629,6 +667,14 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
yield return new KeyValuePair<Type, object>(IHttpConnectionFeatureType, _currentIHttpConnectionFeature); yield return new KeyValuePair<Type, object>(IHttpConnectionFeatureType, _currentIHttpConnectionFeature);
} }
if (_currentIRouteValuesFeature != null)
{
yield return new KeyValuePair<Type, object>(IRouteValuesFeatureType, _currentIRouteValuesFeature);
}
if (_currentIEndpointFeature != null)
{
yield return new KeyValuePair<Type, object>(IEndpointFeatureType, _currentIEndpointFeature);
}
if (_currentIHttpAuthenticationFeature != null) if (_currentIHttpAuthenticationFeature != null)
{ {
yield return new KeyValuePair<Type, object>(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature); yield return new KeyValuePair<Type, object>(IHttpAuthenticationFeatureType, _currentIHttpAuthenticationFeature);

View File

@ -17,6 +17,7 @@ using Microsoft.AspNetCore.Hosting.Server;
using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Http.Features;
using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Internal;
using Microsoft.AspNetCore.Routing;
using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure; using Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Infrastructure;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Primitives; using Microsoft.Extensions.Primitives;
@ -64,6 +65,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
private readonly HttpConnectionContext _context; private readonly HttpConnectionContext _context;
private DefaultHttpContext _httpContext; private DefaultHttpContext _httpContext;
private RouteValueDictionary _routeValues;
private Endpoint _endpoint;
protected string _methodText = null; protected string _methodText = null;
private string _scheme = null; private string _scheme = null;
@ -326,6 +329,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
{ {
_onStarting?.Clear(); _onStarting?.Clear();
_onCompleted?.Clear(); _onCompleted?.Clear();
_routeValues?.Clear();
_requestProcessingStatus = RequestProcessingStatus.RequestPending; _requestProcessingStatus = RequestProcessingStatus.RequestPending;
_autoChunk = false; _autoChunk = false;
@ -340,6 +344,7 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Internal.Http
TraceIdentifier = null; TraceIdentifier = null;
Method = HttpMethod.None; Method = HttpMethod.None;
_methodText = null; _methodText = null;
_endpoint = null;
PathBase = null; PathBase = null;
Path = null; Path = null;
RawTarget = null; RawTarget = null;

View File

@ -127,6 +127,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_collection[typeof(IHttpMinResponseDataRateFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpMinResponseDataRateFeature)] = CreateHttp1Connection();
_collection[typeof(IHttpBodyControlFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpBodyControlFeature)] = CreateHttp1Connection();
_collection[typeof(IHttpResponseStartFeature)] = CreateHttp1Connection(); _collection[typeof(IHttpResponseStartFeature)] = CreateHttp1Connection();
_collection[typeof(IRouteValuesFeature)] = CreateHttp1Connection();
_collection[typeof(IEndpointFeature)] = CreateHttp1Connection();
CompareGenericGetterToIndexer(); CompareGenericGetterToIndexer();
@ -148,6 +150,8 @@ namespace Microsoft.AspNetCore.Server.Kestrel.Core.Tests
_collection.Set<IHttpMinResponseDataRateFeature>(CreateHttp1Connection()); _collection.Set<IHttpMinResponseDataRateFeature>(CreateHttp1Connection());
_collection.Set<IHttpBodyControlFeature>(CreateHttp1Connection()); _collection.Set<IHttpBodyControlFeature>(CreateHttp1Connection());
_collection.Set<IHttpResponseStartFeature>(CreateHttp1Connection()); _collection.Set<IHttpResponseStartFeature>(CreateHttp1Connection());
_collection.Set<IRouteValuesFeature>(CreateHttp1Connection());
_collection.Set<IEndpointFeature>(CreateHttp1Connection());
CompareGenericGetterToIndexer(); CompareGenericGetterToIndexer();

View File

@ -19,6 +19,8 @@ namespace CodeGenerator
"IServiceProvidersFeature", "IServiceProvidersFeature",
"IHttpRequestLifetimeFeature", "IHttpRequestLifetimeFeature",
"IHttpConnectionFeature", "IHttpConnectionFeature",
"IRouteValuesFeature",
"IEndpointFeature"
}; };
var commonFeatures = new[] var commonFeatures = new[]
@ -70,7 +72,9 @@ namespace CodeGenerator
"IHttpConnectionFeature", "IHttpConnectionFeature",
"IHttpMaxRequestBodySizeFeature", "IHttpMaxRequestBodySizeFeature",
"IHttpBodyControlFeature", "IHttpBodyControlFeature",
"IHttpResponseStartFeature" "IHttpResponseStartFeature",
"IRouteValuesFeature",
"IEndpointFeature"
}; };
var usings = $@" var usings = $@"