ASP.NET MVC隨想錄——創建自定義的Middleware中間件


經過前2篇文章的介紹,相信大家已經對OWIN和Katana有了基本的了解,那么這篇文章我將繼續OWIN和Katana之旅——創建自定義的Middleware中間件。

何為Middleware中間件

Middleware中間件從功能上可以理解為用來處理Http請求,當Server將Http請求封裝成符合OWIN規范的字典后,交由Middleware去處理,一般情況下,Pipeline中的Middleware以鏈式的形式處理Http請求,即每一個Middleware都是最小的模塊化,彼此獨立、高效。

從語法上理解Middleware的話,他是一個應用程序委托(Func<IDictionary<string, object>, Task>)的實例,通過使用IAppBuilder 接口的Use或者Run方法將一個Middleware插入到Pipeline中,不同的是使用Run方法不需要引用下一個Middleware,即他是Pipeline中最后的處理元素。

使用Inline方式注冊Middleware

使用Use方法可以將一個Middleware插入到Pipeline中,值得注意的是需要傳入下一個Middleware的引用,代碼如下所示:

  1. app.Use(new Func<Func<IDictionary<string, object>, Task>/*Next*/,
  2.              Func<IDictionary<string, object>/*Environment Dictionary*/, Task>>(next => async env =>
  3.              {
  4.                  string before = "Middleware1--Before(inline)"+Environment.NewLine;
  5.                  string after = "Middleware1--After(inline)"+Environment.NewLine;
  6.                  var response = env["owin.ResponseBody"] as Stream;
  7.                  await response.WriteAsync(Encoding.UTF8.GetBytes(before), 0, before.Length);
  8.                  await next.Invoke(env);
  9.                  await response.WriteAsync(Encoding.UTF8.GetBytes(after), 0, after.Length);
  10.          }));

上述代碼中,實例化了一個委托,它需要傳入下一個Pipeline中的Middleware引用同時返回一個新的Middleware並插入到Pipeline中。因為是異步的,所以別忘了async、await關鍵字。

使用Inline+ AppFunc方式注冊Middleware

為了簡化書寫,我為應用程序委托(Func<IDictionary<string, object>, Task>)類型創建了別名AppFunc

  1. using AppFunc=Func<IDictionary<string,object>/*Environment Dictionary*/,Task/*Task*/>;

所以又可以使用如下方式來講Middleware添加到Pipeline中:

  1. app.Use(new Func<AppFunc, AppFunc>(next => async env =>
  2. {
  3.     string before = "\tMiddleware2--Before(inline+AppFunc)" + Environment.NewLine;
  4.     string after = "\tMiddleware2--After(inline+AppFunc)" + Environment.NewLine;
  5.     var response = env["owin.ResponseBody"] as Stream;
  6.     await response.WriteAsync(Encoding.UTF8.GetBytes(before), 0, before.Length);
  7.     await next.Invoke(env);
  8.     await response.WriteAsync(Encoding.UTF8.GetBytes(after), 0, after.Length);
  9. }));

考慮到業務邏輯的增長,有必要將Lambda表達式中的處理邏輯給分離開來,所以對上述代碼稍作修改,提取到一個名為Invoke的方法內:

  1. app.Use(new Func<AppFunc, AppFunc>(next => env => Invoke(next, env)));
  2. private async Task Invoke(Func<IDictionary<string, object>, Task> next, IDictionary<string, object> env)
  3.         {
  4.             var response = env["owin.ResponseBody"] as Stream;
  5.             string pre = "\t\tMiddleware 3 - Before (inline+AppFunc+Invoke)" + Environment.NewLine;
  6.             string post = "\t\tMiddleware 3 - After (inline+AppFunc+Invoke)" + Environment.NewLine;
  7.             await response.WriteAsync(Encoding.UTF8.GetBytes(pre), 0, pre.Length);
  8.             await next.Invoke(env);
  9.             await response.WriteAsync(Encoding.UTF8.GetBytes(post), 0, post.Length);
  10.         }

雖然將業務邏輯抽取到一個方法中,但Inline這種模式對於復雜的Middleware還是顯得不夠簡潔、易懂。我們更傾向於創建一個單獨的類來表示。

定義原生Middleware類的形式來注冊Middleware

