.Net Core如何优雅的实现中间件

摘要:
应用程序。运行(asynccontext=>因此我们定义了IApplicationBuilder公共接口IApplicationBuilder{IApplicationBuilderUse(Func<//注册中间件IApplicationBuilder Build();//调用委托链的接口设计}Use_components=new();

在.Net Core的源码中,很多地方都有中间件的地方,Kestrel Server和Asp.net Core 等都用了中间件的设计,比如在Kestrel Server中,Http协议的1.0, 1.1, 2.0分别注册了不同的中间件从而导致不同方式的解析报文,这些要求了我们如何设计一个优雅的中间件框架,在MSDN 上这样描述了asp.net core的 中间件,每个中间件都应该

  • Chooses whether to pass the request to the next component in the pipeline.(选择是否将请求传递到管道中的下一个组件)
  • Can perform work before and after the next component in the pipeline.(可在管道中的下一个组件前后执行工作)

这无疑给了中间件的设计难度,在经典模型里,asp.net还是停留在管道模型里,定义了十几个event,分别执行在不同的时间节点,然后在不同的module里会注册自己的行为到事件上,在当时的观念来看,这是一个非常好的代码设计,面向切面编程,不同的module分工明细降低耦合性,但是随之而来带来的是臃肿全家桶设计,在当前的微服务年代,我们需要动态的添加这些设计,比如认证授权session module。

而现在的asp.net core 的中间件设计非常的好,可以拿到下一个中间件的控制权,并且在下一个中间件之前或者结束做其他的工作。如果不熟悉中间件的同学可以看看 msdn 的描述,这里我们来根据源码自己实现一个优雅的中间件。

首先我们要达成的效果是这样的

public class Startup
{
    public void Configure(IApplicationBuilder app)
    {
        app.Use(async (context, next) =>
        {
            // Do work that doesn't write to the Response.
            await next.Invoke();
            // Do logging or other work that doesn't write to the Response.
        });

        app.Run(async context =>
        {
            await context.Response.WriteAsync("Hello from 2nd delegate.");
        });
    }
}

中间件的执行顺序是这样的

.Net Core如何优雅的实现中间件第1张

从上面的用法可以看到,我们需要定义一个IApplicationBuilder,然后用use去注册中间件到applicationbuild 对象,所以我们定义一个IApplicationBuilder

 public interface IApplicationBuilder {

        IApplicationBuilder Use(Func<HttpContext, Func<Task>, Task> middleware);//注册中间件

        IApplicationBuilder Build();//生成委托链

        IApplicationBuilder Run();//调用委托链

    }

Use的接口设计,是为了我们上面实现的效果,传入一个方法指针(委托,后略),这个指针需要两个参数,一个HttpContext,一个是下一个管道的方法指针,返回一个task对象, 现在为了让我们的代码跑起来,再定义一个HttpContext对象如下。

 public class HttpContext {

    }

现在让我们去实现一个这个接口

   public delegate Task RequestDelegate(HttpContext httpContext);

  class DefaultApplicationBuilder : IApplicationBuilder { public static List<Func<RequestDelegate, RequestDelegate>> _components = new (); public IApplicationBuilder Use(Func<HttpContext,Func<Task>,Task> middleware) { Func<RequestDelegate, RequestDelegate> component = next => { return context => { Func<Task> task = () => next(context); return middleware(context,task); }; }; _components.Add(component); return this; } }

现在我们分析Use的实现,首先我们定义了一个方法指针RequestDelegate,这个没啥说的,而这个的设计妙处在DefaultApplicationBuilder中维护了一个 _components对象,是一个集合对象,定义了“二级”方法指针对象,这里的二级指的是Func<Func<T>>对象,得到一个“一级”方法指针处理后返回另一个“一级”方法指针。现在我们看一下这个Use方法的实现,一个中间件middleware就相当于一个方法指针,这时候它定义了一个component,获取一个方法指针,然后返回一个方法指针,注意在返回的方法指针里,它将之前传入的方法指针重新包装了一下得到task对象,这个相当于二级的指针,然后传给中间件。这个地方有点绕。大家需要多看一下理解其中的含义。

然后我们再实现一下build 和run 方法如下。

 public IApplicationBuilder Build() {

            RequestDelegate app = context => Task.CompletedTask;

            _components.Reverse();

            foreach (var component in _components) {

                app = component(app);
            }

            requestDelegate = app;

            return this;
        }

        public IApplicationBuilder Run() {

            var context = new HttpContext();

            requestDelegate(context);

            return this;
        }

简单说一下build方法,这里的设计之妙就在于将“二级”指针转发成“一级”指针并生成一个委托链,其中的next参数装的就是一系列的委托链。返回的就是第一个注册的中间件。现在我们使用一下这个中间件吧。

 static void Main(string[] args) {

            IApplicationBuilder applicationBuilder = new DefaultApplicationBuilder();

            applicationBuilder.Use(async (context, next) => {
                Console.WriteLine(1);
                await next.Invoke();
                Console.WriteLine(2);

            });

            applicationBuilder.Use(async (context, next) => {
                Console.WriteLine(3);
                await next.Invoke();
                Console.WriteLine(4);
            });

            applicationBuilder.Use(async (context, next) => {
                Console.WriteLine(5);
                await next.Invoke();
                Console.WriteLine(6);
            });

            applicationBuilder
                .Build()
                .Run();

            Console.WriteLine("Hello World!");
        }

返回结果就是如下,就是msdn文档所说的调用逻辑。

1
3
5
6
4
2
Hello World!

 这一块理解起来比较难,设计了这中间件这一块的人很厉害,已经将代码上传到github 上了,大家有兴趣可以对比代码来研究分析。如果有任何问题欢迎大家留言。谢谢大家的阅读

免责声明:文章转载自《.Net Core如何优雅的实现中间件》仅用于学习参考。如对内容有疑问,请及时联系本站处理。

上篇使用作业定时压缩数据库小程序 自定义多选框、单选框样式下篇

宿迁高防,2C2G15M,22元/月;香港BGP,2C5G5M,25元/月 雨云优惠码:MjYwNzM=

相关文章

标准C程序设计七---33

Linux应用 编程深入 语言编程标准C程序设计七---经典C11程序设计以下内容为阅读:《标准C程序设计》(第7版) 作者:E. Balagurusamy(印), 李周芳译 清华大学出版社 2017.7《21天学通C语言》(第7版) 作者:Bradley Jones Peter Aitken Dean Miller(美), 姜佑译 人民邮电出版社 201...

mysql灾备演练问题

前期写的mysql热备份脚本恢复,还没有正式用到过,但是今天演练灾备恢复,但是遇到几个问题。 测试环境: 搭建mysql,安装xtrabackup vim /etc/yum.repos.d/Percona.repo [percona] name = CentOS $releasever - Percona baseurl=http://repo.per...

浏览器的统一指针事件:Pointer Event

 在早期的浏览器,输入的事件其实相对单纯,只有考虑到鼠标和键盘两种;而当时的鼠标事件,其实就是 click、mousedown、mouseup 等等的事件。但是当手机、平板开始流行时候,再移动装置上的主要操作界面,已经从鼠标变成是触控了~ 由于触控和鼠标的操作逻辑,算是有根本上的差异的,再加上大部分的装置又支持多点触控,所以虽然浏览器大多会把触控的事件对应...

分布式系统互斥性与幂等性问题的分析与解决

随着互联网信息技术的飞速发展,数据量不断增大,业务逻辑也日趋复杂,对系统的高并发访问、海量数据处理的场景也越来越多。如何用较低成本实现系统的高可用、易伸缩、可扩展等目标就显得越发重要。 为了解决这一系列问题,系统架构也在不断演进。传统的集中式系统已经逐渐无法满足要求,分布式系统被使用在更多的场景中。 分布式系统由独立的服务器通过网络松散耦合组成。在这个系统...

Invoke()方法的使用

在多线程编程中,我们经常要在工作线程中去更新界面显示,而在多线程中直接调用界面控件的方法是错误的做法,Invoke 和 BeginInvoke 就是为了解决这个问题而出现的,使你在多线程中安全的更新界面显示。 正确的做法是将工作线程中涉及更新界面的代码封装为一个方法,通过 Invoke 或者 BeginInvoke 去调用,两者的区别就是一个导致工作线程等...

虚表和虚表指针

  编译器:VS2015 0x01 基础概念   首先还是简单重复一下基础概念。   C++的多态性可以简单地概括为“一个接口,多种方法”,程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。    1、多态性     指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。     a...