詳解Asp.net 5中的ApplicationBuilder
ApplicationBuilder(IApplicationBuilder接口),是OWIN的基礎,而且裡面都是代理、代理的代理,各種lambda表達式,估計要看這部分代碼,很多人得頭昏腦漲。今天就對個類以及幾個擴展方法進行講解。
按慣例先貼代碼(這是我修改後的,將接口繼承去掉瞭、HttpContext類修改成自己的MyHttpContext類)
public class ApplicationBuilder { private readonly IList<Func<RequestDelegate, RequestDelegate>> _components = new List<Func<RequestDelegate, RequestDelegate>>(); public ApplicationBuilder() { } private ApplicationBuilder(ApplicationBuilder builder) { } public ApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware) { _components.Add(middleware); return this; } public ApplicationBuilder New() { return new ApplicationBuilder(this); } public RequestDelegate Build() { RequestDelegate app = context => { context.StatusCode = "404"; System.Console.WriteLine("404"); return Task.FromResult(0); }; foreach (var component in _components.Reverse()) { app = component(app); } return app; } }
RequestDelegate的定義如下:
public delegate Task RequestDelegate(MyHttpContext context);
從ApplicationBuilder的源代碼中我們可以關註3個點:_components、Use方法、Build方法。
- _components是也一個列表(IList)對象,不過裡面類型有點特殊——是以代理RequestDelegate為參數、代理RequestDelegate為返回值的一個代理。這裡用代理說有點別嘴,可以把代理叫做函數,就是裡面的類型是一個函數,這個函數的參數也是函數,返回值也是函數。
- Use方法,就是在上面的列表對象後面添加一條新記錄。
- Build方法就是將_components數組按照反向順序,制作成一個鏈式結構(有點類似鏈表的感覺)。下面用倆幅圖說明下:
Build之前
Build之後
我們還可以從代碼中看到Item1的參數給的是“404”,而返回結果是RequestDelegate類型。也就是說這個返回類似於voidRequestDelegate(MyHttpContext context)。如果系統給我們一個context變量,那麼這個管道就可以從頭到尾的跑下去瞭。而事實上在Asp.net5中,這個管道就是用於替代傳統的IHttpModule的(可能不準確),那現在問題就來瞭,Item1的參數是這個管道的第一環還是最後一環呢?從圖形來看應該是第一環,但是事實上這是一個誤解。因為箭頭兩面一個是參數,一個是執行體(參數是一個方法,會在執行體內調用執行)。在執行體內,可能在開始就執行參數的內容,之後執行具體的內容;也可以是先執行具體內容,之後執行參數,最後在執行一部分具體內容;還可以先執行具體內容,之後參數;還可能無視參數,直接直接自己的內容,那麼之前的參數就會被忽略。也就是說無所謂順序,404可能是管道的第一環,也可能是最後一環,也可能是中間環節,還可能壓根就不執行。這個和Item1、Item2等內容具體的寫法有關系。(雖然也是鏈式結構是不是和鏈表感覺不一樣)
是不是感覺太零活瞭,源碼還對ApplicationBuilder做瞭倆個擴展方法,代碼整理如下:
public static class RunExtensions { public static ApplicationBuilder Use(this ApplicationBuilder app, Func<MyHttpContext, Func<Task>, Task> middleware) { return app.Use(next => { return context => { Func<Task> simpleNext = () => next(context); return middleware(context, simpleNext); }; }); } public static void Run(this ApplicationBuilder app, RequestDelegate handler) { if (app == null) { throw new ArgumentNullException("why?"); } if (handler == null) { throw new ArgumentNullException("How?"); } app.Use(_ => handler); } }
首先說Use方法,改方法是對之前Use方法的一個更改。將傳入的參數更改為Func<MyHttpContext, Func<Task>, Task>。這樣做有什麼好處?之前的Func<RequestDelegate, RequestDelegate>對象並不能給人清楚的明瞭的感覺,而Func<MyHttpContext, Func<Task>, Task>就非常明確瞭。傳入的參數:MyHttpContext就是Context對象,Func<Task>就是next的執行體。返回值是一個Task(類似於void)。一目瞭然。
再說Run方法,顯而易見,Run方法隻執行自己的內容,並沒有執行參數體。所以鏈式結構的在其前的都會被舍棄,不會被執行。
最後把自己的測試例子貼出來,供大傢參考
示例1:
static void Main(string[] args) { MyHttpContext context = new MyHttpContext() { StatusCode = "A" }; Func<MyHttpContext, Func<Task>, Task> middleware = (x, y) => { context.StatusCode += "C"; System.Console.WriteLine(context.StatusCode); return y(); }; Func<MyHttpContext, Func<Task>, Task> middleware2 = (x, y) => { context.StatusCode += "End1"; System.Console.WriteLine(context.StatusCode); return Task.FromResult(0); }; ApplicationBuilder builder = new ApplicationBuilder(); builder.Use( next => { return (MyHttpContext o) => { o.StatusCode += "B"; System.Console.WriteLine(context.StatusCode); next(o); return Task.FromResult(0); }; } ); builder.Use(middleware); //builder.Use(middleware2); //builder.Use(middleware); //builder.Run(o => { o.StatusCode += "End2"; return Task.FromResult(0); }); builder.Build().Invoke(context); System.Console.ReadLine(); }
執行結果:
示例2:
static void Main(string[] args) { MyHttpContext context = new MyHttpContext() { StatusCode = "A" }; Func<MyHttpContext, Func<Task>, Task> middleware = (x, y) => { context.StatusCode += "C"; System.Console.WriteLine(context.StatusCode); return y(); }; Func<MyHttpContext, Func<Task>, Task> middleware2 = (x, y) => { context.StatusCode += "End1"; System.Console.WriteLine(context.StatusCode); return Task.FromResult(0); }; ApplicationBuilder builder = new ApplicationBuilder(); builder.Use( next => { return (MyHttpContext o) => { o.StatusCode += "B"; System.Console.WriteLine(context.StatusCode); next(o); return Task.FromResult(0); }; } ); builder.Use(middleware); builder.Use(middleware2); //builder.Use(middleware); //builder.Run(o => { o.StatusCode += "End2"; System.Console.WriteLine(context.StatusCode); return Task.FromResult(0); }); builder.Build().Invoke(context); System.Console.ReadLine(); }
執行結果:
示例3:
static void Main(string[] args) { MyHttpContext context = new MyHttpContext() { StatusCode = "A" }; Func<MyHttpContext, Func<Task>, Task> middleware = (x, y) => { context.StatusCode += "C"; System.Console.WriteLine(context.StatusCode); return y(); }; Func<MyHttpContext, Func<Task>, Task> middleware2 = (x, y) => { context.StatusCode += "End1"; System.Console.WriteLine(context.StatusCode); return Task.FromResult(0); }; ApplicationBuilder builder = new ApplicationBuilder(); builder.Use( next => { return (MyHttpContext o) => { o.StatusCode += "B"; System.Console.WriteLine(context.StatusCode); next(o); return Task.FromResult(0); }; } ); builder.Use(middleware); //builder.Use(middleware2); //builder.Use(middleware); builder.Run(o => { o.StatusCode += "End2"; System.Console.WriteLine(context.StatusCode); return Task.FromResult(0); }); builder.Build().Invoke(context); System.Console.ReadLine(); }
執行結果:
到此這篇關於詳解Asp.net 5中的ApplicationBuilder的文章就介紹到這瞭。希望對大傢的學習有所幫助,也希望大傢多多支持WalkonNet。
推薦閱讀:
- ASP.NET Core模仿中間件方式實現列表過濾功能
- .Net中Task Parallel Library的進階用法
- .NET Core中如何實現或使用對象池?
- ASP.NET Core自定義中間件的方式詳解
- ASP.NET Core使用Middleware設置有條件允許訪問路由