Core 1.0中的管道-中间件模式

时间:2022-06-06 13:52:21

ASP.NET Core 1.0中的管道-中间件模式

SP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline)。日志记录、用户认证、MVC等模块都以中间件(Middleware)的方式注册在管道中。显而易见这样的设计非常松耦合并且非常灵活,你可以自己定义任意功能的Middleware注册在管道中。这一设计非常适用于“请求-响应”这样的场景——消息从管道头流入最后反向流出。

在本文中暂且为这种模式起名叫做“管道-中间件(Pipeline-Middleware)”模式吧。

本文将描述”管道-中间件模式”的“契约式”设计和“函数式”设计两种方案。

一、什么是管道-中间件模式?

Core 1.0中的管道-中间件模式

在此模式中抽象了一个类似管道的概念,所有的组件均以中间件的方式注册在此管道中,当请求进入管道后:中间件依次对请求作出处理,然后从最后一个中间件开始处理响应内容,最终反向流出管道。

二、契约式设计

契约式设计是从面向对象的角度来思考问题,根据管道-中间件的理解,中间件(Middleware)有两个职责:

1
2
3
4
5
public interface IMiddleware
{
    Request ProcessRequest(Request request);
    Response ProcessResponse(Response response);
}

管道(Pipeline)抽象应该能够注册中间件(Middleware):

1
2
3
4
5
6
7
8
9
public interface IApplicationBuilder
{
    void Use(IMiddleware middleware);
    void UseArrange(List<IMiddleware> middlewares);
    Context Run(Context context);
}

实现IApplicationBuilder:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
public class ApplicationBuilder : IApplicationBuilder
{
    public IWindsorContainer Container { get; private set; }
    private readonly List<IMiddleware> _middlewares;
    public ApplicationBuilder(IWindsorContainer container)
    {
        Contract.Requires(container!=null,"container!=null");
        _middlewares=new List<IMiddleware>();
        Container = container;
    }
    public void Use(IMiddleware middleware)
    {
        Contract.Requires(middleware != null, "middleware!=null");
        _middlewares.Add(middleware);
    }
    public void UseArrange(List<IMiddleware> middlewares)
    {
        Contract.Requires(middlewares != null, "middlewares!=null");
        _middlewares.AddRange(middlewares);
    }
    public Context Run(Context context)
    {
        Contract.Requires(context!=null,"context!=null");
        var request=context.Request;
        var response=context.Response;
        foreach (var middleware in _middlewares)
        {
            request = middleware.ProcessRequest(request);
        }
        _middlewares.Reverse();
        foreach (var middleware in _middlewares)
        {
            response = middleware.ProcessResponse(response);
        }
        return new Context(request,response);
    }
}

Run()方法将依次枚举Middleware并对消息的请求和响应进行处理,最后返回最终处理过的消息。

接下来需要实现一个Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class DefaultMiddleware:IMiddleware
 {
     public Request ProcessRequest(Request request)
     {
         request.Process("default request", "processed by defaultMiddleware");
         return request;
     }
     public Response ProcessResponse(Response response)
     {
         response.Process("default response", "processed by defaultMiddleware");
         return response;
     }
 }

为了将Middleware注册进管道,我们还可以写一个扩展方法增加代码的可读性:

1
2
3
4
5
6
7
8
9
10
11
12
public static void UseDefaultMiddleware(this IApplicationBuilder applicationBuilder)
{
    applicationBuilder.Use<DefaultMiddleware>();
}
public static void Use<TMiddleware>(this IApplicationBuilder applicationBuilder)
    where TMiddleware:IMiddleware
{
    var middleware = applicationBuilder.Container.Resolve<TMiddleware>();
    applicationBuilder.Use(middleware);
}

写个测试看看吧:

Core 1.0中的管道-中间件模式

写第二个Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class GreetingMiddleware:IMiddleware
{
    public Request ProcessRequest(Request request)
    {
        request.Process("hello, request","processed by greetingMiddleware");
        return request;
    }
    public Response ProcessResponse(Response response)
    {
        response.Process("hello, request", "processed by greetingMiddleware");
        return response;
    }
}

编写测试:

Core 1.0中的管道-中间件模式

三、函数式设计方案

此方案也是Owin和ASP.NET Core采用的方案,如果站在面向对象的角度,第一个方案是非常清晰的,管道最终通过枚举所有Middleware来依次处理请求。

站在函数式的角度来看,Middleware可以用Func<Context, Context>来表示,再来看看这张图:

