Katana 项目入门
當 ASP.NET 首次在 2002 年發布時,時代有所不同。 那時,Internet 仍處于起步階段,大約有 5.69 億用戶,每個用戶平均每天訪問 Internet 的時間為 46 分鐘,大約有 3 百萬個網站。 僅僅在 10 年之后,相同的測量指標揭示,大約有 22.7 億個 Internet 用戶,每個用戶平均每天訪問 Internet 的時間為 4 小時,大約有 5.55 億個網站(請參閱 bit.ly/MY7GzO)。
很顯然,這種增長相應地刺激了對應用程序開發者的需求變化,具體表現在生成和運行 Web 應用程序的基礎框架、工具和運行時等方面。 現代 Web 應用程序必須能夠快速地發展,充分利用許多不同的組件和框架。它們的占用空間必須很小,這樣才能在云的大型運行時環境中有效地運行。
確保 ASP.NET 能夠滿足這些當前需求和未來需求正是 Katana 項目的主要目標。
##Katana 簡介
Katana 項目實際可以追溯到 Microsoft 外部一個名為 Open Web Inter-face for .NET (OWIN) 的開放源代碼項目。OWIN 是一種定義 Web 服務器和應用程序組件之間的交互的規范(請參閱 owin.org)。 由于這一規范的目的是發展一個廣闊且充滿活力的、基于 Microsoft .NET Framework 的 Web 服務器和應用程序組件生態系統,因此它可以將服務器與應用程序之間的交互減少到一小部分類型和單個函數簽名,這個函數簽名被稱為應用程序委托(即 AppFunc):
using AppFunc = Func<IDictionary<string, object>, Task>;基于 OWIN 的應用程序中的每個組件都向服務器提供應用程序委托。然后,這些組件鏈接成一個管道,基于 OWIN 的服務器將會向該管道推送請求。為了更有效地使用資源,管道中的所有組件都應該是異步的,這體現在返回 Task 對象的應用程序委托中。
包括應用程序狀態、請求狀態和服務器狀態等在內的所有狀態都保存在應用程序委托上指定的 IDictionary<string, object> 對象中。這種數據結構稱為環境字典,隨著請求通過管道時會從一個組件傳遞到另一個組件。雖然任何鍵/值數據都可以插入到環境字典中,但 OWIN 規范為某些 HTTP 核心元素定義了鍵,如圖 1 中所示。
| Yes | owin.RequestBody | A Stream with the request body, if any. Stream.Null MAY be used as a placeholder if there is no request body. See Request Body. |
| Yes | owin.RequestHeaders | An IDictionary<string, string[]> of request headers. See Headers. |
| Yes | owin.RequestMethod | A string containing the HTTP request method of the request (e.g., “GET”, “POST”). |
| Yes | owin.RequestPath | A string containing the request path. The path MUST be relative to the “root” of the application delegate. See Paths. |
| Yes | owin.RequestPathBase | A string containing the portion of the request path corresponding to the “root” of the application delegate; see Paths. |
| Yes | owin.RequestProtocol | A string containing the protocol name and version (e.g. “HTTP/1.0” or “HTTP/1.1”). |
| Yes | owin.RequestQueryString | A string containing the query string component of the HTTP request URI, without the leading “?” (e.g., “foo=bar&baz=quux”). The value may be an empty string. |
| Yes | owin.RequestScheme | A string containing the URI scheme used for the request (e.g., “http”, “https”); see URI Scheme. |
定義一組基本的環境字典鍵/值對,使得許多不同的框架和組件作者可以在一個 OWIN 管道中進行互操作,而不必強制實施對特定 .NET 對象模型的協議,例如針對 ASP.NET MVC 中的 HttpContextBase 或 ASP.NET Web API 中的 HttpRequestMessage/HttpResponseMessage 的協議。
應用程序委托和環境字典這兩個元素構成了 OWIN 規范。Katana 項目是 Microsoft 創建和推出的基于 OWIN 的組件和框架集合。
Katana 組件可以通過體系結構堆棧查看,如圖 2 中所示。
圖 2 Katana 項目體系結構
此堆棧由以下層組成:
- 主機:運行應用程序的進程,可以是從 IIS 或獨立可執行文件到您自己的自定義程序的任何內容。主機負責啟動、加載其他 OWIN組件和正常關閉。
- 服務器:負責綁定到 TCP 端口,構造環境字典和通過 OWIN 管道處理請求。
- 中間件:這是為處理 OWIN 管道中的請求的所有組件指定的名稱。它可以是從簡單壓縮組件到 ASP.NET Web API這樣的完整框架,不過從服務器的角度而言,它只是一個公開應用程序委托的組件。
- 應用程序:這是您的代碼。由于 Katana 并不取代 ASP.NET,而是一種編寫和托管組件的新方式,因此現有的 ASP.NET Web API 和 SignalR 應用程序將保持不變,因為這些框架可以參與 OWIN 管道。事實上,對于這些類型的應用程序,Katana 組件只需使用一個小的配置類即可見。
在體系結構方面,Katana 可以按其中每個層都能夠輕松替代的方式分解,通常不需要重新生成代碼。在處理 HTTP 請求時,各個層一起工作,方式類似于圖 3 中所示的數據流。
圖 3 Katana 中數據流的示例
##使用 Katana 生成現代 Web 應用程序
現代 Web 應用程序通常具有四項功能:
使用所有這些功能生成應用程序時,需要專用于相關功能的各種不同框架。不過,從這些框架編寫應用程序頗具挑戰性,當前要求在 IIS 上托管不同的應用程序部分,可能需要使用應用程序和虛擬目錄將這些部分相互隔離。
與此相對的是,通過 Katana 可以利用各種不同的 Web 技術編寫現代 Web 應用程序,然后在所需的任何位置托管該應用程序,并且只在一個 HTTP 端點下公開。這樣可提供多種優勢:
- 部署過程非常簡單,因為只涉及一個應用程序,而不是每種功能一個應用程序。
- 可以添加驗證之類的額外功能,這些功能可應用于管道中的所有下游組件。
- 不同的組件,無論是 Microsoft 還是第三方組件,都可以通過環境字典對同一個請求狀態執行操作。
現在,我將研究一個示例應用程序。該應用程序具有您應該熟悉的域:錯誤跟蹤。該應用程序將提供一組處于各種不同狀態(積壓、正在處理和已完成)的錯誤,并且我可以將錯誤在這些狀態之間變換。此外,由于許多人可能正在同時管理錯誤,當錯誤的狀態發生變化時,應用程序將會實時更新所有瀏覽器。下面是我將用于生成應用程序的內容:Nancy (nancyfx.org) 用于服務器端標記生成和靜態文件服務;ASP.NET Web API (asp.net/web-api) 用于處理 AJAX 請求;而 SignalR (signalr.net) 用于實時消息服務。
另外,我不打算花費很多時間在瀏覽器客戶端的標記和腳本上,我將使用 Knockout.js 來將 HTML 標記與 Web API 及 SignalR 數據分隔開。
需要牢記的首要原則是,我正在將所有這些不同的框架編寫到一個 OWIN 管道中,這樣當新功能可用時,我只需將這些功能插入到管道,即可將它們添加到應用程序中。
##開始使用
Katana 的其中一個目標就是能夠讓您精確地控制添加到應用程序中的功能(這樣您就可以精確控制在每個請求的處理性能方面的工作)。牢記這一點后,我將從在 Visual Studio 2013 Preview 中創建一個空的新 ASP.NET Web 應用程序項目開始,如圖 4 中所示。
圖 4 Visual Studio 2013 Preview 中的新 ASP.NET Web 應用程序項目
即使是空的 Web 項目模板也能提供有用的功能,因為在默認情況下,它們會將編譯的程序集直接放在 /bin 文件夾而不是 /bin/debug 文件夾中(這一點在其他項目類型中很常見)。默認的 Katana 主機會在此 /bin 文件夾中查找程序集。您可以將基于 Katana 的應用程序創建為類庫,但您需要修改項目屬性以便符合此結構,或者提供自己的自定義應用程序加載程序,該加載程序能夠在其他文件夾結構中搜索程序集和類型。
下一步,我將使用 Nancy Web 框架生成服務器端標記生成代碼。
Nancy 的語法非常簡潔,可以快速輕松地生成基于 HTTP 的站點和服務。本練習中更重要的事項是,與 ASP.NET Web API 類似,該應用程序與 System.Web.dll 沒有任何依賴關系,并且它的構建目的是為了在 OWIN 管道中運行。諸如 ASP.NET MVC 之類的框架與 System.Web.dll 具有依賴關系(截止編寫本文時),這使得它們不太適合用于非 IIS 托管方案。
大多數情況下,在您向應用程序添加新功能時,您將從添加 NuGet 程序包開始。(您可以在 docs.nuget.org 上詳細了解 NuGet。)在編寫本文時,此處使用的許多程序包都是預發布版本,因此請確保可在 NuGet 對話框中顯示預發布程序包。
為了將 Nancy 添加到應用程序中,我只需安裝 Nancy NuGet 程序包即可。不過,因為我還希望在 OWIN 管道中運行 Nancy,我打算安裝 Nancy.Owin 程序包 (nuget.org/packages/nancy.owin)。這會將 Nancy 程序包作為依賴關系安裝,并且提供額外的輔助方法在我的 OWIN 管道中配置 Nancy。
下一步,我需要創建 Nancy 模塊(類似于模型視圖控制器,或者稱 MVC、控制器)來處理請求,還需要創建視圖來向瀏覽器顯示內容。下面是模塊 (HomeModule.cs) 的代碼:
public class HomeModule : NancyModule {public HomeModule() {Get["/"] = _ => {var model = new { title = "We've Got Issues..." };return View["home", model];};} }正如您看到的那樣,對于聲明定向到應用程序根 (“/”) 的請求的模塊,應該由關聯 lambda 中定義的匿名委托來處理。 該函數會創建一個包含頁標題的模型,并指示 Nancy 呈現“主”視圖,并將模型傳遞到視圖。 如圖 5 中所示,該視圖會將模型的標題屬性同時插入到頁標題和 h1 元素中。
圖 5 Home.html
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head><title>@Model.title</title> </head><body><header><h1>@Model.title</h1> </header><section><h2>Backlog</h2><ul class="bugs" id="backlog"><li>a bug</li></ul></section><section><h2>Working</h2><ul class="bugs" id="working"><li>a bug</li></ul></section><section><h2>Done</h2><ul class="bugs" id="done"><li>a bug</li></ul></section></body> </html>有關這些列表的更多信息,請參閱 Nancy 文檔。
現在,我已經實現了基本的 Nancy 功能,我需要建立 OWIN 管道并配置 Nancy 模塊來參與該管道。 為此,我首先需要安裝 Katana 主機和服務器組件,然后編寫少量的探測代碼來設置 OWIN 管道并將 Nancy 插入該管道中。
對于 Katana 主機和服務器組件,我將從使用 IIS Express 和 System.Web 入手,因為這些在本質上可以為 Visual Studio 所理解,并且將在生成應用程序時實現順暢的 F5 體驗。 為了將 System.Web 主機合并到項目中,我安裝了 NuGet 程序包 Microsoft.Owin.Host.SystemWeb 。
默認的 Katana 組件使用多個不同的約定來加載和運行 OWIN 應用程序,包括啟動類。 當 Katana 主機加載 OWIN 應用程序時,它會根據以下規則發現并運行啟動類(按照優先級順序):
- 如果 web.config 文件包含帶有 key=“owin:AppStartup”的 appSetting,則加載程序將使用設置值。該值必須是有效的 .NET 類型名稱。
- 如果程序集包含屬性 [assembly:OwinStartup(typeof(MyStartup))],則加載程序將使用屬性值中指定的類型。
- 如果這些條件中的任意一個為真,加載程序將通過查找名為 Startup 的類型(帶有與簽名 void Configure(IAppBuilder app) 匹配的方法)來掃描已加載的程序集。
在本示例中,我將允許加載程序掃描該類的程序集。 不過,如果您的項目中有許多不同的類型和程序集,那么使用 appSetting 或程序集屬性來防止不必要的掃描可能會更好。
我將創建啟動類,該類將初始化 OWIN 管道并將 Nancy 作為管道組件添加。 我創建一個名為 Startup 的新類,并按下面所示添加配置方法:
public class Startup {public void Configuration(IAppBuilder app){app.UseNancy();} }UseNancy 是 Nancy.Owin NuGet 程序包公開的擴展方法。 雖然您可以使用 IAppBuilder 的更通用的 Use 方法來添加中間件,不過許多中間件庫將提供這些有用的擴展方法,這些方法可以簡化配置過程。
此時,您可以在 Visual Studio 中使用 F5 運行項目,并且可以看到一個雖然不足以令人非常興奮,但功能完備的 Web 應用程序。 此時,OWIN 管道包含單個組件,即 Nancy,如圖 6 中所示。
圖 6 包含單個組件的正常運行的 Web 應用程序
##將數據與 ASP.NET Web API 合并
目前,HTML 視圖由主靜態標記構成。 我現在將為用戶提供一些可處理的實際錯誤。 對于許多現代 Web 應用程序,將數據提供給瀏覽器客戶機的任務已從服務器端標記生成框架(例如 Nancy 模塊)轉移到單獨的 Web API 服務。 接下來,瀏覽器會加載 HTML 頁并立即執行 JavaScript,后者將從 Web API 中提取數據并在頁自身中動態生成 HTML 標記。
我將從使用 ASP.NET Web API 框架構造 Web API 開始入手。 通常,第一步是安裝 Web API NuGet 程序包。 為了確保輕松地將 ASP.NET Web API 插入到 OWIN 管道中,我將安裝 Microsoft.Asp-Net.WebApi.Owin 程序包 (bit.ly/1dnocmK)。 此程序包會將 ASP.NET Web API 框架的其余部分作為依賴關系安裝。 在安裝框架后,我將創建一個簡單的 API,如圖 7 中所示。
圖 7 BugsController.cs
public class BugsController : ApiController {IBugsRepository _bugsRepository = new BugsRepository();public IEnumerable<Bug> Get(){return _bugsRepository.GetBugs();}[HttpPost("api/bugs/backlog")]public Bug MoveToBacklog([FromBody] int id){var bug = _bugsRepository.GetBugs().First(b=>b.id==id);bug.state = "backlog";return bug;}[HttpPost("api/bugs/working")]public Bug MoveToWorking([FromBody] int id){var bug = _bugsRepository.GetBugs().First(b => b.id == id);bug.state = "working";return bug;}[HttpPost("api/bugs/done")]public Bug MoveToDone([FromBody] int id){var bug = _bugsRepository.GetBugs().First(b => b.id == id);bug.state = "done";return bug;} }該 API 包含一個可從存儲庫返回一組錯誤對象的方法,還有一些可將錯誤在不同狀態之間變換的方法。 有關 ASP.NET Web API 的詳細信息,請參閱 asp.net/web-api。
現在我已定義了一個 ASP.NET Web API 控制器,我需要將它添加到現有的 OWIN 管道。 為此,我只需將下面的代碼行添加到啟動類的配置方法中:
var config = new HttpConfiguration(); config.MapHttpAttributeRoutes(); config.Routes.MapHttpRoute("bugs", "api/{Controller}"); app.UseWebApi(config);就如 Nancy 一樣,ASP.NET Web API OWIN 程序包提供了 UseWebApi 擴展方法,因此可以輕松地將 ASP.NET Web API 合并到現有的 OWIN 管道中。 現在,OWIN 管道包含兩個組件,即 ASP.NET Web API 和 Nancy,如圖 8 中所示。
圖 8 包含兩個組件的 OWIN 管道
當請求進入管道中時,如果它們與其中一個 ASP.NET Web API 路由規則匹配,ASP.NET Web API 就會處理該請求并生成響應。 否則,請求會繼續通過管道,Nancy 會嘗試處理該請求。 如果沒有任何管道組件可以處理特定的請求,默認的 Katana 組件將返回 HTTP 404 響應。
盡管我已經有正常運行的 ASP.NET Web API,但它目前未由主視圖訪問。 因此,我將添加代碼,以便從 Web API 使用數據,并且生成每個不同狀態的錯誤列表:積壓、正在處理和已完成。 對于此任務,我將利用 Knockout.js,這是一個 JavaScript 的“模型-視圖-視圖模型”(MVVM) 庫。 有關 Knockout 的更多信息,請參閱 knockoutjs.com。
為了使用 Knockout 啟用在客戶端動態生成 HTML 標記的功能,我首先需要做的工作是從 ASP.NET Web API 中提取所有錯誤,并創建 Knockout 可綁定到 HTML 元素的 viewModel。 如圖 9 所示。
圖 9 設置錯誤 viewModel
<script>$(function () {var viewModel;$.getJSON('/api/bugs', function(data) {var model = data;viewModel = {backlog: ko.observableArray(model.filter(function(element) { return element.state === 'backlog'; })),working: ko.observableArray(model.filter(function(element) { return element.state === 'working'; })),done: ko.observableArray(model.filter(function(element) { return element.state === 'done'; })),changeState: function (bug, newState) {var self = this;$.post('/api/bugs/' + newState, { '': bug.id }, function(data){self.moveBug(data);});},moveBug: function (bug) {// Remove the item from one of the existing lists...// Add bug to correct listthis[bug.state].push(bug);}};ko.applyBindings(viewModel);})}) </script>在創建 viewModel 后,Knockout 接下來可以通過將 viewModel 綁定到使用 Knockout 特定屬性修飾的 HTML 元素,動態地生成并更新 HTML 內容。 例如,積壓列表可以從 viewModel 使用圖 10 中所示的屬性生成。
圖 10 用于生成積壓列表的屬性
<section><h2>Backlog</h2><ul class="bugs" id="backlog" data-bind="foreach:backlog"><li>[<span data-bind="text: id"></span>] <span data-bind="text: title"></span>:<span data-bind="text: description"></span><ul><li><a href="#" data-bind="click: $root.changeState.bind($root, $data,'working')">Move to working</a></li> <li><a href="#" data-bind="click: $root.changeState.bind($root, $data,'done')">Move to done</a></li> </ul></li></ul> </section>##使用 SignalR 添加實時更改通知
此時,我已經有了一個功能完備的單頁 Web 應用程序。 用戶可以瀏覽到主視圖,并將錯誤在不同的錯誤狀態之間變換。 此外,當前功能級別的基礎技術 Nancy 和 ASP.NET Web API 在相同的 OWIN 管道中一起運行。
不過,我打算更深入一步,允許不同的用戶實時查看其他用戶對錯誤所做的更新。 為此,我將利用 SignalR 庫。該庫提供了客戶機和服務器 API 來管理瀏覽器與 Web 服務器之間的實時消息交換。 編寫 SignalR 的目的也是為了在 OWIN 管道中運行,因此將它添加到現有的應用程序中不過是小事一樁。
我將使用一項名為 Hub 的 SignalR 功能,但在本文中不會討論有關 SignalR 的細節。Hub 使客戶端和服務器能夠相互調用對方的方法。在我的應用程序中,當 ASP.NET Web API 接收到更改錯誤狀態的請求時,它將會更新錯誤,然后通過 SignalR Hub 將更新后的錯誤廣播給當前連接到應用程序的所有瀏覽器客戶端。
我將從在服務器上創建 Hub 來開始此過程。 因為我不會利用任何其他 SignalR 功能,因此我的 Hub 將只包含以下空的類定義:
[HubName("bugs")] public class BugHub : Hub { }為了將廣播從 ASP.NET Web API 發送到 Hub,我首先需要獲取器運行時上下文的實例。 我可以通過添加以下 BugsController 構造函數來實現此目的:
public BugsController() {_hub = GlobalHost.ConnectionManager.GetHubContext<BugHub>(); }從其中一個 MoveToXX 操作,我接下來可以將更新的錯誤廣播到所有連接的瀏覽器客戶端:
_hub.Clients.All.moved(bug);在主視圖中,將幾個腳本引用添加到 SignalR JavaScript 庫后,我可以使用以下代碼連接到 bugsHub 并開始偵聽“已變換”消息:
$.connection.hub.logging = true; var bugsHub = $.connection.bugs; bugsHub.client.moved = function (item) {viewModel.moveBug(item); }; $.connection.hub.start().done(function() {console.log('hub connection open'); });請注意,當我通過 moved 函數從服務器接收到調用時,我按照該項的單擊處理程序的相同方式調用 viewModel moveBug 方法。 差別在于,由于此方法是 SignalR 廣播的結果,因此所有瀏覽器客戶端可以同時更新其 viewModel。 通過打開兩個瀏覽器窗口,在其中一個窗口中進行更改,然后在另一個窗口中查看狀態更改,即可清楚地了解這一點。
正如我所提到的,將 SignalR 添加到 OWIN 管道中不過是小事一樁。 我只需將下面的代碼添加到啟動類的配置方法中:
app.MapSignalR();這將創建類似圖 11 中所示的管道。
圖 11 包含三個組件的 OWIN 管道
##轉向自托管
我現在有了一個正常運行的錯誤管理應用程序,但仍缺少一些關鍵功能來執行某些有意思的操作。 我已經使用 Microsoft 和第三方 Katana 中間件組件,逐步向應用程序添加了一些功能。 不過,今天許多此類功能已可以使用 ASP.NET HttpModules 和 HttpHandlers 來實現。 因此,除了提供更加簡單的、代碼驅動的方法來編寫管道組件之外,我真正實現了哪些功能?
答案就是記住圖 2 中的高級 Katana 體系結構圖表。 到目前為止,我只使用了 Katana 堆棧的最上面兩層。 不過,所有這些層都可以輕松地替換,包括服務器和主機。
為了進行演示,我將處理我的整個管道,將它提升到 IIS 和 System.Web.dll 之外,并將它放在一個簡單的輕型 HTTP 服務器上,該服務器是由名為 OwinHost.exe 的 Katana 可執行文件托管的。 自托管在許多方案中是非常有用的,從開發計算機上未安裝 Web 服務器的安裝情形,到在使用進程隔離并且不公開 Web 服務器訪問的共享托管環境中部署應用程序的生產情形。
我將從安裝以下附加 NuGet 程序包開始:
- Microsoft.Owin.Host.HttpListener
- OwinHost
然后,我將重新生成應用程序。 請注意,對于在新服務器和主機之上運行應用程序,重新生成操作并不是必需的。 唯一的要求是,在運行時,/bin 文件夾中必須存在這些文件,而重新生成是一種將文件復制到 /bin 的便捷方式。
在安裝程序包和復制文件之后,我打開命令提示符,導航到 Web 項目的根文件夾,然后從程序包文件夾中調用 OwinHost.exe,如圖 12 中所示:
…\packages\OwinHost.2.0.0\tools\OwinHost.exe
圖 12 從程序包文件夾中調用 OwinHost.exe
默認情況下,OwinHost.exe 將啟動,加載 Microsoft.Ow-in.Host.HttpListener 服務器,然后開始偵聽端口 5000。 接下來,我可以導航到 http://localhost:5000,確認整個應用程序正在運行。
此外,幾乎所有的默認設置都可以使用命令行開關進行覆蓋。 例如,如果您希望偵聽其他端口,可以提供 -p 12345。 如果希望使用完全不同的服務器,請使用 -s your.custom.server.assembly。 Katana 設計的強大之處在于它的模塊化。 當堆棧中的任何層發生創新后,它們可以立即集成到正在運行的應用程序。 并且,由于堆棧的所有組件之間的約定只是應用程序委托,創新的步伐可以遠遠超越目前提供的功能。
這僅僅是一個開端
Katana 2.0 將隨 Visual Studio 2013 一起發布。 新版本有兩個值得關注的方面:
- 為自托管提供核心基礎結構組件
- 提供了一套豐富的驗證中間件(包括 Facebook、Google、Twitter 和 Microsoft Account 這樣的社交提供商)以及適用于 Windows Azure Active Directory、cookie 和聯合身份驗證的提供程序
在發布 Katana 2.0 后,將會立即開始未來的 Katana 組件集的開發工作。 具體細節和優先級仍有待確定,不過您可以在 katanaproject.codeplex.com 中創建問題,推動這種討論。
總結
以上是生活随笔為你收集整理的Katana 项目入门的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 编码转换参考范例大全
- 下一篇: 【积跬步以至千里】Windows无法访问