鍍金池/ 教程/ C#/ ASP.NET MVC 隨想錄(8)——?jiǎng)?chuàng)建自定義的 Middleware 中間件
ASP.NET MVC 使用 Bootstrap 系列(4)——使用 JavaScript 插件
ASP.NET MVC 隨想錄(6)——漫談 OWIN
ASP.NET MVC 隨想錄(5)——?jiǎng)?chuàng)建 ASP.NET MVC Bootstrap Helpers
ASP.NET MVC 隨想錄(3)——使用 Bootstrap 組件
ASP.NET MVC 隨想錄(7)——鋒利的 KATANA
ASP.NET MVC 隨想錄(1)——開(kāi)始使用 Bootstrap
ASP.NET MVC 隨想錄(8)——?jiǎng)?chuàng)建自定義的 Middleware 中間件
ASP.NET MVC 隨想錄(2)——使用 Bootstrap CSS 和 HTML 元素
作者簡(jiǎn)介

ASP.NET MVC 隨想錄(8)——?jiǎng)?chuàng)建自定義的 Middleware 中間件

經(jīng)過(guò)前 2 篇文章的介紹,相信大家已經(jīng)對(duì) OWIN 和 Katana 有了基本的了解,那么這篇文章我將繼續(xù)OWIN 和 Katana 之旅——?jiǎng)?chuàng)建自定義的 Middleware 中間件。

何為 Middleware 中間件

Middleware 中間件從功能上可以理解為用來(lái)處理 Http 請(qǐng)求,當(dāng) Server 將 Http 請(qǐng)求封裝成符合 OWIN 規(guī)范的字典后,交由 Middleware 去處理,一般情況下, Pipeline 中的 Middleware 以鏈?zhǔn)降男问教幚?Http 請(qǐng)求,即每一個(gè) Middleware 都是最小的模塊化,彼此獨(dú)立、高效。

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

使用 Inline 方式注冊(cè) Middleware

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

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

上述代碼中,實(shí)例化了一個(gè)委托,它需要傳入下一個(gè) Pipeline 中的 Middleware 引用同時(shí)返回一個(gè)新的 Middleware 并插入到 Pipeline 中。因?yàn)槭钱惒降?,所以別忘了 async、await 關(guān)鍵字。

使用 Inline+ AppFunc 方式注冊(cè) Middleware

為了簡(jiǎn)化書(shū)寫(xiě),我為應(yīng)用程序委托(Func<IDictionary<string,object>, Task>)類(lèi)型創(chuàng)建了別名 AppFunc:

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

所以又可以使用如下方式來(lái)講 Middleware 添加到 Pipeline 中:

app.Use(new Func<AppFunc, AppFunc>(next => async env =>
{
    string before = "\tMiddleware2--Before(inline+AppFunc)" + Environment.NewLine;

    string after = "\tMiddleware2--After(inline+AppFunc)" + Environment.NewLine;

    var response = env["owin.ResponseBody"] as Stream;

    await response.WriteAsync(Encoding.UTF8.GetBytes(before), 0, before.Length);

    await next.Invoke(env);

    await response.WriteAsync(Encoding.UTF8.GetBytes(after), 0, after.Length);

}));

考慮到業(yè)務(wù)邏輯的增長(zhǎng),有必要將 Lambda 表達(dá)式中的處理邏輯給分離開(kāi)來(lái),所以對(duì)上述代碼稍作修改,提取到一個(gè)名為 Invoke 的方法內(nèi):

app.Use(new Func<AppFunc, AppFunc>(next => env => Invoke(next, env)));
private async Task Invoke(Func<IDictionary<string, object>, Task> next, IDictionary<string, object> env)
{

    var response = env["owin.ResponseBody"] as Stream;

    string pre = "\t\tMiddleware 3 - Before (inline+AppFunc+Invoke)" + Environment.NewLine;

    string post = "\t\tMiddleware 3 - After (inline+AppFunc+Invoke)" + Environment.NewLine;

    await response.WriteAsync(Encoding.UTF8.GetBytes(pre), 0, pre.Length);

    await next.Invoke(env);

    await response.WriteAsync(Encoding.UTF8.GetBytes(post), 0, post.Length);

}

雖然將業(yè)務(wù)邏輯抽取到一個(gè)方法中,但 Inline 這種模式對(duì)于復(fù)雜的 Middleware 還是顯得不夠簡(jiǎn)潔、易懂。我們更傾向于創(chuàng)建一個(gè)單獨(dú)的類(lèi)來(lái)表示。