如果你只想簡單的跟蹤一下請求,使用Inline也是可行的,但對於復雜的Middleware,我傾向於創建一個單獨的類,如下所示:

  1. public class RawMiddleware
  2.   {
  3.       private readonly AppFunc _next;
  4.       public RawMiddleware(AppFunc next)
  5.       {
  6.           this._next = next;
  7.       }
  8.       public async Task Invoke(IDictionary<string,object> env )
  9.       {
  10.           var response = env["owin.ResponseBody"] as Stream;
  11.           string pre = "\t\t\tMiddleware 4 - Before (RawMiddleware)" + Environment.NewLine;
  12.           string post = "\t\t\tMiddleware 4 - After (RawMiddleware)\r\n" + Environment.NewLine;
  13.           await response.WriteAsync(Encoding.UTF8.GetBytes(pre), 0, pre.Length);
  14.           await _next.Invoke(env);
  15.           await response.WriteAsync(Encoding.UTF8.GetBytes(post), 0, post.Length);
  16.       }
  17.   }

最后,依舊是通過Use方法來將Middleware添加到Pipeline中:

  1. //兩者方式皆可
  2. //app.Use<RawMiddleware>();
  3. app.Use(typeof (RawMiddleware));

上述代碼中,IAppBuilder實例的Use方法添加Middleware至Pipeline與Inline方式有很大不同,它接受一個Type而非Lambda表達式。在這種情形下,創建了一個Middleware類型的實例,並將Pipeline中下一個Middleware傳遞到構造函數中,最后當Middleware被執行時調用Invoke方法。

注意Middleware是基於約定的形式定義的,需要滿足如下條件:

  • 構造函數的第一個參數必須是Pipeline中下一個Middleware
  • 必須包含一個Invoke方法,它接收Owin環境字典,並返回Task

使用Katana Helper來注冊Middleware

程序集Microsoft.Owin包含了Katana為我們提供的Helper,通過他,可以簡化我們的開發,比如IOwinContext封裝了Owin的環境字典,強類型對象可以通過屬性的形式獲取相關數據,同時為IAppBuilder提供了豐富的擴展方法來簡化Middleware的注冊,如下所示:

  1. app.Use(async (context, next) =>
  2.            {
  3.                await context.Response.WriteAsync("\t\t\t\tMiddleware 5--Befone(inline+katana helper)"+Environment.NewLine);
  4.                await next();
  5.                await context.Response.WriteAsync("\t\t\t\tMiddleware 5--After(inline+katana helper)"+Environment.NewLine);
  6.            });

當然我們也可以定義一個Middleware類並繼承OwinMiddleware,如下所示:

  1. public class MyMiddleware : OwinMiddleware
  2.    {
  3.        public MyMiddleware(OwinMiddleware next)
  4.            : base(next)
  5.        {
  6.  
  7.        }
  8.        public override async Task Invoke(IOwinContext context)
  9.        {
  10.            await context.Response.WriteAsync("\t\t\t\t\tMiddleware 6 - Before (Katana helped middleware class)"+Environment.NewLine);
  11.            await this.Next.Invoke(context);
  12.            await context.Response.WriteAsync("\t\t\t\t\tMiddleware 6 - After (Katana helped middleware class)"+Environment.NewLine);
  13.        }
  14.    }

然后將其添加到Pipeline中:

  1. app.Use<MyMiddleware>();

Middleware的執行順序

在完成上面Middleware注冊之后,在Configuration方法的最后添加最后一個的Middleware中間件,注意它並不需要對下一個Middleware的引用了,我們可以使用Run方法來完成注冊:

  1. app.Run(context => context.Response.WriteAsync("\t\t\t\t\t\tHello World"+Environment.NewLine));

值得注意的是,Pipeline中Middleware處理Http Request順序同注冊順序保持一致,即和Configuration方法中書寫的順序保持一致,Response順序則正好相反,如下圖所示:

最后,運行程序,查看具體的輸出結果是否和我們分析的保持一致:

小結

在這篇文章中,我為大家講解了自定義Middleware的創建,Katana為我們提供了非常多的方式來創建和注冊Middleware,在下一篇文章中,我將繼續OWIN和Katana之旅,探索Katana和其他Web Framework的集成。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM