正如上篇文章所述那樣,OWIN在Web Server與Web Application之間定義了一套規範(Specs),意在解耦Web Server與Web Application,
從而推進跨平臺的實現。若要真正使用OWIN規範,那麼必須要對他們進行實現。目前有兩個產品實現了OWIN規範——由微軟主導的
Katana和第三方的Nowin。這篇文章,我主要關注還是Katana,由微軟團隊主導,開源到CodePlex上。
可以在Visual Studio中輸入命令:git clone https://git01.codeplex.com/katanaproject來檢視原始碼。
在介紹Katana之前,我覺得有必要為大家梳理一下過去10幾年前ASP.NET 發展歷程。
ASP.NET 發展歷程
ASP.NET Web Form
ASP.NET Web Form 在2002正式釋出時,面向的開發者主要有兩類:
- 使用混合HTML標記和服務端指令碼開發動態網站的ASP開發者,另外,ASP執行時抽象了底層的HTTP連線和Web Server,併為開發者提供了一系列的物件模型用於互動Http請求,當然也提供了額外的服務諸如Session、Cache、State等。
- 開發WinForm的程式設計師,他們可能對HTTP和HTML一無所知,但熟悉拖控制元件的方式來構建應用程式。
為了迎合這兩類開發者,ASP.NET Web Form通過使用沉重的ViewState來儲存頁面回傳過程中的狀態值,因為HTTP協議是無狀態的,通過ViewState,使原本沒有記憶的Http協議變得有記憶起來。這在當時是非常好的設計,能通過拖拽控制元件的形式快速開發Web,而不必過多的去關注底層原理。同時ASP.NET團隊還為ASP.NET豐富了更多的功能、諸如:Session、Cache、Configuration等等。
這在當時無疑是成功的,ASP.NET的釋出迅速拉攏了開發者,在Web開發中形成了一股新的勢力,但同時也買下來一些隱患:
- 所有的功能、特性都發布在一個整體框架上並且緊耦合核心的Web抽象庫——System.Web
- System.Web是.NET Framework的重要組成部分,這意味著要修復更新System.Web必須更新.NET Framework,但.NET Framework是作業系統的基礎,為了穩定性往往不會頻繁更新。
- ASP.NET Framework (System.Web)緊耦合IIS
- IIS只能執行在Windows系統
ASP.NET MVC
由於Web Form產生一大堆ViewState和客戶端指令碼,這對開發者來說慢慢變成一種累贅,因為我們只想產生純淨的HTML標記。所以開發者更想去主動控制而非被動產生額外HTML標記。
所以微軟基於MVC設計模式推出了其重要的Web Framework——ASP.NET MVC Framework,通過Model-View-Control解耦了業務邏輯和表現邏輯,同時沒有了伺服器端控制元件,將頁面的控制權完全交給了開發者。
為了快速更新迭代,通過Nuget來獲取更新,故從.NET Framework中分離開了。
但唯一不足的是,ASP.NET MVC還是基於ASP.NET Framework(注:ASP.NET MVC 6已經不依賴System.Web),所以Web Application和Web Server依舊沒有解耦。
ASP.NET Web API
隨著時間的推移,一些問題開始暴露出來了,由於Web Server和Web Application緊耦合在一起,微軟在開發獨立、簡單的Framework上越發捉襟見肘,這和其他平臺下開源社群蓬勃發展形成鮮明對比,幸運的是,微軟做出了改變,推出了獨立的Web Framework ——ASP.NET Web API,他適用於移動網際網路並可以快速通過Nuget安裝,更為重要的是,他不依賴System.Web,也不依賴IIS,你可以Self-Host或者在其他Web Server部署。
Katana
隨著Web API能夠執行在自己的輕量級的宿主中,並且越來越多簡單、模組化、專一的Framework問世,開發人員有時候不得不啟動單獨的程式來處理Web應用程式的各種元件(模組)、如靜態檔案、動態檔案、Web API和Socket。為了避免程式擴散,所有的程式必須啟動、停止並且獨立進行管理。這時,我們需要一個公共的宿主程式來管理這些模組。
這就是OWIN誕生的原因,解耦成最小粒度的元件,然後這些標準化框架和元件可以很容易地插入到OWIN Pipeline中,從而對元件進行統一管理。而Katana正是OWIN的實現,為我們提供了豐富的Host和Server。
走進Katana的世界
Katana作為OWIN的規範實現,除了實現Host和Server之外,還提供了一系列的API幫助開發應用程式,其中已經包括一些功能元件如身份驗證(Authentication)、診斷(Diagnostics)、靜態檔案處理(Static Files)、ASP.NET Web API和SignalR的繫結等。
Katana的基本原則
- 可移植性:從HostàServeràMiddleware,每個Pipeline中的元件都是可替換的,並且第三方公司和開源專案的Framework都是可以在OWIN Server上執行,也就是說不受平臺限制,從而實現跨平臺。
- 模組化:每一個元件都必須保持足夠獨立性,通常只做一件事,以混合模組的形式來滿足實際的開發需求
- 輕量和高效:因為每一個元件都是模組化開發,而且可以輕鬆的在Pipeline中插拔元件,實現高效開發
Katana 體系結構
Katana實現了OWIN的Layers,所以Katana的體系結構和OWIN一致,如下所示:
1.)Host :宿主Host被OWIN規範定義在第一層(最底層),他的職責是管理底層的程式(啟動、關閉)、初始化OWIN Pipeline、選擇Server執行等。
Katana為我們提供了3中選擇:
- IIS / ASP.NET :使用IIS是最簡單和向後相容方式,在這種場景中OWIN Pipeline通過標準的HttpModule和HttpHandler啟動。使用此Host你必須使用System.Web作為OWIN Server
- Custom Host :如果你想要使用其他Server來替換掉System.Web,並且可以有更多的控制權,那麼你可以選擇建立一個自定義宿主,如使用Windows Service、控制檯應用程式、Winform來承載Server。
- OwinHost :如果你對上面兩種Host還不滿意,那麼最後一個選擇是使用Katana提供的OwinHost.exe:他是一個命令列應用程式,執行在專案的根部,啟動HttpListener Server並找到基於約束的Startup啟動項。OwinHost提供了命令列選項來自定義他的行為,比如:手動指定Startup啟動項或者使用其他Server(如果你不需要預設的HttpListener Server)。
2.)Server
Host之後的Layer被稱為Server,他負責開啟套接字並監聽Http請求,一旦請求到達,根據Http請求來構建符合OWIN規範的Environment Dictionary(環境字典)並將它傳送到Pipeline中交由Middleware處理。Katana對OWIN Server的實現分為如下幾類:
- System.Web:如前所述那樣,System.Web和IIS/ASP.NET Host兩者彼此耦合,當你選擇使用System.Web作為Server ,Katana System.Web Server把自己註冊為HttpModule和HttpHandler並且處理髮送給IIS的請求,最後將HttpRequest、HttpResponse物件對映為OWIN環境字典並將它傳送至Pipeline中處理。
- HttpListener:這是OwinHost.exe和自定義Host預設的Server。
- WebListener:這是ASP.NET vNext預設的輕量級Server,他目前無法使用在Katana中
3)Middleware
Middleware(中介軟體)位於Host、Server之後,用來處理Pipeline中的請求,Middleware可以理解為實現了OWIN應用程式委託AppFun的元件。
Middleware處理請求之後並可以交由下一個Pipeline中的Middleware元件處理,即鏈式處理請求,通過環境字典可以獲取到所有的Http請求資料和自定義資料。Middleware可以是簡單的Log元件,亦可以為複雜的大型Web Framework,諸如:ASP.NET Web API、Nancy、SignlR等,如下圖所示:Pipeline中的Middleware用來處理請求:
4.)Application
最後一層即為Application,是具體的程式碼實現,比如ASP.NET Web API、SignalR具體程式碼的實現。
現在,我想你應該瞭解了什麼事Katana以及Katana的基本原則和體系結構,那麼現在就是具體應用到實際當中去了。
使用ASP.NET/IIS託管Katana-based應用程式
- Visual Studio建立Web Application
-
Install-Package Microsoft.Owin.Host.SystemWeb新增Startup啟動類
- ASP.NET/IIS作為Host
- System.Web作為Server
在Startup的Configuration方法中實現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方法將一個接受IOwinContext物件最為輸入引數並返回Task的Lambda表示式作為OWIN Pipeline的最後處理步驟,IOwinContext強型別物件是對Environment Dictionary的封裝,然後非同步輸出"Hello World"字串。
細心的你可能觀察到,在Nuget安裝Microsoft.Owin.Host.SystemWeb程式集時,預設安裝了依賴項Microsoft.Owin程式集,正式它為我們提供了擴充套件方法Run和IOwinContext介面,當然我們也可以使用最原始的方式來輸出"Hello World"字串,即Owin程式集為我們提供的最原始方式,這僅僅是學習上參考,雖然我們不會在正式場景下使用:
-
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(self-host)託管Katana-based應用程式
使用自定義Host託管Katana應用程式與使用IIS託管差別不大,你可以使用控制檯、WinForm、WPF等實現託管,但要記住,這會失去IIS帶有的一些功能(SSL、Event Log、Diagnostics、Management…),當然這可以自己來實現。
- 建立控制檯應用程式
- Install-Package Microsoft.Owin.SelfHost
- 在Main方法中使用Startup配置項構建Pipeline並監聽埠
-
static void Main(string[] args)
-
{
-
using (WebApp.Start<Startup>("http://localhost:10002"))
-
{
-
System.Console.WriteLine("啟動站點:http://localhost:10002");
-
System.Console.ReadLine();
-
}
-
}
使用自定義的Host將失去IIS的一些功能,當然我們可以自己去實現。幸運的是,Katana為我們預設實現了部分功能,比如Diagnostic,包含在程式集Microsoft.Owin.Diagnostic中。
-
public void Configuration(IAppBuilder app)
-
{
-
app.UseWelcomePage("/");
-
app.UseErrorPage();
-
app.Run(context =>
-
{
-
//將請求記錄在控制檯
-
Trace.WriteLine(context.Request.Uri);
-
//顯示錯誤頁
-
if (context.Request.Path.ToString().Equals("/error"))
-
{
-
throw new Exception("丟擲異常");
-
}
-
context.Response.ContentType = "text/plain";
-
return context.Response.WriteAsync("Hello World");
-
});
-
}
在上述程式碼中,當請求的路徑(Request.Path)為根目錄時,渲染輸出Webcome Page並且不繼續執行Pipeline中的其餘Middleware元件,如下所示:
如果請求的路徑為Error時,丟擲異常,顯示錯誤頁,如下所示:
使用OwinHost.exe託管Katana-based應用程式
當然我們還可以使用Katana提供的OwinHost.exe來託管應用程式,毫無疑問,通過Nuget來安裝OwinHost。
如果你按照我的例子一步一步執行的話,你會發現不管使用ASP.NET/IIS託管還是自託管,Startup配置類都是不變的,改變的僅僅是託管方式。同理OwinHost也是一樣的,但它更靈活,我們可以使用類庫或者Web應用程式來作為Application。
- 使用類庫
類庫作為Application,可以最小的去引用程式集,建立一個類庫後,刪除預設的Class1.cs,然後並且新增Startup啟動項,這會預設像類庫中新增Owin和Microsoft.Owin程式集的引用。
然後,使用Nuget來安裝OwinHost.exe,如Install-Package OwinHost,注意它並不是一個程式集,而是.exe應用程式位於<solution root>/packages/OwinHost.(version)/tools資料夾。
因為類庫不能直接執行,那麼只能在它的根目錄呼叫OwinHost.exe來託管,它將載入.\bin檔案下所有的程式集,所以需要改變類庫的預設輸出,如下所示:
然後編譯解決方案,開啟cmd,鍵入如下命令:
如上圖成功啟動了宿主Host並且預設監聽5000埠。
OwinHost.exe還提供自定義引數,通過追加-h來檢視,如下所示:
既然類庫不能直接執行,當然你也不能直接進行除錯,我們可以附加OwinHost程式來進行除錯,如下所示:
注:
我在使用OwinHost.exe 3.0.1時,Startup如果是如下情況下,它提示轉換失敗,不知是否是該版本的Bug。
-
using AppFunc = Func<IDictionary<string, object>, Task>;
-
public class Startup
-
{
-
public void Configuration(IAppBuilder app)
-
{
-
//使用OwinHost.exe,報錯,提示轉換失敗
-
app.Run(context=>context.Response.WriteAsync("Hello World"));
-
//使用OwinHost.exe 不報錯
-
//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);
-
//})));
-
}
-
}
報錯資訊如下:
- 使用Web Application
Web Application比類庫使用起來輕鬆多了,你可以直接執行和除錯,唯一比較弱的可能是它引用較多的程式集,你完全可以刪掉,比如System.Web。
通過Nuget安裝了OwinHost.exe之後,可以在Web中使用它,如下所示:
幾種指定啟動項Startup的方法
- 預設名稱約束:預設情況下Host會去查詢root namespace下的名為Startup的類作為啟動項。
- OwinStartup Attribute:當建立Owin Startup類時,自動會加上Attribute 如:[assembly: OwinStartup(typeof(JKXY.KatanaDemo.OwinHost.Startup))]
- 配置檔案,如: <add key="owin:appStartup" value="JKXY.KatanaDemo.Web.StartupDefault" />
- 如果使用自定義Host,那麼可以通過WebApp.Start<Startup>("http://localhost:10002") 來設定啟動項。
- 如果使用OwinHost,那麼可以通過命令列引數來實現,如下截圖所示
啟動項Startup的高階應用
啟動項Startup支援Friendly Name,通過Friendly Name我們可以動態切換Startup。
比如在部署時,我們會有UAT環境、Production環境,在不同的環境中我們可以動態切換Startup來執行不同的操作。
舉個栗子,我建立來兩個帶有Friendly Name的Startup,如下所示:
- Production Startup
1 [assembly: OwinStartup("Production", typeof(JKXY.KatanaDemo.Web.StartupProduction))] 2 namespace JKXY.KatanaDemo.Web 3 { 4 using AppFunc = Func<IDictionary<string, object>, Task>; 5 public class StartupProduction 6 { 7 public void Configuration(IAppBuilder app) 8 { 9 app.Run(context=>context.Response.WriteAsync("Production")); 10 } 11 } 12 }
- UAT Startup
1 [assembly: OwinStartup("UAT",typeof(JKXY.KatanaDemo.Web.StartupUAT))] 2 3 namespace JKXY.KatanaDemo.Web 4 { 5 public class StartupUAT 6 { 7 public void Configuration(IAppBuilder app) 8 { 9 app.Run(context=>context.Response.WriteAsync("UAT")); 10 } 11 } 12 }
- 根據Friendly Name使用配置檔案或者OwinHost 引數來切換Startup
<appSettings> <add key="owin:appStartup" value="Production" /> </appSettings>
小結
花了好久才最終完成這篇部落格,為大家講解了Katana的世界,那麼接下來我將繼續OWIN & Katana之旅,探索Middleware的建立,謝謝大家支援。