Asp.Net Core 系列教程 (四) 消息中間件

在ASP.Net Core 添加了諸多的“中間件”處理用戶的請求和響應,這種方式非常編譯功能擴展,如果我需要修改某個功能時 直接添加對應的中間件就好了,不需要 大規模的調整架構了。

當然再好的架構都有其弊端。

 

矛盾存在於一切事物,並貫穿於事物發展的始終。

 好了,言歸正傳


消息中間件的原理

先看下面的代碼

        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }
            app.UseStaticFiles();
            app.UseStatusCodePages();
            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });
        }

這段代碼很常見,依次注入了使用靜態文件,是錯狀態碼頁,使用路由,使用節點,那麼這些都是如何實現的呢?

我們先看一下原理,搞懂原理再擼代碼。

如下是個簡化的管道

 

看完上面的圖片,再看代碼我想 你也就能明白編寫各個Use時順序爲啥是異常處理在最前面了吧。

越是靠前,請求越是先運行,越是靠前,應答越是靠後處理。

因果關係是客觀事物本身的一種規律。


如何編寫自己的中間件

在MVC中如果我們要在管道中添加一箇中間件,可以使用Use方法,該方法是MVC有限的共有方法中的一個,你應該可以發現這個Run方法是多麼重要。

在事物發展的任何階段上,必有而且只有一種矛盾居於支配的地位,起着規定或影響其他矛盾的作用。這種矛盾就是主要矛盾。其他矛盾則是非主要矛盾,即次要矛盾。

我們先看一下Use方法。

        //
        // 摘要:
        //     Adds a middleware delegate to the application's request pipeline.
        //
        // 參數:
        //   middleware:
        //     The middleware delegate.
        //
        // 返回結果:
        //     The Microsoft.AspNetCore.Builder.IApplicationBuilder.
        IApplicationBuilder Use(Func<RequestDelegate, RequestDelegate> middleware);

這個方法只有一個參數,這個參數 是個帶返回值的委託,第一個委託參數 你可以理解爲HttpContext上下文(也就是被處理就是對象),第二個委託參數就是管道中的下一個消息中間件,我們調用時不能直接return掉。爲啥?如果你直接return掉 後面的消息中間件就不會執行了,任務直接涼涼。

我們應該這樣寫。

                 app.Use(async (current, next) =>
              {
                  if (current.Request.Headers.ContainsKey("CurrentDateTime"))
                  {
                      current.Response.Headers.Add("DateTime", DateTime.Now.ToString());
                  }
                  await next.Invoke();
              });           

當我們檢測到Http上下文請求中包含頭CurrentDateTime,就在返回的頭中添加 當前時間,最後我們直接await 下一個 中間件。這裏不是 retun切記。當然這種寫法 在.net 3.0中 已經不推薦了(微軟內部還是這樣實現的,只是不推薦你這樣寫)。

小夥子 你不推薦我這樣寫,那我怎麼寫?

你應該這樣寫。

    public class DateTimeMiddleware
    {
        private readonly RequestDelegate next;

        public DateTimeMiddleware(RequestDelegate next)
        {
            this.next = next;
        }
        public async Task Inboke(HttpContext httpContext)
        {
            if (httpContext.Request.Headers.ContainsKey("CurrentDateTime"))
            {
                httpContext.Response.Headers.Add("DateTime", DateTime.Now.ToString());
            }
            await next.Invoke(httpContext);
        }
    }

那麼這個類如何調用呢?當然這裏不能直接使用Use了,很明顯沒法傳入,怎麼搞?使用微軟編寫的擴展方法

            app.UseMiddleware<DateTimeMiddleware>();

這個擴展方法也是擴展的Use,你也可以自己寫一個。爲啥要這樣寫???因爲這樣方便做單元測試,而你直接寫Use那其實是一個匿名方法,不太好做單元測試。


要想詳細瞭解中間件,我們還不得不瞭解一下分支管道,所謂分支管道就是指滿足一定條件纔會觸發的管道,我們前面說的管道其實無論是否滿足條件是都會執行一遍的,好,接下來,我們說下分支管道。

在Asp.Net Core中,分支管道是通過Map來實現的。 

