1. 程式人生 > >ASP.NET Core 1.0中的管道-中介軟體模式

ASP.NET Core 1.0中的管道-中介軟體模式

ASP.NET Core 1.0借鑑了Katana專案的管道設計(Pipeline)。日誌記錄、使用者認證、MVC等模組都以中介軟體(Middleware)的方式註冊在管道中。顯而易見這樣的設計非常鬆耦合並且非常靈活,你可以自己定義任意功能的Middleware註冊在管道中。這一設計非常適用於“請求-響應”這樣的場景——訊息從管道頭流入最後反向流出。

在本文中暫且為這種模式起名叫做“管道-中介軟體(Pipeline-Middleware)”模式吧。

本文將描述”管道-中介軟體模式”的“契約式”設計和“函式式”設計兩種方案。

一、什麼是管道-中介軟體模式?

在此模式中抽象了一個類似管道的概念,所有的元件均以中介軟體的方式註冊在此管道中,當請求進入管道後:中介軟體依次對請求作出處理,然後從最後一箇中間件開始處理響應內容,最終反向流出管道。

二、契約式設計

契約式設計是從面向物件的角度來思考問題,根據管道-中介軟體的理解,中介軟體(Middleware)有兩個職責:

C#
12345 public
interfaceIMiddleware{Request ProcessRequest(Request request);Response ProcessResponse(Response response);}

管道(Pipeline)抽象應該能夠註冊中介軟體(Middleware):

C#
123456789 publicinterfaceIApplicationBuilder{voidUse(IMiddleware middleware);voidUseArrange(List middlewares);Context Run(Context context);}

實現IApplicationBuilder:

C#
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849 publicclassApplicationBuilder:IApplicationBuilder{publicIWindsorContainerContainer{get;privateset;}privatereadonlyList _middlewares;publicApplicationBuilder(IWindsorContainer container){Contract.Requires(container!=null,"container!=null");_middlewares=newList();Container=container;}publicvoidUse(IMiddleware middleware){Contract.Requires(middleware!=null,"middleware!=null");_middlewares.Add(middleware);}publicvoidUseArrange(List middlewares){Contract.Requires(middlewares!=null,"middlewares!=null");_middlewares.AddRange(middlewares);}publicContext Run(Context context){Contract.Requires(context!=null,"context!=null");varrequest=context.Request;varresponse=context.Response;foreach(varmiddleware in_middlewares){request=middleware.ProcessRequest(request);}_middlewares.Reverse();foreach(varmiddleware in_middlewares){response=middleware.ProcessResponse(response);}returnnewContext(request,response);}}

Run()方法將依次列舉Middleware並對訊息的請求和響應進行處理,最後返回最終處理過的訊息。

接下來需要實現一個Middleware:

C#
1234567891011121314 publicclassDefaultMiddleware:IMiddleware{publicRequest ProcessRequest(Request request){request.Process("default request","processed by defaultMiddleware");returnrequest;}publicResponse ProcessResponse(Response response){response.Process("default response","processed by defaultMiddleware");returnresponse;}}

為了將Middleware註冊進管道,我們還可以寫一個擴充套件方法增加程式碼的可讀性:

C#
123456789101112 publicstaticvoidUseDefaultMiddleware(thisIApplicationBuilder applicationBuilder){applicationBuilder.Use();}publicstaticvoidUse(thisIApplicationBuilder applicationBuilder)where TMiddleware:IMiddleware{varmiddleware=applicationBuilder.Container.Resolve();applicationBuilder.Use(middleware);}

寫個測試看看吧:

寫第二個Middleware:

C#
12345678910111213141516 publicclassGreetingMiddleware:IMiddleware{publicRequest ProcessRequest(Request request){request.Process("hello, request","processed by greetingMiddleware");returnrequest;}publicResponse ProcessResponse(Response response){response.Process("hello, request","processed by greetingMiddleware");returnresponse;}}

編寫測試:

三、函式式設計方案

此方案也是Owin和ASP.NET Core採用的方案,如果站在面向物件的角度,第一個方案是非常清晰的,管道最終通過列舉所有Middleware來依次處理請求。

站在函式式的角度來看,Middleware可以用Func來表示,再來看看這張圖:

一個Middleware的邏輯可以用Func, Func>來表示,整個Middleware的邏輯可以用下面的程式碼描述:

C#
1234567891011121314151617181920 publicFunc,Func>Process(){Func,Func>middleware=next=>{Func process=context=>{/*process request*/next(context);/*process response*/returncontext;};returnprocess;};returnmiddleware;}

這一過程是理解函式式方案的關鍵,所有Middleware可以聚合為一個Func,為了易於閱讀,我們可以定義一個委託:

C#
1 publicdelegateContext RequestDelegate(Context context);

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

C#
12345678910111213 publicIApplication Build(){RequestDelegate request=context=>context;_middlewares.Reverse();foreach(varmiddleware in_middlewares){request=middleware(request);}returnnewApplication(request);}

自定義一個函式式Middleware:

C#
12345678910111213141516171819202122 publicclassDefaultMiddleware:IMiddleware{publicFunc Request(){Func request=next=>{returncontext=>{context.Request.Process("default request","processed by defaultMiddleware");next(context);context.Response.Process("default response","processed by defaultMiddleware");returncontext;};};returnrequest;}}