正如上篇文章所講解的,OWIN 在 Web Server 與 Web Application 之間定義了一套規(guī)范(Specs),意在解耦 Web Server 與 Web Application,從而推進(jìn)跨平臺(tái)的實(shí)現(xiàn)。若要真正使用 OWIN 規(guī)范,那么必須要對他們進(jìn)行實(shí)現(xiàn)。目前有兩個(gè)產(chǎn)品實(shí)現(xiàn)了 OWIN 規(guī)范——一是由微軟主導(dǎo)的 Katana,二是第三方的 Nowin。本文主要關(guān)注的還是 Katana,由微軟團(tuán)隊(duì)主導(dǎo),開源到 CodePlex 上。
在介紹 Katana 之前,我覺得有必要先為大家梳理一下十幾年以來 ASP.NET 發(fā)展歷程。
ASP.NET Web Form 在 2002 正式發(fā)布時(shí),面向的開發(fā)者主要有兩類:
為了迎合這兩類開發(fā)者,ASP.NET Web Form 通過使用沉重的 ViewState 來保存頁面回傳過程中的狀態(tài)值,因?yàn)?HTTP 協(xié)議是無狀態(tài)的,通過 ViewState,使原本沒有記憶的 Http 協(xié)議變得有記憶起來。這在當(dāng)時(shí)是非常好的設(shè)計(jì),能通過拖拽控件的形式快速開發(fā) Web,而不必過多的去關(guān)注底層原理。同時(shí) ASP.NET 團(tuán)隊(duì)還為 ASP.NET 豐富了更多的功能,諸如:Session、Cache、Configuration 等等。
這在當(dāng)時(shí)無疑是成功的,ASP.NET 的發(fā)布迅速拉攏了開發(fā)者,在 Web 開發(fā)中形成了一股新的勢力,但同時(shí)也買下來一些隱患:
由于 Web Form 產(chǎn)生一大堆 ViewState 和客戶端腳本,這對開發(fā)者來說慢慢變成一種累贅,因?yàn)槲覀冎幌氘a(chǎn)生純凈的 HTML 標(biāo)記。所以開發(fā)者更想去主動(dòng)控制而非被動(dòng)產(chǎn)生額外 HTML 標(biāo)記。
所以微軟基于 MVC 設(shè)計(jì)模式推出了其重要的 Web Framework——ASP.NET MVC Framework,通過 Model-View-Control 解耦了業(yè)務(wù)邏輯和表現(xiàn)邏輯,同時(shí)沒有了服務(wù)器端控件,將頁面的控制權(quán)完全交給了開發(fā)者。
為了快速更新迭代,通過 Nuget 來獲取更新,故從.NET Framework 中分離開了。但唯一不足的是,ASP.NET MVC 還是基于 ASP.NET Framework(注:ASP.NET MVC 6 已經(jīng)不依賴 System.Web),所以 Web Application 和 Web Server 依舊沒有解耦。
隨著時(shí)間的推移,一些問題開始暴露出來了,由于 Web Server 和 Web Application 緊耦合在一起,微軟在開發(fā)獨(dú)立、簡單的 Framework 上越發(fā)捉襟見肘,這和其他平臺(tái)下開源社區(qū)蓬勃發(fā)展形成鮮明對比,幸運(yùn)的是微軟做出了改變,推出了獨(dú)立的 Web Framework ——ASP.NET Web API,它適用于移動(dòng)互聯(lián)網(wǎng)并可以快速通過 Nuget 安裝,更為重要的是,它不依賴 System.Web,也不依賴 IIS,你可以使用 Self-Host 或者在其他 Web Server 部署。
隨著 Web API 能夠運(yùn)行在自己的輕量級(jí)的宿主中,并且越來越多簡單、模塊化、專一的 Framework 問世,開發(fā)人員有時(shí)候不得不啟動(dòng)單獨(dú)的進(jìn)程來處理 Web 應(yīng)用程序的各種組件(模塊)、如靜態(tài)文件、動(dòng)態(tài)文件、Web API 和 Socket。為了避免進(jìn)程擴(kuò)散,所有的進(jìn)程必須啟動(dòng)、停止并且獨(dú)立進(jìn)行管理。這時(shí),我們需要一個(gè)公共的宿主進(jìn)程來管理這些模塊。
這就是 OWIN 誕生的原因,解耦成最小粒度的組件,然后這些標(biāo)準(zhǔn)化框架和組件可以很容易地插入到 OWIN Pipeline 中,從而對組件進(jìn)行統(tǒng)一管理。而 Katana 正是 OWIN 的實(shí)現(xiàn),為我們提供了豐富的 Host 和 Server。
Katana 作為 OWIN 的規(guī)范實(shí)現(xiàn),除了實(shí)現(xiàn) Host 和 Server 之外,還提供了一系列的 API 幫助開發(fā)應(yīng)用程序,其中已經(jīng)包括一些功能組件如身份驗(yàn)證(Authentication)、診斷(Diagnostics)、靜態(tài)文件處理(Static Files)、ASP.NET Web API 和 SignalR 的綁定等。
Katana 實(shí)現(xiàn)了 OWIN 的 Layers,所以 Katana 的體系結(jié)構(gòu)和 OWIN 一致,如下所示:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/2.png" alt="" />
1.)Host :宿主 Host 被 OWIN 規(guī)范定義在第一層(最底層),他的職責(zé)是管理底層的進(jìn)程(啟動(dòng)、關(guān)閉)、初始化 OWIN Pipeline、選擇 Server 運(yùn)行等。
Katana 為我們提供了 3 種選擇:
2.)Server
Host 之后的 Layer 被稱為 Server,他負(fù)責(zé)打開套接字并監(jiān)聽 Http 請求,一旦請求到達(dá),根據(jù)Http 請求來構(gòu)建符合 OWIN 規(guī)范的 Environment Dictionary(環(huán)境字典)并將它發(fā)送到 Pipeline 中交由 Middleware 處理。Katana 對 OWIN Server 的實(shí)現(xiàn)分為如下幾類:
3)Middleware
Middleware(中間件)位于 Host、Server 之后,用來處理 Pipeline 中的請求,Middleware 可以理解為實(shí)現(xiàn)了 OWIN 應(yīng)用程序委托 AppFun 的組件。
Middleware 處理請求之后并可以交由下一個(gè) Pipeline 中的 Middleware 組件處理,即鏈?zhǔn)教幚碚埱?,通過環(huán)境字典可以獲取到所有的 Http 請求數(shù)據(jù)和自定義數(shù)據(jù)。Middleware 可以是簡單的 Log 組件,亦可以為復(fù)雜的大型 Web Framework,諸如:ASP.NET Web API、Nancy、SignlR 等,如下圖所示:Pipeline 中的 Middleware 用來處理請求:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/3.png" alt="" />
4.)Application
最后一層即為 Application,是具體的代碼實(shí)現(xiàn),比如 ASP.NET Web API、SignalR 具體代碼的實(shí)現(xiàn)。
現(xiàn)在,我想你應(yīng)該了解了什么事 Katana 以及 Katana 的基本原則和體系結(jié)構(gòu),那么現(xiàn)在就是具體應(yīng)用到實(shí)際當(dāng)中去了。
在 Startup 的 Configuration 方法中實(shí)現(xiàn) OWIN Pipeline 處理邏輯,如下代碼所示:
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Run(context =>
{
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync("Hello World");
});
}
}
app.Run 方法將一個(gè)接受 IOwinContext 對象最為輸入?yún)?shù)并返回 Task 的 Lambda 表達(dá)式作為 OWIN Pipeline 的最后處理步驟,IOwinContext 強(qiáng)類型對象是對 Environment Dictionary 的封裝,然后異步輸出"Hello World"字符串。
細(xì)心的你可能觀察到,在 Nuget 安裝 Microsoft.Owin.Host.SystemWeb 程序集時(shí),默認(rèn)安裝了依賴項(xiàng) Microsoft.Owin 程序集,正式它為我們提供了擴(kuò)展方法 Run 和 IOwinContext 接口,當(dāng)然我們也可以使用最原始的方式來輸出"Hello World"字符串,即 Owin 程序集為我們提供的最原始方式,這僅僅是學(xué)習(xí)上參考,雖然我們不會(huì)在正式場景下使用:
using AppFunc = Func<IDictionary<string, object>, Task>;
public class Startup
{
public void Configuration(IAppBuilder app)
{
app.Use(new Func<AppFunc, AppFunc>(next => (env =>
{
string text = "Hello World";
var response = env["owin.ResponseBody"] as Stream;
var headers = env["owin.ResponseHeaders"] as IDictionary<string, string[]>;
headers["Content-Type"] = new[] { "text/plain" };
return response.WriteAsync(Encoding.UTF8.GetBytes(text), 0, text.Length);
})));
}
}
使用自定義 Host 托管 Katana 應(yīng)用程序與使用 IIS 托管差別不大,你可以使用控制臺(tái)、WinForm、WPF 等實(shí)現(xiàn)托管,但要記住,這會(huì)失去 IIS 帶有的一些功能(SSL、Event Log、Diagnostics、Management…),當(dāng)然這可以自己來實(shí)現(xiàn)。
在 Main 方法中使用 Startup 配置項(xiàng)構(gòu)建 Pipeline 并監(jiān)聽端口
using (WebApp.Start("http://localhost:10002"))
{
System.Console.WriteLine("啟動(dòng)站點(diǎn):http://localhost:10002");
System.Console.ReadLine();
}
使用自定義的 Host 將失去 IIS 的一些功能,當(dāng)然我們可以自己去實(shí)現(xiàn)。幸運(yùn)的是,Katana 為我們默認(rèn)實(shí)現(xiàn)了部分功能,比如 Diagnostic,包含在程序集 Microsoft.Owin.Diagnostic 中。
public void Configuration(IAppBuilder app)
{
app.UseWelcomePage("/");
app.UseErrorPage();
app.Run(context =>
{
//將請求記錄在控制臺(tái)
Trace.WriteLine(context.Request.Uri);
//顯示錯(cuò)誤頁
if (context.Request.Path.ToString().Equals("/error"))
{
throw new Exception("拋出異常");
}
context.Response.ContentType = "text/plain";
return context.Response.WriteAsync("Hello World");
});
}
在上述代碼中,當(dāng)請求的路徑(Request.Path)為根目錄時(shí),渲染輸出 Webcome Page并且不繼續(xù)執(zhí)行 Pipeline 中的其余 Middleware 組件,如下所示:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/4.png" alt="" />
如果請求的路徑為 Error時(shí),拋出異常,顯示錯(cuò)誤頁,如下所示:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/5.png" alt="" />
當(dāng)然我們還可以使用 Katana 提供的 OwinHost.exe 來托管應(yīng)用程序,毫無疑問,通過 Nuget 來安裝 OwinHost。
如果你按照我的例子一步一步執(zhí)行的話,你會(huì)發(fā)現(xiàn)不管使用 ASP.NET/IIS 托管還是自托管,Startup 配置類都是不變的,改變的僅僅是托管方式。同理 OwinHost 也是一樣的,但它更靈活,我們可以使用類庫或者 Web 應(yīng)用程序來作為 Application。
類庫作為 Application,可以最小的去引用程序集,創(chuàng)建一個(gè)類庫后,刪除默認(rèn)的 Class1.cs,然后并且添加 Startup 啟動(dòng)項(xiàng),這會(huì)默認(rèn)像類庫中添加 Owin 和 Microsoft.Owin 程序集的引用。
然后,使用 Nuget 來安裝 OwinHost.exe,如 Install-Package OwinHost,注意它并不是一個(gè)程序集,而是.exe 應(yīng)用程序位于
因?yàn)轭悗觳荒苤苯舆\(yùn)行,那么只能在它的根目錄調(diào)用 OwinHost.exe 來托管,它將加載.bin 文件下所有的程序集,所以需要改變類庫的默認(rèn)輸出,如下所示:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/6.png" alt="" />
然后編譯解決方案,打開 cmd,鍵入如下命令:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/7.png" alt="" />
如上圖成功啟動(dòng)了宿主 Host 并且默認(rèn)監(jiān)聽 5000 端口。
OwinHost.exe 還提供自定義參數(shù),通過追加-h 來查看,如下所示:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/8.png" alt="" />
既然類庫不能直接運(yùn)行,當(dāng)然你也不能直接進(jìn)行調(diào)試,我們可以附加 OwinHost 進(jìn)程來進(jìn)行調(diào)試,如下所示:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/9.png" alt="" />
注:我在使用 OwinHost.exe 3.0.1 時(shí),Startup 如果是如下情況下,它提示轉(zhuǎn)換失敗,不知是否是該版本的 Bug。
using AppFunc = Func<idictionary<string, object="">, Task>;
public class Startup
{
public void Configuration(IAppBuilder app)
{
//使用OwinHost.exe,報(bào)錯(cuò),提示轉(zhuǎn)換失敗
app.Run(context=>context.Response.WriteAsync("Hello World"));
//使用OwinHost.exe 不報(bào)錯(cuò)
//app.Use(new Func<appfunc, appfunc="">(next => (env =>
//{
// string text = "Hello World";
// var response = env["owin.ResponseBody"] as Stream;
// var headers = env["owin.ResponseHeaders"] as IDictionary<string, string[]="">;
// headers["Content-Type"] = new[] { "text/plain" };
// return response.WriteAsync(Encoding.UTF8.GetBytes(text), 0, text.Length);
//})));
}
}
報(bào)錯(cuò)信息如下:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/10.png" alt="" />
Web Application 比類庫使用起來輕松多了,你可以直接運(yùn)行和調(diào)試,唯一比較弱的可能是它引用較多的程序集,你完全可以刪掉,比如 System.Web。
通過 Nuget 安裝了 OwinHost.exe 之后,可以在 Web 中使用它,如下所示:
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/11.png" alt="" />
http://wiki.jikexueyuan.com/project/think-in-asp-net-mvc/images/Chapter7/12.png" alt="" />
啟動(dòng)項(xiàng) Startup 支持 Friendly Name,通過 Friendly Name 我們可以動(dòng)態(tài)切換 Startup。
比如在部署時(shí),我們會(huì)有 UAT 環(huán)境、Production 環(huán)境,在不同的環(huán)境中我們可以動(dòng)態(tài)切換 Startup 來執(zhí)行不同的操作。
舉個(gè)例子,我創(chuàng)建來兩個(gè)帶有 Friendly Name 的 Startup,如下所示:
[assembly: OwinStartup("Production", typeof(JKXY.KatanaDemo.Web.StartupProduction))]
namespace JKXY.KatanaDemo.Web
{
using AppFunc = Func<idictionary<string, object="">, Task>;
public class StartupProduction
{
public void Configuration(IAppBuilder app)
{
app.Run(context=>context.Response.WriteAsync("Production"));
}
}
}
[assembly: OwinStartup("UAT",typeof(JKXY.KatanaDemo.Web.StartupUAT))]
namespace JKXY.KatanaDemo.Web
{
public class StartupUAT
{
public void Configuration(IAppBuilder app)
{
app.Run(context=>context.Response.WriteAsync("UAT"));
}
}
}
根據(jù) Friendly Name 使用配置文件或者 OwinHost 參數(shù)來切換 Startup
<appsettings>
<add key="owin:appStartup" value="Production">
</add></appsettings>
這篇博客為大家講解了 Katana 的世界,那么接下來我將繼續(xù) OWIN & Katana 之旅,探索 Middleware 的創(chuàng)建,謝謝大家支持。