原理很簡單,框架是通過識別PathString來識別,然後條件注入IApplicationBuilder。我們具體看一下這個方法。

IApplicationBuilder Map(this IApplicationBuilder app, PathString pathMatch, Action<IApplicationBuilder> configuration);

 依據上面說的,我們就可以把前面寫的中間件改成如下格式

            app.Map("/dateTime", appbuilder =>
            {
                appbuilder.UseMiddleware<DateTimeMiddleware>();
            });

 當HttpContext請求上下文Uri中包含dateTime,該消息中間件便會被觸發。

發展的實質是新事物產生和舊事物滅亡,發展的本質是事物不斷實現自身的“揚棄”向着更高的層次不斷前進

 上面說的管道實際上 進入是完全判斷dateTime的,但是如果我們還想附加一些條件怎麼辦?那就使用MapWhen()

基於上面的介紹再看如下的代碼,我想你肯定能搞懂如何使用了。

IApplicationBuilder MapWhen(this IApplicationBuilder app, Func<HttpContext, bool> predicate, Action<IApplicationBuilder> configuration);

 其實他不是調用PathString了,而是傳入一個HTTPContext,你給他返回一個bool結果,然後框架根據返回值自動決定是否運行後面的代碼。如此擴展性強多了。

在Asp.Net Core3.0中(我不是很確定之前是否存在)微軟又擴展出了一個新的中間件,

UseEndpoints(this IApplicationBuilder builder, Action<IEndpointRouteBuilder> configure);

這個中間件使用前必須調用

public static IApplicationBuilder UseRouting(this IApplicationBuilder builder);

你可以把他倆理解爲一對鴛鴦。

所以我們編寫的代碼應該類似於這樣

            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapGet("/", async context =>
                {
                    await context.Response.WriteAsync("Hello World!");
                });
            });

嗯?怎麼這裏又出現一個Map????啥????這個和app直接map有啥區別??

其實本質上沒啥區別,這裏的map 匹配的是路由字符串。Look at this!

        //
        // 摘要:
        //     Adds a Microsoft.AspNetCore.Routing.RouteEndpoint to the Microsoft.AspNetCore.Routing.IEndpointRouteBuilder
        //     that matches HTTP requests for the specified pattern.
        //
        // 參數:
        //   endpoints:
        //     The Microsoft.AspNetCore.Routing.IEndpointRouteBuilder to add the route to.
        //
        //   pattern:
        //     The route pattern.
        //
        //   requestDelegate:
        //     The delegate executed when the endpoint is matched.
        //
        // 返回結果:
        //     A Microsoft.AspNetCore.Builder.IEndpointConventionBuilder that can be used to
        //     further customize the endpoint.
        public static IEndpointConventionBuilder Map(this IEndpointRouteBuilder endpoints, RoutePattern pattern, RequestDelegate requestDelegate);
        //
        // 摘要:
        //     Adds a Microsoft.AspNetCore.Routing.RouteEndpoint to the Microsoft.AspNetCore.Routing.IEndpointRouteBuilder
        //     that matches HTTP requests for the specified pattern.
        //
        // 參數:
        //   endpoints:
        //     The Microsoft.AspNetCore.Routing.IEndpointRouteBuilder to add the route to.
        //
        //   pattern:
        //     The route pattern.
        //
        //   requestDelegate:
        //     The delegate executed when the endpoint is matched.
        //
        // 返回結果:
        //     A Microsoft.AspNetCore.Builder.IEndpointConventionBuilder that can be used to
        //     further customize the endpoint.
        public static IEndpointConventionBuilder Map(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);

第一個參數換成了RoutePattern,哦吼!這不就是之前老版使用的路由嘛!

微軟還在此基礎上擴展出瞭如下方法。


        public static IEndpointConventionBuilder Map(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapDelete(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapGet(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapMethods(this IEndpointRouteBuilder endpoints, string pattern, IEnumerable<string> httpMethods, RequestDelegate requestDelegate);
        
        public static IEndpointConventionBuilder MapPost(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);
      
        public static IEndpointConventionBuilder MapPut(this IEndpointRouteBuilder endpoints, string pattern, RequestDelegate requestDelegate);

類似,只不過區分了Http請求方法。看完這些你再看其他的擴展方法,就會容易理解多啦

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