經(jīng)過(guò)前 2 篇文章的介紹,相信大家已經(jīng)對(duì) OWIN 和 Katana 有了基本的了解,那么這篇文章我將繼續(xù)OWIN 和 Katana 之旅——?jiǎng)?chuàng)建自定義的 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 中最后的處理元素。
使用 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)鍵字。
為了簡(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)表示。
如果你只想簡(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
程序集 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 注冊(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="" />
在這篇文章中,我為大家講解了有關(guān)自定義 Middleware 的創(chuàng)建的相關(guān)知識(shí),Katana 為我們提供了非常多的方式來(lái)創(chuàng)建和注冊(cè) Middleware,在下一篇文章中,我將繼續(xù)為大家講解 OWIN 和 Katana 的其他知識(shí),和大家一起探索 Katana 和其他 Web Framework 的集成。