Core 1.0中的管道-中间件模式

一个Middleware的逻辑可以用Func<Func<Context, Context>, Func<Context, Context>>来表示,整个Middleware的逻辑可以用下面的代码描述:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public Func<Func<Context, Context>, Func<Context, Context>> Process()
{
    Func<Func<Context, Context>, Func<Context, Context>> middleware = next =>
    {
        Func<Context, Context> process = context =>
        {
            /*process request*/
           
            next(context);
            /*process response*/
            return context;
        };
        return process;
    };
    return middleware;
}

这一过程是理解函数式方案的关键,所有Middleware可以聚合为一个Func<Context,Context>,为了易于阅读,我们可以定义一个委托:

1
public delegate Context RequestDelegate(Context context);

给定初始RequestDelegate,聚合所有Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
public IApplication Build()
{
    RequestDelegate request = context => context;
    _middlewares.Reverse();
    foreach (var middleware in _middlewares)
    {
        request = middleware(request);
    }
    return new Application(request);
}

自定义一个函数式Middleware:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class DefaultMiddleware:IMiddleware
{
    public Func<RequestDelegate, RequestDelegate> Request()
    {
        Func<RequestDelegate, RequestDelegate> request = next =>
        {
            return context =>
            {
                context.Request.Process("default request", "processed by defaultMiddleware");
                next(context);
                context.Response.Process("default response", "processed by defaultMiddleware");
                return context;
            };
        };
        return request;
    }
}

所有代码提供下载:https://git.oschina.net/richieyangs/Pipeline.Middleware.git

