环境:
- .netcore 3.1.1.0
- vs2019 16.4.5
探索问题:
- http请求到来时,aspnetcore框架是怎样找到对应的action的?
- 路由模块
Routing
向http请求管道中注册了几个中间件? - mvc模块有没有向http管道注册中间件?
useRouting()
和UseEndpoints()
这两个方法有什么区别,路由模块中为什么要分别注册这两个中间件?UseAuthorization()
为什么要写在useRouting()
和UseEndpoints()
之间?
一、终结点EndPoint
EndPoint是路由匹配的目标,当http请求到来时,路由模块就会将请求匹配到某个EndPoint。EndPoint这个类封装了action的信息,比如:Controller类型、Action方法、[Attribute]情况等。在程序启动的时候,“mvc”模块就已经将所有的action转化成了Endpoint并交给“路由”模块去管理了,它的源码如下:
namespace Microsoft.AspNetCore.Http
{
public class Endpoint
{
public Endpoint(
RequestDelegate requestDelegate,
EndpointMetadataCollection metadata,
string displayName)
{
RequestDelegate = requestDelegate;
Metadata = metadata ?? EndpointMetadataCollection.Empty;
DisplayName = displayName;
}
public string DisplayName { get; }
public EndpointMetadataCollection Metadata { get; }
public RequestDelegate RequestDelegate { get; }
public override string ToString() => DisplayName ?? base.ToString();
}
}
二、终结点工作流程概述
“路由”模块一前一后注册了两个中间件,第一个中间件(EndpointRoutingMiddleware)匹配到了EndPoint,第二个中间件(EndpointMiddleware)去调用已经匹配到了的EndPoint。“授权”模块会在这两个中间件之间注册一个中间件,所以授权模块在进行授权的时候可以明确当前的请求是指向哪个Action的。
三、整体代码执行流程(以webapi项目为例)
下面以webapi项目为例,讲解从项目的启动到http请求处理的核心环节,首先看下参照的startup.cs中简化的代码:
public void ConfigureServices(IServiceCollection services)
{
services.AddControllers();
}
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints => endpoints.MapControllers());
}
这些代码的执行步骤如下:
程序启动阶段:
- 第一步:执行
services.AddControllers()
将Controller的核心服务注册到容器中去 - 第二步:执行
app.UseRouting()
将EndpointRoutingMiddleware中间件注册到http管道中 - 第三步:执行
app.UseAuthorization()
将AuthorizationMiddleware中间件注册到http管道中 - 第四步:执行
app.UseEndpoints(encpoints=>endpoints.MapControllers())
有两个主要的作用:- 调用
endpoints.MapControllers()
将本程序集定义的所有Controller和Action转换为一个个的EndPoint并放到路由中间件的配置对象RouteOptions中 - 将EndpointMiddleware中间件注册到http管道中
- 调用
当Http请求到来时
- 第五步:收到一条http请求,此时EndpointRoutingMiddleware为它匹配一个Endpoint,并放到HttpContext中去
- 第六步:授权中间件进行拦截,根据Endpoint的信息对这个请求进行授权验证。
- 第七步:EndpointMiddleware中间件执行Endpoint中的RequestDelegate逻辑,即执行Controller的Action
四、aspnetcore各模块之间关系
上面几个步骤中主要涉及到“路由”、“授权”、“mvc”等几个模块。
- “授权” 模块:
Authorization
“授权”模块注册了一个中间件进行授权拦截,拦截的对象是路由模块已匹配到的Endpoint。 - “路由” 模块:
Routing
“路由”模块前后注册了两个中间件,第一个用来寻找匹配的终结点,并将这个终结点放到HttpContext中,这之后其他的中间件可以针对匹配的结果做一些事情(比如:授权中间件),第二个用来执行终结点,也就是调用Controller中的action方法。 - “mvc” 模块:
包括controllers、views和pages等小模块
“mvc”模块并没有注册中间件,它的工作依赖于“路由”模块,在程序启动的时候将扫描所有的action并把他们封装成EndPoint交给“路由”模块,当http请求到来的时候就会被“路由”模块自动匹配和调用执行。
五、“路由”模块的两个中间件
说明:
在程序启动时每个action方法都会被封装成Endpoint对象交给路由模块,这样当http请求到来时,路由模块才会匹配到正确的Endpoint,进而找到Controller和Action!
“路由”模块注册中间件的源码如下(EndpointRoutingApplicationBuilderExtensions.cs
):
namespace Microsoft.AspNetCore.Builder
{
public static class EndpointRoutingApplicationBuilderExtensions
{
private const string EndpointRouteBuilder = "__EndpointRouteBuilder";
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<EndpointRoutingMiddleware>(endpointRouteBuilder);
}
public static IApplicationBuilder UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> 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);
var routeOptions = builder.ApplicationServices.GetRequiredService<IOptions<RouteOptions>>();
foreach (var dataSource in endpointRouteBuilder.DataSources)
{
routeOptions.Value.EndpointDataSources.Add(dataSource);
}
return builder.UseMiddleware<EndpointMiddleware>();
}
......
}
}
从上面的源码中可以看到:
UseRouting()
这个方法很简单就是注册了EndpointRoutingMiddleware
中间件;UseEndpoints()
这个方法是先生成一个EndpointRouteBuilder,然后调用我们的代码endpoints.MapControllers()
将所有的action都包装成Endpoint放进了EndpointRouteBuilder的DataSources属性中,然后又将这些Endpoint放进了RouteOptions
的EndpointDataSources
属性中,最后注册了EndpointMiddleware
中间件。至于action是怎么被封装成
Endpoint
的这里不做介绍(参考:.netcore入门13:aspnetcore源码之如何在程序启动时将Controller里的Action自动扫描封装成Endpoint),我们现在只要知道“路由”模块在程序启动的时候就已经将所有的action转化成了Endpoint以进行管理,并且注册了两个中间件。
5.1 EndpointRoutingMiddleware
中间件:
这个中间件的主要作用是寻找匹配的Endpoint,我们直接看它的源码:
namespace Microsoft.AspNetCore.Routing
{
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<Matcher> _initializationTask;
public EndpointRoutingMiddleware(
MatcherFactory matcherFactory,
ILogger<EndpointRoutingMiddleware> 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)
{
var endpoint = httpContext.GetEndpoint();
if (endpoint != null)
{
Log.MatchSkipped(_logger, endpoint);
return _next(httpContext);
}
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);
static async Task AwaitMatcher(EndpointRoutingMiddleware middleware, HttpContext httpContext, Task<Matcher> 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);
}
}
private Task SetRoutingAndContinue(HttpContext httpContext)
{
var endpoint = httpContext.GetEndpoint();
if (endpoint == null)
{
Log.MatchFailure(_logger);
}
else
{
......
Log.MatchSuccess(_logger, endpoint);
}
return _next(httpContext);
}
private Task<Matcher> InitializeAsync()
{
......
return InitializeCoreAsync();
}
private Task<Matcher> InitializeCoreAsync()
{
var initialization = new TaskCompletionSource<Matcher>(TaskCreationOptions.RunContinuationsAsynchronously);
......
try
{
var matcher = _matcherFactory.CreateMatcher(_endpointDataSource);
......
initialization.SetResult(matcher);
return initialization.Task;
}
catch (Exception ex)
{
......
}
}
......
}
}
我们从它的源码中可以看到,EndpointRoutingMiddleware
中间件先是创建matcher,然后调用matcher.MatchAsync(httpContext)
去匹配Endpoint(匹配到的结果自然就放在了HttpContext中),最后通过httpContext.GetEndpoint()
验证了是否已经匹配到了正确的Endpoint并交给下个中间件继续执行!注意,在这里的代码中发现了方法中嵌套定义方法,这个是c#新的语法,可参考本地函数(C# 编程指南),不是语法错误。
下面来看看EndpointMiddleware
中间件的源码:
5.2 EndpointMiddleware
中间件:
namespace Microsoft.AspNetCore.Routing
{
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<EndpointMiddleware> logger,
RequestDelegate next,
IOptions<RouteOptions> 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<IAuthorizeData>() != null &&
!httpContext.Items.ContainsKey(AuthorizationMiddlewareInvokedKey))
{
ThrowMissingAuthMiddlewareException(endpoint);
}
if (endpoint.Metadata.GetMetadata<ICorsMetadata>() != 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);
}
}
}
......
}
}
从上面的模块中我们可以看到, EndpointMiddleware
中间件先是从HttpContext中取出已经匹配了的Endpoint,如果Endpoint的RequestDelegate不为空的话就执行这个RequestDelegate,而这个RequestDelegate代表的是Controller的某个Action!