asp.Netcore 3.1 Routing(二)
52399 ワード
EndpointRouteの新しいルーティングスキーム
EndpointRoutingMiddlewareはまずHttpContextからEndpointを取得し、nullでない場合は次のミドルウェアの呼び出しを続行します.
見つからない場合は、DfaMatcherクラスのMatchAsyncを呼び出して対応するEndpointを見つけ、HttpContextを設定します.
具体的にどのように対応するEndpointを探し当てて、ここで詳しく言いません
Endpointを見て
EndpointMiddlewareミドルウェアは、HttpContextのEndPointを取得し、EndpointのRequestDelegate処理要求を呼び出す
public static class EndpointRoutingApplicationBuilderExtensions { private const string EndpointRouteBuilder = "__EndpointRouteBuilder"; ///
/// Adds a
middleware to the specified . /// /// The to add the middleware to. /// A reference to this instance after the operation has completed. /// /// /// A call to must be followed by a call to /// for the same /// instance. /// /// /// The defines a point in the middleware pipeline where routing decisions are /// made, and an is associated with the . The /// defines a point in the middleware pipeline where the current is executed. Middleware between /// the and may observe or change the /// associated with the . /// /// public static IApplicationBuilder UseRouting(this IApplicationBuilder builder) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } VerifyRoutingServicesAreRegistered(builder); var endpointRouteBuilder = new DefaultEndpointRouteBuilder(builder); builder.Properties[EndpointRouteBuilder] = endpointRouteBuilder; return builder.UseMiddleware (endpointRouteBuilder); } /// /// Adds a
middleware to the specified /// with the instances built from configured . /// The will execute the associated with the current /// request. /// /// The to add the middleware to. /// An to configure the provided . /// A reference to this instance after the operation has completed. /// /// /// A call to must be preceded by a call to /// for the same /// instance. /// /// /// The defines a point in the middleware pipeline where routing decisions are /// made, and an is associated with the . The /// defines a point in the middleware pipeline where the current is executed. Middleware between /// the and may observe or change the /// associated with the . /// /// public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action configure) { if (builder == null) { throw new ArgumentNullException(nameof(builder)); } if (configure == null) { throw new ArgumentNullException(nameof(configure)); } VerifyRoutingServicesAreRegistered(builder); VerifyEndpointRoutingMiddlewareIsRegistered(builder, out var endpointRouteBuilder); configure(endpointRouteBuilder); // Yes, this mutates an IOptions. We're registering data sources in a global collection which // can be used for discovery of endpoints or URL generation. // // Each middleware gets its own collection of data sources, and all of those data sources also // get added to a global collection. var routeOptions = builder.ApplicationServices.GetRequiredService >(); foreach (var dataSource in endpointRouteBuilder.DataSources) { routeOptions.Value.EndpointDataSources.Add(dataSource); } return builder.UseMiddleware (); } private static void VerifyRoutingServicesAreRegistered(IApplicationBuilder app) { // Verify if AddRouting was done before calling UseEndpointRouting/UseEndpoint // We use the RoutingMarkerService to make sure if all the services were added. if (app.ApplicationServices.GetService(typeof(RoutingMarkerService)) == null) { throw new InvalidOperationException(Resources.FormatUnableToFindServices( nameof(IServiceCollection), nameof(RoutingServiceCollectionExtensions.AddRouting), "ConfigureServices(...)")); } } private static void VerifyEndpointRoutingMiddlewareIsRegistered(IApplicationBuilder app, out DefaultEndpointRouteBuilder endpointRouteBuilder) { if (!app.Properties.TryGetValue(EndpointRouteBuilder, out var obj)) { var message = $"{nameof(EndpointRoutingMiddleware)} matches endpoints setup by {nameof(EndpointMiddleware)} and so must be added to the request " + $"execution pipeline before {nameof(EndpointMiddleware)}. " + $"Please add {nameof(EndpointRoutingMiddleware)} by calling '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' inside the call " + $"to 'Configure(...)' in the application startup code."; throw new InvalidOperationException(message); } // If someone messes with this, just let it crash. endpointRouteBuilder = (DefaultEndpointRouteBuilder)obj; // This check handles the case where Map or something else that forks the pipeline is called between the two // routing middleware. if (!object.ReferenceEquals(app, endpointRouteBuilder.ApplicationBuilder)) { var message = $"The {nameof(EndpointRoutingMiddleware)} and {nameof(EndpointMiddleware)} must be added to the same {nameof(IApplicationBuilder)} instance. " + $"To use Endpoint Routing with 'Map(...)', make sure to call '{nameof(IApplicationBuilder)}.{nameof(UseRouting)}' before " + $"'{nameof(IApplicationBuilder)}.{nameof(UseEndpoints)}' for each branch of the middleware pipeline."; throw new InvalidOperationException(message); } } }
这里涉及到两个中间件EndpointRoutingMiddleware和EndpointMiddleware
internal sealed class EndpointRoutingMiddleware
{
private const string DiagnosticsEndpointMatchedKey = "Microsoft.AspNetCore.Routing.EndpointMatched";
private readonly MatcherFactory _matcherFactory;
private readonly ILogger _logger;
private readonly EndpointDataSource _endpointDataSource;
private readonly DiagnosticListener _diagnosticListener;
private readonly RequestDelegate _next;
private Task _initializationTask;
public EndpointRoutingMiddleware(
MatcherFactory matcherFactory,
ILogger logger,
IEndpointRouteBuilder endpointRouteBuilder,
DiagnosticListener diagnosticListener,
RequestDelegate next)
{
if (endpointRouteBuilder == null)
{
throw new ArgumentNullException(nameof(endpointRouteBuilder));
}
_matcherFactory = matcherFactory ?? throw new ArgumentNullException(nameof(matcherFactory));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_diagnosticListener = diagnosticListener ?? throw new ArgumentNullException(nameof(diagnosticListener));
_next = next ?? throw new ArgumentNullException(nameof(next));
_endpointDataSource = new CompositeEndpointDataSource(endpointRouteBuilder.DataSources);
}
public Task Invoke(HttpContext httpContext)
{
// There's already an endpoint, skip maching completely
var endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
Log.MatchSkipped(_logger, endpoint);
return _next(httpContext);
}
// 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.
var matcherTask = InitializeAsync();
if (!matcherTask.IsCompletedSuccessfully)
{
return AwaitMatcher(this, httpContext, matcherTask);
}
var matchTask = matcherTask.Result.MatchAsync(httpContext);
if (!matchTask.IsCompletedSuccessfully)
{
return AwaitMatch(this, httpContext, matchTask);
}
return SetRoutingAndContinue(httpContext);
// Awaited fallbacks for when the Tasks do not synchronously complete
static async Task AwaitMatcher(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matcherTask)
{
var matcher = await matcherTask;
await matcher.MatchAsync(httpContext);
await middleware.SetRoutingAndContinue(httpContext);
}
static async Task AwaitMatch(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task matchTask)
{
await matchTask;
await middleware.SetRoutingAndContinue(httpContext);
}
}
[MethodImpl(MethodImplOptions.AggressiveInlining)]
private Task SetRoutingAndContinue(HttpContext httpContext)
{
// If there was no mutation of the endpoint then log failure
var endpoint = httpContext.GetEndpoint();
if (endpoint == null)
{
Log.MatchFailure(_logger);
}
else
{
// Raise an event if the route matched
if (_diagnosticListener.IsEnabled() && _diagnosticListener.IsEnabled(DiagnosticsEndpointMatchedKey))
{
// We're just going to send the HttpContext since it has all of the relevant information
_diagnosticListener.Write(DiagnosticsEndpointMatchedKey, httpContext);
}
Log.MatchSuccess(_logger, endpoint);
}
return _next(httpContext);
}
// Initialization is async to avoid blocking threads while reflection and things
// of that nature take place.
//
// We've seen cases where startup is very slow if we allow multiple threads to race
// while initializing the set of endpoints/routes. Doing CPU intensive work is a
// blocking operation if you have a low core count and enough work to do.
private Task InitializeAsync()
{
var initializationTask = _initializationTask;
if (initializationTask != null)
{
return initializationTask;
}
return InitializeCoreAsync();
}
private Task InitializeCoreAsync()
{
var initialization = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var initializationTask = Interlocked.CompareExchange(ref _initializationTask, initialization.Task, null);
if (initializationTask != null)
{
// This thread lost the race, join the existing task.
return initializationTask;
}
// This thread won the race, do the initialization.
try
{
var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
// Now replace the initialization task with one created with the default execution context.
// This is important because capturing the execution context will leak memory in ASP.NET Core.
using (ExecutionContext.SuppressFlow())
{
_initializationTask = Task.FromResult(matcher);
}
// Complete the task, this will unblock any requests that came in while initializing.
initialization.SetResult(matcher);
return initialization.Task;
}
catch (Exception ex)
{
// Allow initialization to occur again. Since DataSources can change, it's possible
// for the developer to correct the data causing the failure.
_initializationTask = null;
// Complete the task, this will throw for any requests that came in while initializing.
initialization.SetException(ex);
return initialization.Task;
}
}
private static class Log
{
private static readonly Actionstring, Exception> _matchSuccess = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(1, "MatchSuccess"),
"Request matched endpoint '{EndpointName}'");
private static readonly Action _matchFailure = LoggerMessage.Define(
LogLevel.Debug,
new EventId(2, "MatchFailure"),
"Request did not match any endpoints");
private static readonly Actionstring, Exception> _matchingSkipped = LoggerMessage.Define<string>(
LogLevel.Debug,
new EventId(3, "MatchingSkipped"),
"Endpoint '{EndpointName}' already set, skipping route matching.");
public static void MatchSuccess(ILogger logger, Endpoint endpoint)
{
_matchSuccess(logger, endpoint.DisplayName, null);
}
public static void MatchFailure(ILogger logger)
{
_matchFailure(logger, null);
}
public static void MatchSkipped(ILogger logger, Endpoint endpoint)
{
_matchingSkipped(logger, endpoint.DisplayName, null);
}
}
}
EndpointRoutingMiddlewareはまずHttpContextからEndpointを取得し、nullでない場合は次のミドルウェアの呼び出しを続行します.
見つからない場合は、DfaMatcherクラスのMatchAsyncを呼び出して対応するEndpointを見つけ、HttpContextを設定します.
具体的にどのように対応するEndpointを探し当てて、ここで詳しく言いません
Endpointを見て
public class Endpoint { ///
/// Creates a new instance of
. /// /// The delegate used to process requests for the endpoint. /// /// The endpoint . May be null. /// /// /// The informational display name of the endpoint. May be null. /// public Endpoint( RequestDelegate requestDelegate, EndpointMetadataCollection metadata, string displayName) { // All are allowed to be null RequestDelegate = requestDelegate; Metadata = metadata ?? EndpointMetadataCollection.Empty; DisplayName = displayName; } /// /// Gets the informational display name of this endpoint. /// public string DisplayName { get; } ///
/// Gets the collection of metadata associated with this endpoint. /// public EndpointMetadataCollection Metadata { get; } ///
/// Gets the delegate used to process requests for the endpoint. /// public RequestDelegate RequestDelegate { get; } public override string ToString() => DisplayName ?? base.ToString(); }
其中RequestDelegate是处理真个http请求
EndpointRoutingMiddleware中间件的主要作用就是设置HttpContext的IEndpointFeature属性
internal sealed class EndpointMiddleware
{
internal const string AuthorizationMiddlewareInvokedKey = "__AuthorizationMiddlewareWithEndpointInvoked";
internal const string CorsMiddlewareInvokedKey = "__CorsMiddlewareWithEndpointInvoked";
private readonly ILogger _logger;
private readonly RequestDelegate _next;
private readonly RouteOptions _routeOptions;
public EndpointMiddleware(
ILogger logger,
RequestDelegate next,
IOptions routeOptions)
{
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
_next = next ?? throw new ArgumentNullException(nameof(next));
_routeOptions = routeOptions?.Value ?? throw new ArgumentNullException(nameof(routeOptions));
}
public Task Invoke(HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
if (endpoint?.RequestDelegate != null)
{
if (!_routeOptions.SuppressCheckForUnhandledSecurityMetadata)
{
if (endpoint.Metadata.GetMetadata() != null &&
!httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
{
ThrowMissingAuthMiddlewareException(endpoint);
}
if (endpoint.Metadata.GetMetadata() != null &&
!httpContext.Items.ContainsKey(CorsMiddlewareInvokedKey))
{
ThrowMissingCorsMiddlewareException(endpoint);
}
}
Log.ExecutingEndpoint(_logger, endpoint);
try
{
var requestTask = endpoint.RequestDelegate(httpContext);
if (!requestTask.IsCompletedSuccessfully)
{
return AwaitRequestTask(endpoint, requestTask, _logger);
}
}
catch (Exception exception)
{
Log.ExecutedEndpoint(_logger, endpoint);
return Task.FromException(exception);
}
Log.ExecutedEndpoint(_logger, endpoint);
return Task.CompletedTask;
}
return _next(httpContext);
static async Task AwaitRequestTask(Endpoint endpoint, Task requestTask, ILogger logger)
{
try
{
await requestTask;
}
finally
{
Log.ExecutedEndpoint(logger, endpoint);
}
}
}
private static void ThrowMissingAuthMiddlewareException(Endpoint endpoint)
{
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains authorization metadata, " +
"but a middleware was not found that supports authorization." +
Environment.NewLine +
"Configure your application startup by adding app.UseAuthorization() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).");
}
private static void ThrowMissingCorsMiddlewareException(Endpoint endpoint)
{
throw new InvalidOperationException($"Endpoint {endpoint.DisplayName} contains CORS metadata, " +
"but a middleware was not found that supports CORS." +
Environment.NewLine +
"Configure your application startup by adding app.UseCors() inside the call to Configure(..) in the application startup code. The call to app.UseAuthorization() must appear between app.UseRouting() and app.UseEndpoints(...).");
}
private static class Log
{
private static readonly Actionstring, Exception> _executingEndpoint = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(0, "ExecutingEndpoint"),
"Executing endpoint '{EndpointName}'");
private static readonly Actionstring, Exception> _executedEndpoint = LoggerMessage.Define<string>(
LogLevel.Information,
new EventId(1, "ExecutedEndpoint"),
"Executed endpoint '{EndpointName}'");
public static void ExecutingEndpoint(ILogger logger, Endpoint endpoint)
{
_executingEndpoint(logger, endpoint.DisplayName, null);
}
public static void ExecutedEndpoint(ILogger logger, Endpoint endpoint)
{
_executedEndpoint(logger, endpoint.DisplayName, null);
}
}
}
EndpointMiddlewareミドルウェアは、HttpContextのEndPointを取得し、EndpointのRequestDelegate処理要求を呼び出す