在.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."); }); } }
中介軟體的執行順序是這樣的
從上面的用法可以看到,我們需要定義一個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 上了,大家有興趣可以對比程式碼來研究分析。如果有任何問題歡迎大家留言。謝謝大家的閱讀