NET CORE 管道模型及中間件使用解讀

說到NET CORE 管道模型不得不先來看看之前的ASP.NET 的管道模型,兩者差異很大,.NET CORE 3.1 後完全重新設計了框架的底層,.net core 3.1 的管道模型更加靈活便捷,可做到熱插拔,通過管道可以隨意註冊自己想要的服務或者第三方服務插件.

ASP.NET 管道

 

請求進入ASP.NET 工作進程後,由進程創建HttpWorkRequest 對象,封裝此次請求有關的所有信息,然後進入HttpRuntime 類進行進一步的處理。HttpRuntime 通過請求信息創建HttpContext 上下文對象,此對象將貫穿整個管道,直到響應結束。同時創建或從應用程序池裏初始化一個HttpApplication對象,由此對象開始處理之前註冊的多個HttpModule。之後調用HandlerFactory 創建Handler處理程序,最終處理此次請求內容,生成響應返回。

 以前的管道模型是全家桶方式,所有的管道不支持熱插拔,一次性全部集成在裏面,所有這也是ASP.NET 沒有.NET CORE 性能好的一大原因所在。

    IHttpModule 和IHttpHandler 已經不復存在了,取而代之的是一個個中間件(Middleware)。Server將接收到的請求直接向後傳遞,依次經過每一箇中間件進行處理,然後由最後一箇中間件處理並生成響應內容後回傳,再反向以此經過每個中間件,直到由Server發送出去。中間件就像一層一層的“濾網”,過濾所有的請求和響應。這一設計非常適用於“請求-響應”這樣的場景--消息從管道頭流入最後反向流出。

     ASP.NET Core是一套全新的平臺,已經不再向前兼容,設計更追求組件化,追求高性能,沒有全家桶,那麼ASP.NET Core是怎麼搭建請求管道的呢?默認情況,管道只有一個404。然後你也可以增加請求的處理,這就是以前的Handler,只包含業務處理環節,其他的就是中間件,MiddleWare。

我們現在來看下幾種中間件註冊的模式:

以下的代碼都把Configure 中的代碼全部註釋的情況下從零代碼開始一個一個註冊演示

       終結者模式

  • public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {      Console.WriteLine("Configure");        app.Run(async (HttpContext context) => {              await context.Response.WriteAsync("Hello World Run");      });     app.Run(async (HttpContext context) => {             await context.Response.WriteAsync("Hello World Run Again");      }); }

運行代碼後瀏覽器可以看到結果如下:

 

 

從上面的運行結果可以看出 Run 終結式  只是執行,沒有去調用Next  ,一般作爲終結點。所謂Run終結式註冊,其實只是一個擴展方法,最終還不是得調用Use方法,

     Use 方式註冊

    ​​​​​

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)  {            app.Use(async (context, next) =>            {                await context.Response.WriteAsync("Hello World Use1 <br/>");                await next();//調用下一個中間件                await context.Response.WriteAsync("Hello World Use1 End <br/>");            });            app.Use(async (context, next) =>            {                await context.Response.WriteAsync("Hello World Use2 Again <br/>");                await next();            });  }

以上代碼得出的結果如下:

Hello World Use1 <br/>Hello World Use2 Again <br/>

從運行結果 中hello world use 1 end <br/> 並未執行,主要是在它上面 next() 調用了下一個中間件,到那裏已經終結到下一個中間件執行去了。

再來看下面的代碼運行結果:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {            app.Use(async (context, next) =>            {                await context.Response.WriteAsync("Hello World Use1 <br/>");            });            app.Use(async (context, next) =>            {                await context.Response.WriteAsync("Hello World Use2  <br/>");            }); }

結果如圖:

     第二個中間件也並未得到執行,use 方式註冊中間件得出的結論是:Use註冊動作  不是終結點  ,執行next,就可以執行下一個中間件   如果不執行,就等於Run

      UseWhen可以對HttpContext檢測後,增加處理環節;原來的流程還是正常執行的,代碼如下 該方式註冊可以實現一系列的驗證攔截等操作,從管道的上一層管道進行合理性攔截匹配等等系列過濾,可以說類似於Filter 的實現 ​​​​​​​

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {            app.UseWhen(context =>            {                             return context.Request.Query.ContainsKey("Name");            },            appBuilder =>             {                 appBuilder.Use(async (context, next) =>                 {                     await context.Response.WriteAsync("Hello World Use3 Again Again Again <br/>");                     await next();                 });            }); }

看了上面的幾個管道應用模塊的註冊,我們再來一起解讀下源代碼

IApplicationBuilder 應用程序的組裝者,RequestDelegate:傳遞一個HttpContext,異步操作下,不返回;也就是一個處理動作,Use(Func<RequestDelegate, RequestDelegate> middleware) 委託,傳入一個RequestDelegate,返回一個RequestDelegate。ApplicationBuilder裏面有個容器IList<Func<RequestDelegate, RequestDelegate>> _components,Use就只是去容器裏面添加個元素。最終會Build()一下, 如果沒有任何註冊,就直接404處理。

 核心代碼如下:​​​​​​​

public IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware){     _components.Add(middleware);     return this;} public RequestDelegate Build() {            RequestDelegate app = context =>            {                // If we reach the end of the pipeline, but we have an endpoint, then something unexpected has happened.                // This could happen if user code sets an endpoint, but they forgot to add the UseEndpoint middleware.                var endpoint = context.GetEndpoint();                var endpointRequestDelegate = endpoint?.RequestDelegate;                if (endpointRequestDelegate != null)                {                    var message =                        $"The request reached the end of the pipeline without executing the endpoint: '{endpoint.DisplayName}'. " +                        $"Please register the EndpointMiddleware using '{nameof(IApplicationBuilder)}.UseEndpoints(...)' if using " +                        $"routing.";                    throw new InvalidOperationException(message);                }                context.Response.StatusCode = 404;                return Task.CompletedTask;            };            foreach (var component in _components.Reverse())            {                app = component(app);            }            return app; }

IApplicationBuilder build之後其實就是一個RequestDelegate,能對HttpContext加以處理,默認情況下,管道是空的,就是404;可以根據你的訴求,任意的配置執行,一切全部由開發者自由定製,框架只是提供了一個組裝方式

看了源代碼後我們再來對上面的中間件進行優雅的封裝,封裝後的代碼如下:​​​​​​​

public class FirstMiddleWare    {        private readonly RequestDelegate _next;        public FirstMiddleWare(RequestDelegate next)        {            this._next = next;        }                public async Task Invoke(HttpContext context)        {            await context.Response.WriteAsync($"{nameof(FirstMiddleWare)},Hello World1!<br/>");            await _next(context);            await context.Response.WriteAsync($"{nameof(FirstMiddleWare)},Hello World2!<br/>");        }    }

使用註冊中間件​​​​​​​

public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {      app.UseMiddleware<FirstMiddleWare>(); }

我們可以再升級一點點,使用擴展方法,將這個類中的邏輯作爲IApplicationBuilder的擴展方法。

public static class MiddleExtend{    public static IApplicationBuilder UseFirstMiddleWare(this IApplicationBuilder builder)    {        return builder.UseMiddleware<FirstMiddleWare>();    }}

使用時代碼如下:

app.UseFirstMiddleWare();

到這裏.net core 管道模型和中間件註冊使用已經告一段落了!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章