.Net Core如何優雅的實現中介軟體

NeilHu發表於2021-08-13

在.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 上了,大家有興趣可以對比程式碼來研究分析。如果有任何問題歡迎大家留言。謝謝大家的閱讀

 

相關文章