摘要: ASP.NET Core 1.0借鉴了Katana项目的管道设计(Pipeline)。日志记录、用户认证、MVC等模块都以中间件(Middleware)的方式注册在管道中。显而易见这样的设计非常松耦合并且非常灵活,你可以自己定义任意功能的Middleware注册在管道中。这一设计非常适用于“请求-响应”这样的场景——消息从管道头流入最后反向流出。 在本文中暂且为这种模式起名叫做“管道-中间件(Pi...阅读全文
posted @ 2016-03-24 14:36 richiezhang 阅读(438) | 评论 (5) 编辑
摘要: 本文将对微软下一代ASP.NET框架做个概括性介绍,方便大家进一步熟悉该框架。 在介绍ASP.NET Core 1.0之前有必要澄清一些产品名称及版本号。ASP.NET Core1.0是微软下一代ASP.NET 框架,在这之前ASP.NET版本稳定在ASP.NET 4.6,对应的.NET Frame阅读全文
posted @ 2016-03-18 21:00 richiezhang 阅读(3601) | 评论 (44) 编辑
摘要: MVC模型以低耦合、可重用、可维护性高等众多优点已逐渐代替了WebForm模型。能够灵活使用MVC提供的扩展点可以达到事半功倍的效果,另一方面Asp.net MVC优秀的设计和高质量的代码也值得我们去阅读和学习。 本文将介绍Asp.net MVC中常用的八个扩展点并举例说明。 一、ActionRes阅读全文
posted @ 2016-02-04 21:02 richiezhang 阅读(4175) | 评论 (44) 编辑
摘要: IDisposable是.Net中一个很重要的接口,一般用来释放非托管资源,我们知道在使用了IDisposable的对象之后一定要调用IDisposable.Dispose()方法,或者使用.Net提供的关键字using来达到这一目的,如: public void ReadFile()...阅读全文
posted @ 2016-01-24 13:08 richiezhang 阅读(486) | 评论 (2) 编辑
摘要: 不知从何时起,我不太轻易去设计抽象类了,一方面是因为我写的业务确实没有设计抽象类的需求,另一方面则基于以下三个考虑:1、面向对象编程中建议多使用“组合”而不是使用“抽象”,原因在于“组合”更加灵活。2、因为要公用一个“方法”,就迫不及待的设计出抽象关系,很容易造成抽象类不够SRP,久而久之抽象类成了...阅读全文
posted @ 2016-01-10 17:12 richiezhang 阅读(228) | 评论 (0) 编辑
摘要: 这两周我需要对一个历史遗留的功能做一些扩展,正如很多人不愿意碰这些历史遗留的代码一样,我的内心也同样对这样的任务充满反抗。这些代码中充斥着各种null判断(你写的return null正确吗?),不规范的变量命名,层层嵌套的if…else语句。显然面对这样的代码我无从下手,更别提什么重构、单元测试了...阅读全文
posted @ 2015-12-25 08:54 richiezhang 阅读(582) | 评论 (16) 编辑
摘要: 系列主题:基于消息的软件架构模型演变 NServiceBus 是一个.Net平台下开源的消息服务框架,这类产品有时也被称作ESB(Enterprise Service Bus)——企业服务总线。NServicebus官方地址:http://particular.net/git: https://github.com/Particular/NServiceBusNServiceBus原作者Udi D...阅读全文
posted @ 2015-12-14 14:26 richiezhang 阅读(3566) | 评论 (40) 编辑
摘要: 又到了周末的code review环节,这次code review发现了一个对async/await的理解问题。让我们直奔主题: var foodsSearch = new FoodSearchService().SearchAsync(); var fruitsSearch = new FruitSearchService().SearchAsync...阅读全文
posted @ 2015-12-05 00:08 richiezhang 阅读(1602) | 评论 (23) 编辑
摘要: 在上一篇”使用OAuth打造webapi认证服务供自己的客户端使用“的文章中我们实现了一个采用了OAuth流程3-密码模式(resource owner password credentials)的WebApi服务端。今天我们来实现一个js+html版本的客户端。一、angular客户端angula...阅读全文
posted @ 2015-11-28 13:11 richiezhang 阅读(1092) | 评论 (2) 编辑
摘要: 上次一篇“你写的try…catch真的有必要吗”引起了很多朋友的讨论。本次我在code review又发现了一个问题,那就是有人有意无意的写出了return null这样的代码,例如: public User GetUser(Guid userId) { ...阅读全文
posted @ 2015-11-27 23:57 richiezhang 阅读(3972) | 评论 (57) 编辑
摘要: 系列主题:基于消息的软件架构模型演变 一、反应式编程(Reactive Programming) 1、什么是反应式编程:反应式编程(Reactive programming)简称Rx,他是一个使用LINQ风格编写基于观察者模式的异步编程模型。简单点说Rx = Observables + LINQ + Schedulers。 2、为什么会产生这种风格的编程模型?我在本系列文章开始的时候说过一个使...阅读全文
posted @ 2015-11-18 16:09 richiezhang 阅读(6634) | 评论 (59) 编辑
摘要: 系列主题:基于消息的软件架构模型演变 既然这个系列的主题是”基于消息的架构模型演变“,少不了说说Actor模型。Akka.net是一个基于Actor模型的分布式框架。如果你对分布式应用还非常陌生,当别人在谈”分布式“、”云计算“等名词时你感到茫然,那么本篇文章将带你进行一次分布式开发之旅。 一、什么是Actor模型 Actor模型由Carl Hewitt于上世纪70年代早期提出并在Erlang...阅读全文
posted @ 2015-11-07 19:59 richiezhang 阅读(6319) | 评论 (55) 编辑
摘要: 一、什么是OAuth OAuth是一个关于授权(Authorization)的开放网络标准,目前的版本是2.0版。注意是Authorization(授权),而不是Authentication(认证)。用来做Authentication(认证)的标准叫做openid connect,我们将在以后的文章中进行介绍。 二、名词定义 理解OAuth中的专业术语能够帮助你理解其流程模式,OAuth中常用的名...阅读全文
posted @ 2015-10-28 22:57 richiezhang 阅读(7620) | 评论 (92) 编辑
摘要: 系列主题:基于消息的软件架构模型演变.net中事件模型很优雅的实现了观察者模式,同时被大量的使用在各种框架中。如果我们非要给事件模型挑毛病,我觉得有两点:实现起来略微繁琐正如我们上篇文章分析,事件模型在特定的情况下会发生内存泄漏于是我们想到了更加简单易用的模型:EventAggregator,正如该...阅读全文
posted @ 2015-10-25 20:06 richiezhang 阅读(477) | 评论 (0) 编辑
摘要: 系列主题:基于消息的软件架构模型演变在Winform和Asp.net时代,事件被大量的应用在UI和后台交互的代码中。看下面的代码: private void BindEvent() { var btn = new Button(); ...阅读全文
posted @ 2015-10-25 18:29 richiezhang 阅读(487) | 评论 (2) 编辑
摘要: 一个优秀的架构师总是能对各种解决方案的优点和对应成本之间取得良好的平衡,而这种能力背后是架构师丰富的经验和广阔的知识体系。基于消息的软件建构模型则是架构师必备的知识点,本文将详细描述该模型的演变过程。 还记得第一次跟师傅打交道,他问我“Hi ABC,你的功能设计的怎么样了?”我有点不以为然,不就是个阅读全文
posted @ 2015-10-25 18:29 richiezhang 阅读(2590) | 评论 (31) 编辑
摘要: 我之所以对函数式代码感兴趣是因为函数式代码富有表现力,可以使用简短、紧凑的代码完成工作,同时能对特定的问题给出优雅的解决方案。现代的编程语言不约而同的朝着面向对象、函数式、动态、解释执行的方向发展,例如Ruby,Swift。而另一些语言则更加强调函数式编程,如F#,Scala,这种语言有着强大的类型...阅读全文
posted @ 2015-10-16 01:08 richiezhang 阅读(8695) | 评论 (1) 编辑
摘要: 前段时间在周末给朋友做了一个小程序,用来记录他们单位的一些调度信息(免费,无版权问题)。把代码分享出来。整个程序没有做任何架构。但是麻雀虽小,用到的技术也没少。WebApi+Castle+AutoMapper+Ef+angular,日志记录Log4net。初学者可以学习借鉴,虽然做的比较仓促,但是自我感觉代码写的还是比较规范。 git地址:https://git.oschina.net/richi...阅读全文
posted @ 2015-10-14 22:31 richiezhang 阅读(410) | 评论 (2) 编辑
摘要: string.IsNullOrEmpty()这个方法算得上是.net中使用频率最高的方法之一。此方法是string的一个静态方法,类似的静态方法在string这个类中还有很多。那么这样的方法作为静态方法是否合理呢?如果我们从面向对象的角度出发,我们会发现这种方案不是十分符合面向对象的设计原则。 什么阅读全文
posted @ 2015-10-11 14:40 richiezhang 阅读(901) | 评论 (15) 编辑
摘要: 随着软件行业的不断发展,历史遗留的程序越来越多,代码的维护成本越来越大,甚至大于开发成本。而新功能的开发又常常依赖于旧代码,阅读旧代码所花费的时间几乎要大于写新功能的时间。 我前几天看了一本书,书中有这么一句话: 此话虽然说的有点夸张,可是也说明了经验和智慧的的重要性。 我们所写的代码主要是为了阅读阅读全文
posted @ 2015-09-26 14:07 richiezhang 阅读(13235) | 评论 (110) 编辑
摘要: 很多人喜欢用Try...Catch把每一个方法都包裹起来,可是真的有必要么?为什么要这样做?我估计是大家被BUG吓怕了,生怕生产环境出现各种莫名其妙的错误,比如最经典的NullReferenceException,可问题是你用Try...Catch包裹起来后错误是不会爆出来了,但是执行结果是你想要的...阅读全文
posted @ 2015-09-02 17:06 richiezhang 阅读(5880) | 评论 (100) 编辑
摘要: 大家是如何对webApi写测试的呢?1.利用Fiddler直接做请求,观察response的内容。2.利用Httpclient做请求,断言response的内容。3.直接调用webApi的action,这种方式的测试跟真实的调用还是有一定差距,不够完美。接下来我介绍一种webApi的in-memor...阅读全文
posted @ 2015-07-19 14:49 richiezhang 阅读(1063) | 评论 (0) 编辑
摘要: 当我们用在MVC总使用IoC时,大家的Controller生命周期(lifestyle)是以哪种方式注册的呢?之前我一直没有思考过这个问题。众所周知在MVC开发过程中,大部分的组件都是以PerWebRequest的方式注册到容器的,Controller也不例外,以Castle为例,注册Control...阅读全文
posted @ 2015-07-12 12:28 richiezhang 阅读(145) | 评论 (0) 编辑
摘要: 表达式树是LINQ To everything 的基础,同时各种类库的Fluent API也 大量使用了Expression Tree。还记得我在不懂expression tree时,各种眼花缭乱的API 看的我各种膜拜,当我熟悉expression tree 后恍然大悟,不用看代码也能知道别人的API 是如何设计的(^_^)。 接下来这篇博客就谈谈如何使用expression t...阅读全文
posted @ 2015-06-07 13:14 richiezhang 阅读(666) | 评论 (6) 编辑
摘要: 由于项目需求,需要使用angular 实现列表的增、删、改,并且列表支持拖拽。看了下angular-ui 里面的sortable组件,使用起来也是非常简单,几十行代码就完成了所需功能。我现在懒得想如何使用jquery完成该功能,不过我能肯定的是使用jquery完成这个功能,代码至少多几倍效果如下:本...阅读全文
posted @ 2015-04-12 18:22 richiezhang 阅读(202) | 评论 (0) 编辑
摘要: 提起函数式编程,大家一定想到的是语法高度灵活和动态的LISP,Haskell这样古老的函数式语言,往近了说ruby,javascript,F#也是函数式编程的流行语言。然而自从.net支持了lambda表达式,C#虽然作为一种指令式程序设计语言,在函数式编程方面也毫不逊色。我们在使用c#编写代码的过...阅读全文
posted @ 2015-04-06 15:59 richiezhang 阅读(929) | 评论 (3) 编辑
摘要: 系列主题:基于消息的软件架构模型演变 说起观察者模式,估计在园子里能搜出一堆来。所以写这篇博客的目的有两点: 观察者模式是写松耦合代码的必备模式,重要性不言而喻,抛开代码层面,许多组件都采用了Publish-Subscribe模式,所以我想按照自己的理解重新设计一个使用场景并把观察者模式灵活使用在其中 我想把C#中实现观察者模式的三个方案做一个总结,目前还没看到这样的总结 现在我们来假设...阅读全文
posted @ 2015-03-29 16:38 richiezhang 阅读(1587) | 评论 (9) 编辑
摘要: 我们经常使用的一些框架例如:EF,Automaper,NHibernate等都提供了非常优秀的Fluent Interface, 这样的API充分利用了VS的智能提示,而且写出来的代码非常整洁。我们如何在代码中也写出这种Fluent的代码呢,我这里介绍3总比较常用的模式,在这些模式上稍加改动或者修饰...阅读全文
posted @ 2015-03-22 18:42 richiezhang 阅读(1175) | 评论 (1) 编辑
摘要: C#中的线程四(System.Threading.Thread)1.最简单的多线程调用System.Threading.Thread类构造方法接受一个ThreadStart委托,改委托不带参数,无返回值 1 public static void Start1() 2 { 3 Console.WriteLine("this is main thread!:{0},{1}", System.Threading.Thread.CurrentThread.CurrentCulture, Thread.CurrentThread.Name); 4 System.Thread...阅读全文
posted @ 2014-04-11 10:25 richiezhang 阅读(161) | 评论 (0) 编辑
摘要: C#中的线程三(结合ProgressBar学习Control.BeginInvoke) 本篇继上篇转载的关于Control.BeginInvoke的论述之后,再结合一个实例来说明Cotrol.BeginInvoke的功能 通过前面2篇的学习应该得出以下结论1、Delegate.BeginInvoke中执行的方法是异步的1 public static void Start2()2 {3 Console.WriteLine("main thread:{0},{1},{2}", Thread.CurrentThread.CurrentCulture, Thread.Current阅读全文
posted @ 2014-04-11 10:16 richiezhang 阅读(190) | 评论 (0) 编辑
摘要: C#中的线程二(Cotrol.BeginInvoke和Control.Invoke)原文地址:http://www.cnblogs.com/whssunboy/archive/2007/06/07/775319.html近日,被Control的Invoke和BeginInvoke搞的头大,就查了些相关的资料,整理如下。感谢这篇文章对我的理解Invoke和BeginInvoke的真正含义。(一)Control的Invoke和BeginInvoke我们要基于以下认识:(1)Control的Invoke和BeginInvoke与Delegate的Invoke和BeginInvoke是不同的。(2)C阅读全文
posted @ 2014-04-11 10:09 richiezhang 阅读(126) | 评论 (0) 编辑
摘要: C#中的线程一(委托中的异步) 一、同步委托 我们平时所用的委托以同步居多,我们编写一个方法和相关委托进行演示: publicdelegatevoid DoSomethingDelegate(string name); //同步委托public static void Start1(){ Console.WriteLine("this is primary thread"); C...阅读全文
posted @ 2014-04-11 10:05 richiezhang 阅读(196) | 评论 (0) 编辑
摘要: 原文地址: http://nexussharp.wordpress.com/2012/04/21/castle-windsor-avoid-memory-leaks-by-learning-the-underlying-mechanics/ CASTLE WINDSOR: AVOID MEMORY LEAKS BY LEARNING THE UNDERLYING MECHANICS ...阅读全文
posted @ 2014-03-26 22:07 richiezhang 阅读(183) | 评论 (0) 编辑
分类: .NET