定義原生 Middleware 類(lèi)的形式來(lái)注冊(cè) Middleware

如果你只想簡(jiǎn)單的跟蹤一下請(qǐng)求,使用 Inline 也是可行的,但對(duì)于復(fù)雜的 Middleware,我傾向于創(chuàng)建一個(gè)單獨(dú)的類(lèi),如下所示:

public class RawMiddleware
{
      private readonly AppFunc _next;
      public RawMiddleware(AppFunc next)
      {
        this._next = next;
      }

      public async Task Invoke(IDictionary<string,object> env )
      {
          var response = env["owin.ResponseBody"] as Stream;

          string pre = "\t\t\tMiddleware 4 - Before (RawMiddleware)" + Environment.NewLine;

          string post = "\t\t\tMiddleware 4 - After (RawMiddleware)rn" + Environment.NewLine;

          await response.WriteAsync(Encoding.UTF8.GetBytes(pre), 0, pre.Length);

          await _next.Invoke(env);

          await response.WriteAsync(Encoding.UTF8.GetBytes(post), 0, post.Length);

      }

  }

最后,依舊是通過(guò) Use 方法來(lái)將 Middleware 添加到 Pipeline 中:

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

上述代碼中,IAppBuilder 實(shí)例的 Use 方法添加 Middleware 至 Pipeline 與 Inline 方式有很大不同,它接受一個(gè) Type 而非 Lambda 表達(dá)式。在這種情形下,創(chuàng)建了一個(gè) Middleware 類(lèi)型的實(shí)例,并將 Pipeline 中下一個(gè) Middleware 傳遞到構(gòu)造函數(shù)中,最后當(dāng) Middleware 被執(zhí)行時(shí)調(diào)用 Invoke 方法。

注意 Middleware 是基于約定的形式定義的,需要滿(mǎn)足如下條件:

  • 構(gòu)造函數(shù)的第一個(gè)參數(shù)必須是 Pipeline 中下一個(gè) Middleware
  • 必須包含一個(gè) Invoke 方法,它接收 Owin 環(huán)境字典,并返回 Task

使用Katana Helper來(lái)注冊(cè)Middleware

程序集 Microsoft.Owin 包含了 Katana 為我們提供的 Helper,通過(guò)他,可以簡(jiǎn)化我們的開(kāi)發(fā),比如 IOwinContext 封裝了 Owin 的環(huán)境字典,強(qiáng)類(lèi)型對(duì)象可以通過(guò)屬性的形式獲取相關(guān)數(shù)據(jù),同時(shí)為 IAppBuilder 提供了豐富的擴(kuò)展方法來(lái)簡(jiǎn)化 Middleware 的注冊(cè),如下所示:

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

   await next();

   await context.Response.WriteAsync("\t\t\t\tMiddleware 5--After(inline+katana helper)"+Environment.NewLine);

   });

當(dāng)然我們也可以定義一個(gè) Middleware 類(lèi)并繼承 OwinMiddleware,如下所示:

public class MyMiddleware : OwinMiddleware
{
   public MyMiddleware(OwinMiddleware next)
   : base(next)
   {

   }

   public override async Task Invoke(IOwinContext context)
   {
       await context.Response.WriteAsync("tttttMiddleware 6 - Before (Katana helped middleware class)"+Environment.NewLine);

       await this.Next.Invoke(context);

       await context.Response.WriteAsync("tttttMiddleware 6 - After (Katana helped middleware class)"+Environment.NewLine);

   }

  }

然后將其添加到 Pipeline 中:

 app.Use<mymiddleware>();

Middleware 的執(zhí)行順序

在完成上面 Middleware 注冊(cè)之后,在 Configuration 方法的最后添加最后一個(gè)的 Middleware 中間件,注意它并不需要對(duì)下一個(gè) Middleware 的引用了,我們可以使用 Run 方法來(lái)完成注冊(cè):

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

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

http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter8/1.png" alt="" />

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

http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter8/2.png" alt="" />

小結(jié)

在這篇文章中,我為大家講解了有關(guān)自定義 Middleware 的創(chuàng)建的相關(guān)知識(shí),Katana 為我們提供了非常多的方式來(lái)創(chuàng)建和注冊(cè) Middleware,在下一篇文章中,我將繼續(xù)為大家講解 OWIN 和 Katana 的其他知識(shí),和大家一起探索 Katana 和其他 Web Framework 的集成。