乘风原创程序

  • 详解ASP.NET Core端点路由的作用原理
  • 2020/7/13 11:13:17
  • 端点路由(Endpoint Routing)最早出现在ASP.NET Core2.2,在ASP.NET Core3.0提升为一等公民。

    Endpoint Routing的动机

    在端点路由出现之前,我们一般在请求处理管道的末尾,定义MVC中间件解析路由。这种方式意味着在处理管道中,MVC中间件之前的中间件将无法获得路由信息。

    路由信息对于某些中间件非常有用,比如CORS、认证中间件(认证过程可能会用到路由信息)。

    同时端点路由提炼出端点概念,解耦路由匹配逻辑、请求分发。

    Endpoint Routing中间件

    由一对中间件组成:

    UseRouting 将路由匹配添加到中间件管道。该中间件查看应用程序中定义的端点集合,并根据请求选择最佳匹配。UseEndpoints 将端点执行添加到中间件管道。

    MapGet、MapPost等方法将 处理逻辑连接到路由系统;

    其他方法将 ASP.NET Core框架特性连接到路由系统。

    • MapRazorPages for Razor Pages
    • MapControllers for controllers
    • MapHub< THub> for SignalR
    • MapGrpcService< TService> for gRPC

    处于这对中间件上游的 中间件: 始终无法感知 Endpoint;
    处于这对中间件之间的 中间件,将会感知到Endpoint,并有能力执行附加处理逻辑;
    UseEndpoint是一个终点中间件;
    没有匹配,则进入UseEndpoint之后的中间件。


    放置在UseRoutingUseEndpoints之间的认证授权中间件可以:
    感知被匹配的端点信息;在调度到Endpoint之前,应用授权策略。

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
      if (env.IsDevelopment())
      {
        app.UseDeveloperExceptionPage();
      }
    
      // Matches request to an endpoint.
      app.UseRouting();
    
      // Endpoint aware middleware. 
      // Middleware can use metadata from the matched endpoint.
      app.UseAuthentication();
      app.UseAuthorization();
    
      // Execute the matched endpoint.
      app.UseEndpoints(endpoints =>
      {
        // Configure the Health Check endpoint and require an authorized user.
        endpoints.MapHealthChecks("/healthz").RequireAuthorization();
    
        // Configure another endpoint, no authorization requirements.
        endpoints.MapGet("/", async context =>
        {
          await context.Response.WriteAsync("Hello World!");
        });
      });
    }

    以上在/health定义了健康检查,该端点定义了IAuthorizeDatametadata,要求先认证再执行健康检查。

    我们在UseRouting、UseEndpoints之间添加一点口水代码:感知端点:

      app.Use(next => context =>
          {
            var endpoint = context.GetEndpoint();
            if (endpoint is null)
            {
              return Task.CompletedTask;
            }
            Console.WriteLine($"Endpoint: {endpoint.DisplayName}");
    
            if (endpoint is RouteEndpoint routeEndpoint)
            {
              Console.WriteLine("Endpoint has route pattern: " +
                routeEndpoint.RoutePattern.RawText);
            }
    
            foreach (var metadata in endpoint.Metadata)
            {
              Console.WriteLine($"Endpoint has metadata: {metadata}");
            }
            return next(context);
          });

    当请求/healthz时,感知到AuthorizeAttribute metadata

    故猜想认证授权中间件要对/healthz起作用,必然会对这个 AuthorizeAttribute metadata有所反应。

    于是翻阅GithubAuthorizationMiddleware3.0源码:发现确实关注了Endpoint

    // ---- 截取自https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs-----
    if (endpoint != null)
    {
      context.Items[AuthorizationMiddlewareInvokedWithEndpointKey] = AuthorizationMiddlewareWithEndpointInvokedValue;
    }
    var authorizeData = endpoint?.Metadata.GetOrderedMetadata<IAuthorizeData>() ?? Array.Empty<IAuthorizeData>();
    var policy = await AuthorizationPolicy.CombineAsync(_policyProvider, authorizeData);
    if (policy == null)
    {
       await _next(context);
       return;
    }
    var policyEvaluator = context.RequestServices.GetRequiredService<IPolicyEvaluator>();
    ......

    AuthorizeAttribute确实是实现了IAuthorizeData接口。

    binggo, 猜想得到源码验证。

    结论

    端点路由:允许ASP.NET Core应用程序在中间件管道的早期确定要调度的端点,
    以便后续中间件可以使用该信息来提供当前管道配置无法提供的功能。

    这使ASP.NET Core框架更加灵活,强化端点概念,它使路由匹配和解析功能与终结点分发功能脱钩。

    https://github.com/dotnet/aspnetcore/blob/master/src/Security/Authorization/Policy/src/AuthorizationMiddleware.cs