ASP.NET MVC隨想錄——鋒利的KATANA

隨夢而飛發表於2019-02-23

【轉自】ASP.NET MVC隨想錄——鋒利的KATANA

 

正如上篇文章所述那樣,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處理邏輯,如下程式碼所示:

  1. public class Startup
  2.     {
  3.         public void Configuration(IAppBuilder app)
  4.         {
  5.             app.Run(context =>
  6.             {
  7.                 context.Response.ContentType = "text/plain";
  8.                 return context.Response.WriteAsync("Hello World");
  9.             });
  10.         }
  11.     }

app.Run方法將一個接受IOwinContext物件最為輸入引數並返回Task的Lambda表示式作為OWIN Pipeline的最後處理步驟,IOwinContext強型別物件是對Environment Dictionary的封裝,然後非同步輸出"Hello World"字串。

細心的你可能觀察到,在Nuget安裝Microsoft.Owin.Host.SystemWeb程式集時,預設安裝了依賴項Microsoft.Owin程式集,正式它為我們提供了擴充套件方法Run和IOwinContext介面,當然我們也可以使用最原始的方式來輸出"Hello World"字串,即Owin程式集為我們提供的最原始方式,這僅僅是學習上參考,雖然我們不會在正式場景下使用:

  1. using AppFunc = Func<IDictionary<string, object>, Task>;
  2.     public class Startup
  3.     {
  4.         public void Configuration(IAppBuilder app)
  5.         {
  6.             app.Use(new Func<AppFunc, AppFunc>(next => (env =>
  7.             {
  8.                 string text = "Hello World";
  9.                 var response = env["owin.ResponseBody"] as Stream;
  10.                 var headers = env["owin.ResponseHeaders"] as IDictionary<string, string[]>;
  11.                 headers["Content-Type"] = new[] { "text/plain" };
  12.                 return response.WriteAsync(Encoding.UTF8.GetBytes(text), 0, text.Length);
  13.             })));
  14.         }
  15.     }

使用自定義Host(self-host)託管Katana-based應用程式

使用自定義Host託管Katana應用程式與使用IIS託管差別不大,你可以使用控制檯、WinForm、WPF等實現託管,但要記住,這會失去IIS帶有的一些功能(SSL、Event Log、Diagnostics、Management…),當然這可以自己來實現。

  • 建立控制檯應用程式
  • Install-Package Microsoft.Owin.SelfHost
  • 在Main方法中使用Startup配置項構建Pipeline並監聽埠
  1. static void Main(string[] args)
  2.         {
  3.             using (WebApp.Start<Startup>("http://localhost:10002"))
  4.             {
  5.                 System.Console.WriteLine("啟動站點:http://localhost:10002");
  6.                 System.Console.ReadLine();
  7.             }
  8.         }

使用自定義的Host將失去IIS的一些功能,當然我們可以自己去實現。幸運的是,Katana為我們預設實現了部分功能,比如Diagnostic,包含在程式集Microsoft.Owin.Diagnostic中。

  1. public void Configuration(IAppBuilder app)
  2.         {
  3.             app.UseWelcomePage("/");
  4.             app.UseErrorPage();
  5.             app.Run(context =>
  6.             {
  7.                 //將請求記錄在控制檯
  8.                 Trace.WriteLine(context.Request.Uri);
  9.                 //顯示錯誤頁
  10.                 if (context.Request.Path.ToString().Equals("/error"))
  11.                 {
  12.                     throw new Exception("丟擲異常");
  13.                 }
  14.                 context.Response.ContentType = "text/plain";
  15.                 return context.Response.WriteAsync("Hello World");
  16.             });
  17.         }

在上述程式碼中,當請求的路徑(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。

  1. using AppFunc = Func<IDictionary<string, object>, Task>;
  2.    public class Startup
  3.    {
  4.        public void Configuration(IAppBuilder app)
  5.        {
  6.            //使用OwinHost.exe,報錯,提示轉換失敗
  7.            app.Run(context=>context.Response.WriteAsync("Hello World"));
  8.            //使用OwinHost.exe 不報錯
  9.            //app.Use(new Func<AppFunc, AppFunc>(next => (env =>
  10.            //{
  11.            // string text = "Hello World";
  12.            // var response = env["owin.ResponseBody"] as Stream;
  13.            // var headers = env["owin.ResponseHeaders"] as IDictionary<string, string[]>;
  14.            // headers["Content-Type"] = new[] { "text/plain" };
  15.            // return response.WriteAsync(Encoding.UTF8.GetBytes(text), 0, text.Length);
  16.            //})));
  17.        }
  18.    }

報錯資訊如下:

  • 使用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的建立,謝謝大家支援。

本部落格為木宛城主原創,基於Creative Commons Attribution 2.5 China Mainland License釋出,歡迎轉載,演繹或用於商業目的,但是必須保留本文的署名木宛城主(包含連結)。如您有任何疑問或者授權方面的協商,請給我留言。

相關文章