前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >ASP.NET Core Middleware抽丝剥茧

ASP.NET Core Middleware抽丝剥茧

作者头像
有态度的马甲
发布2021-01-20 16:06:52
5110
发布2021-01-20 16:06:52
举报
文章被收录于专栏:精益码农精益码农

一. 中间件的概念和数据结构

ASP.NET Core Middleware是在ASP.NET Core处理管道中处理特定业务逻辑的组件。

ASP.NET Core处理管道由一系列请求委托组成,一环接一环的调用特定的中间件。

上图示例: 处理管道包含四个中间件,每个中间件都包含后续中间件执行动作的引用(next),同时每个中间件在交棒之前和交棒之后可以自行参与针对HttpContxt的业务处理。

通过上面的分析,中间件其实具备两个特征:

  • 入参:下一个中间件的执行委托RequestDelegate (public delegate Task RequestDelegate(HttpContext context);)
  • 输出:特定中间件的业务处理动作:因为中间件是处理管道中预设的处理逻辑,所以这个动作其实也是一个委托RequestDelegate

所以.NET Core用Func<RequestDelegate,RequestDelegate> 数据结构表示中间件是合理的。

二. Middleware的定义方式

ASP.NETCore 提供了很多内置的中间件,帮助我们完成基础通用的业务逻辑。

有两种自定义中间件的方式:

1. Factory-based Middleware

基于工厂模式的中间件有如下优点:

  • 在每个客户端请求时激活实例 (injection of scoped services)
  • 实现IMiddleware接口: 强类型
代码语言:javascript
复制
//  该接口只有一个固定的函数
public Task InvokeAsync(HttpContext context,RequestDelegate next);
代码语言:javascript
复制
  public class FactoryActivatedMiddleware : IMiddleware
    {
        private readonly ILogger _logger;

        public FactoryActivatedMiddleware(ILoggerFactory logger)   // 
        {
            _logger = logger.CreateLogger<FactoryActivatedMiddleware>();

        }
        public async Task InvokeAsync(HttpContext context, RequestDelegate next)
        {
            // TODO  logic handler
            _logger.LogInformation("测试");
            await next(context);
            // TODO  logic handler
        }
    }

使用工厂模式的中间件,构造函数参数由依赖注入(DI)填充;

在[使用UseMiddleware()注册中间件]时不允许显式传参。

源码在https://github.com/dotnet/aspnetcore/blob/v5.0.1/src/Http/Http.Abstractions/src/Extensions/UseMiddlewareExtensions.cs第56行。

2. Conventional-based Middleware

顾名思义,基于约定的中间件类有一些特定约定:

  • 具有RequestDelegate类型参数的公共构造函数
  • 名称为Invoke或InvokeAsync的公共方法, 此方法必须 ①返回Task ② 方法第一个参数是HttpContext
代码语言:javascript
复制
public class ConventionalMiddleware
{
    private readonly RequestDelegate _next;

    public ConventionalMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context, AppDbContext db)
    {
        var keyValue = context.Request.Query["key"];

        if (!string.IsNullOrWhiteSpace(keyValue))
        {
            db.Add(new Request()
                {
                    DT = DateTime.UtcNow, 
                    MiddlewareActivation = "ConventionalMiddleware", 
                    Value = keyValue
                });

            await db.SaveChangesAsync();
        }

        await _next(context);
    }
}

构造函数和Invoke/InvokeAsync的其他参数由依赖注入(DI)填充; 基于约定的中间件,在[使用UseMiddleware()注册中间件]时允许显式传参。

三. 注册中间件的算法分析

app.UseMiddleware<TMiddleware>()内部使用app.Use(Func<RequestDelegate,RequestDelegate> middleware)注册中间件, 返回值还是IApplicationBuilder, 故具备链式注册的能力。

算法类似于基础链表, 只是指针指向的不是节点,而是后续节点的字段。

代码语言:javascript
复制
//--------节选自 Microsoft.AspNetCore.Builder.Internal.ApplicationBuilder--------
private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>();
 
public IApplicationBuilder Use(Func<RequestDelegate,RequestDelegate> middleware)
{
    this._components.Add(middleware);
    return this;
}
 
public RequestDelegate Build()
{
        RequestDelegate app = context =>
            {
                // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.
                // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.
                var endpoint = context.GetEndpoint();
                var endpointRequestDelegate = endpoint?.RequestDelegate;
                if (endpointRequestDelegate != null)
                {
                    var message =
                        $"The request reached the end of the pipeline without executing the endpoint: '{endpoint!.DisplayName}'. " +
                        $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +
                        $"routing.";
                    throw new InvalidOperationException(message);
                }

                context.Response.StatusCode = StatusCodes.Status404NotFound;
                return Task.CompletedTask;
            };

            foreach (var component in _components.Reverse())
            {
                app = component(app);
            }

            return app;
} 

通过以上代码我们可以看出:

  • 注册中间件的过程实际上,是给一个Type为List<Func<RequestDelegate, RequestDelegate>> 的集合依次添加元素的过程;
  • 中间件的数据结构:(input)后置中间件的执行函数指针(以委托RequestDelegate表示),(output)当前中间件的执行函数指针(以委托RequestDelegate表示);
  • 通过build方法将集合打通为链表,链表就是我们常说的[处理管道]。

build方法是在web托管服务GenericWebHostService开始启动的时候被调用。源码在https://github.com/dotnet/aspnetcore/blob/master/src/Hosting/Hosting/src/GenericHost/GenericWebHostedService.cs 105 行。

附:非标准中间件的用法

短路中间件、 分叉中间件、条件中间件

整个处理管道的形成,存在一些管道分叉或者临时插入中间件的行为,一些重要方法可供使用

  • Use方法是一个注册中间件的简便写法
  • Run方法是一个约定,一些中间件使用Run方法来完成管道的结尾
  • Map扩展方法:Path满足指定条件,将会执行分叉管道
  • MapWhen方法:HttpContext满足条件,将会执行分叉管道,相比Map有更灵活的匹配功能
  • UseWhen方法:HttpContext满足条件则插入中间件
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2021-01-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 精益码农 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一. 中间件的概念和数据结构
  • 二. Middleware的定义方式
    • 1. Factory-based Middleware
      • 2. Conventional-based Middleware
      • 三. 注册中间件的算法分析
        • 附:非标准中间件的用法
        相关产品与服务
        消息队列 TDMQ
        消息队列 TDMQ (Tencent Distributed Message Queue)是腾讯基于 Apache Pulsar 自研的一个云原生消息中间件系列,其中包含兼容Pulsar、RabbitMQ、RocketMQ 等协议的消息队列子产品,得益于其底层计算与存储分离的架构,TDMQ 具备良好的弹性伸缩以及故障恢复能力。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档