ASP.NET

Topshelf 及 Katana:統一的 Web 及服務架構

Wes McClure

下載代碼示例

使用主機ASP.NETWeb 應用程式的 IIS 已經事實上的標準超過十年。 生成此類應用程式是一個相對簡單的過程,但部署它們不是。 部署要求的應用程式佈建層次結構的策劃的知識和歷史的 IIS,單調乏味的和調配網站、 應用程式和虛擬目錄的細微差別。 許多基礎設施的關鍵區段往往最終在手動設定 IIS 元件應用程式之外的生活。

當應用程式突破簡單 Web 請求和支援長時間運行的請求、 定期作業和其他處理工作的必要性時,他們變得很難在 IIS 內支援。 通常情況下,解決方案是創建一個單獨的 Windows 服務來承載這些元件。 但這需要一個完全單獨的部署進程,增加了一倍的努力。 最後一根稻草是獲取 Web 和服務進行通信的過程。 這可能是一個非常簡單的應用程式快速變得極為複雜。

圖 1 顯示了此體系結構通常長什麼樣子。 Web 層負責處理快速請求和提供對系統的 UI。 長時間運行的請求下放到服務,它還負責處理重複的作業和處理。 此外,該服務提供有關當前和未來的工作,為 Web 層要包含在使用者介面中的狀態。

Traditional Separated Web and Service Architecture
圖 1 傳統分離的 Web 和服務的體系結構

一種新方法

幸運的,新技術的興起可以使開發和部署 Web 和服務應用程式簡單得多。 武士刀專案 (katanaproject.codeplex.com) 和浩然所提供的規格 (owin.org),它是現在可能自承載 Web 應用程式,IIS 拿走方程,並且仍然支援許多無處不在的ASP.NET元件,如 WebApi 和 SignalR。 自承載的 Web 可以嵌入在簡陋的主控台應用程式及其 Topshelf (topshelf project.com) 來輕鬆地創建 Windows 服務。 因此,Web 和服務元件可以住肩並肩在同一過程中,如中所示圖 2。 這消除了發展無關的通訊層、 單獨的專案和單獨的部署程式的開銷。

Unified Web and Service Architecture with Katana and Topshelf
圖 2 統一的 Web 和服務的體系結構與武士刀和 Topshelf

這種能力不是全新的。 Topshelf 到處都有多年來,説明簡化了開發 Windows 服務,並且有許多開放源碼自承載 Web 框架,如南茜。 不過,直到浩然到武士刀專案得到蓬勃發展,什麼都不顯示盡可能多的諾言成為事實上的標準的替代為宿主在 IIS 中的 Web 應用程式。 此外,南茜和許多開放源碼元件工作與武士刀專案,讓您策劃兼收並蓄、 柔順的框架。

Topshelf 看起來可能是可選的但不是這樣。 沒有簡化服務發展的能力,自託管的 Web 開發可能非常繁瑣。 Topshelf 簡化了發展的服務由其作為一個主控台應用程式處理和抽象它將作為一種服務承載的事實。 當它是部署時間時,Topshelf 將自動處理安裝和啟動應用程式作為 Windows 服務 — — 所有這些都不 InstallUtil ; 處理的系統開銷 細微的服務專案和服務元件 ; 並將調試器附加到服務時什麼差錯。 Topshelf 還允許許多參數,例如,要指定在代碼中或通過命令列安裝過程中配置的服務名稱。

為了說明如何統一 Web 和服務元件與武士刀和 Topshelf,我就會生成簡單的 SMS 消息應用程式。 我先用一個 API,用於接收郵件和發送佇列他們。 這將表明它是多麼容易處理長時間運行的請求。 然後,我將添加 API 查詢方法,以返回的計數掛起的郵件,顯示它也很容易查詢服務狀態從 Web 元件。

下一步,我將添加以證明自託管的 Web 元件仍起構建豐富的 Web 介面的管理介面。 若要圓了消息處理的我會添加要發送消息,因為他們要排隊,到展示包括服務元件的元件。

為了突出這種體系結構的最佳部分之一,我將創建一個 psake 腳本以公開部署的簡單性。

若要集中武士刀和 Topshelf 的組合優勢,我不會進入有關任一專案的詳細資訊。 查閱"越來越開始與武士刀專案"(bit.ly/1h9XaBL) 和"創建 Windows 服務輕鬆地與 Topshelf"(bit.ly/1h9XReh) 瞭解更多。

一個主控台應用程式是所有你需要入門

Topshelf 存在輕鬆地開發和部署 Windows 服務從一個簡單的主控台應用程式的起始點。 若要開始與 SMS 消息傳遞應用程式,我創建 C# 主控台應用程式,然後從封裝管理員主控台安裝的 Topshelf NuGet 包。

主控台應用程式啟動時,我需要配置 Topshelf HostFactory 抽象承載作為一個發展中的主控台和作為一種服務在生產中的應用程式:

private static int Main()
{
  var exitCode = HostFactory.Run(host =>
  {
  });
  return (int) exitCode;
}

HostFactory 將返回結束代碼,服務安裝檢測和阻止在失敗中是有益的。 主機配置器提供服務的方法來指定自訂類型,表示到應用程式代碼的進入點。 Topshelf 是指這作為它承載的服務因為 Topshelf 是一個用於簡化創建 Windows 服務框架:

host.Service<SmsApplication>(service =>
{
});

接下來,創建一個 SmsApplication 類型包含旋轉自託管的 Web 服務器和傳統的 Windows 服務元件的邏輯。 在最低限度,這種自訂類型將包含要在應用程式啟動或停止時執行的行為:

public class SmsApplication
{
  public void Start()
  {
  }
  public void Stop()
  {
  }
}

因為我選擇服務類型使用一個普通的老 CLR 物件 (POCO),我向 Topshelf 構造的 SmsApplication 類型的實例提供了 lambda 運算式和我指定開始和停止方法:

service.ConstructUsing(() => new SmsApplication());
service.WhenStarted(a => a.Start());
service.WhenStopped(a => a.Stop());

Topshelf 允許許多服務 param­eters 要配置在代碼中,所以我用 SetDescription、 SetDisplayName 和 SetServiceName 來描述和名稱服務,將會安裝在生產中:

host.SetDescription("An application to manage
     sending sms messages and provide message status.");
  host.SetDisplayName("Sms Messaging");
  host.SetServiceName("SmsMessaging");
  host.RunAsNetworkService();

最後,我使用 RunAsNetworkService 來指示 Topshelf 要佈建服務以網路服務帳戶下運行。 您可以更改這對無論帳戶適合您的環境。 更多的服務選項,請參閱在 Topshelf 設定檔 bit.ly/1rAfMiQ

作為一個主控台應用程式運行,該服務是作為啟動可執行檔一樣簡單。 圖 3 顯示啟動和停止的短信可執行檔的輸出。 因為這是一個主控台應用程式,當您啟動Visual Studio中的應用程式時,您會遇到相同的行為。

Running the Service as a Console Application
圖 3 作為一個主控台應用程式運行服務

納入一個 API

Topshelf 管道在的地方我可以開始工作上應用程式的 API。 武士刀專案提供元件自主辦浩然管道,所以我安裝要納入自承載元件的 Microsoft.Owin.SelfHost 包。 此套裝軟體引用了幾個包,其中兩個重要的自託管。 第一,Microsoft.Owin.Hosting 提供了一組元件承載並運行浩然管道。 第二,Microsoft.Owin.Host.HttpListener 提供的 HTTP 伺服器的實現。

在 SmsApplication,裡面創建自承載的 Web 應用程式使用託管包所提供的 web 應用程式類型:

protected IDisposable WebApplication;
public void Start()
{
  WebApplication = WebApp.Start<WebPipeline>("http://localhost:5000");
}

Web 應用程式啟動方法需要兩個參數,泛型參數,以指定一種類型,將配置的浩然管道和要偵聽請求的 URL。 Web 應用程式是可支配的資源。 當 SmsApplication 實例停止時,我處理的 Web 應用程式:

public void Stop()
{
  WebApplication.Dispose();
}

使用浩然的一個好處是我可以利用各種熟悉部件。 第一,我會使用 WebApi 創建 API。 我需要安裝要在浩然管道中納入 WebApi 的 Microsoft.AspNet.WebApi.Owin 包。 然後,我將創建的 WebPipeline 類型來配置浩然管道和注入 WebApi 中介軟體。 另外,我將把配置 WebApi 使用屬性路由:

public class WebPipeline
{
  public void Configuration(IAppBuilder application)
  {
    var config = new HttpConfiguration();
    config.MapHttpAttributeRoutes();
    application.UseWebApi(config);
  }
}

現在,可以創建一種 API 方法接收消息和佇列發送:

public class MessageController : ApiController
{
  [Route("api/messages/send")]
  public void Send([FromUri] SmsMessageDetails message)
  {
    MessageQueue.Messages.Add(message);
  }
}

SmsMessageDetails 包含消息的負載。 發送操作將消息添加到佇列在以後的時間進行非同步處理。 MessageQueue 是全球 BlockingCollection。 在實際應用中這可能意味著你需要考慮耐久性和可擴充性等其他問題的因素:

public static readonly BlockingCollection<SmsMessageDetails> Messages;

在一個分離的 Web 服務的體系結構,移交的非同步處理,長時間運行的請求,如發送一條消息,要求 Web 和服務之間的通信流程。 添加 API 方法來查詢服務的狀態意味著更多通信開銷。 統一的辦法使得 Web 和服務之間的共用狀態資訊的元件簡單。 為了證明這一點,我對 API 添加 PendingCount 查詢:

[Route("api/messages/pending")]
public int PendingCount()
{
  return MessageQueue.Messages.Count;
}

建設一個豐富的使用者介面

Api 非常方便,但是自承載的 Web 應用程式仍然需要支援視覺化介面。 在將來,我懷疑,ASP.NETMVC 框架或衍生物,將可用作浩然中介軟體。 現在,南茜是相容,已有一套以支援 Razor 視圖引擎的核心。

我將安裝要添加支援南茜和 Nancy.Viewengines.Razor 將納入 Razor 視圖引擎的 Nancy.Owin 包。 要插入到浩然管道南茜,需要註冊,它註冊後為 WebApi 這樣它不會捕獲我映射到該 API 的路線。 預設情況下,南茜返回一個錯誤,如果不找到的資源,而 WebApi 將請求傳遞它無法處理回管道:

application.UseNancy();

要瞭解更多有關使用南茜的浩然管道,請參閱"在 OWIN 與承載南茜"在 bit.ly/1gqjIye

若要生成的行政地位的介面,我添加一個模組,南茜和映射一個狀態路由,呈現狀態視圖,視圖模型作為傳遞掛起郵件計數:

public class StatusModule : NancyModule
{
  public StatusModule()
  {
    Get["/status"] =
      _ => View["status", MessageQueue.Messages.Count];
  }
}

不是這點,只是簡單的計數掛起的消息非常迷人:

<h2>Status</h2>
There are <strong>@Model</strong> messages pending.

如中所示,我去有點與簡單的引導 navbar 視圖爵士樂圖 4。 使用引導需要承載的引導的樣式表的靜態內容。

Administrative Status Page
圖 4 行政狀態頁

我可以使用南茜主機的靜態內容,但浩然的優勢是混合和匹配中介軟體,所以我要去使用新發佈的 Microsoft.Owin.StaticFiles 套裝軟體,是武士刀專案的一部分。 StaticFiles 套裝軟體提供檔服務中介軟體。 我會將其添加到的浩然管道開始所以南茜靜態檔服務不踢。

application.UseFileServer(new FileServerOptions
{
  FileSystem = new PhysicalFileSystem("static"),
  RequestPath = new PathString("/static")
});

檔案系統參數告訴檔案伺服器在哪裡查找檔服務。 我正在使用一個名為靜態資料夾。 RequestPath 指定的路由首碼用於偵聽對此內容的請求。 在這種情況下,我選擇要鏡像的名稱靜態的但這些不一定要匹配。 我在佈局中使用下面的連結引用的引導的樣式表 (自然,我放入引導的樣式表在 CSS 靜態資料夾內的資料夾中):

<link rel="stylesheet" href="/static/css/bootstrap.min.css">

有關靜態內容和意見的詞

我動議之前,我想告訴你關於開發自承載的 Web 應用程式時,我覺得有説明的提示。 通常情況下,您將設置的靜態內容和 MVC 視圖要這樣自託管的 Web 元件可以找到它們與當前執行的程式集複製到輸出目錄。 這不僅是一種負擔,容易忘記,更改視圖和靜態內容,需要重新編譯應用程式 — — 這絕對殺了生產力。 因此,我建議不將靜態內容和視圖複製到輸出目錄 ; 相反,配置中介軟體,比如南茜和檔案伺服器,將映射到發展資料夾。

預設情況下,一個主控台應用程式的調試輸出目錄是 bin/調試,因此,在發展中我告訴檔案伺服器來看目前的目錄以查找包含引導的樣式表的靜態資料夾上面的兩個目錄:

FileSystem = new PhysicalFileSystem(IsDevelopment() ? 
  "
../../static" : "static")

然後,跟南茜要去哪找的意見,我將創建自訂的 NancyPathProvider:

public class NancyPathProvider : IRootPathProvider
{
  public string GetRootPath()
  {
    return WebPipeline.IsDevelopment()
      ?
Path.Combine(AppDomain.CurrentDomain.BaseDirectory, 
      @"..
\..
\")
      : AppDomain.CurrentDomain.BaseDirectory;
  }
}

再次,我使用進行相同的檢查來看兩個以上的基目錄的目錄,如果我跑在Visual Studio中的發展模式。 我離開你 ; 執行 IsDevelopment 它可能是一個簡單的配置設置或您可以編寫代碼來檢測時從Visual Studio啟動應用程式。

要註冊此自訂根路徑提供程式,創建自訂的 NancyBootstrapper 和重寫預設 RootPathProvider 屬性以創建的 NancyPathProvider 的實例:

public class NancyBootstrapper : DefaultNancyBootstrapper
{
  protected override IRootPathProvider RootPathProvider
  {
    get { return new NancyPathProvider(); }
  }
}

並向浩然管道添加南茜時, 我通過在選項中的 NancyBootstrapper 實例:

application.UseNancy(options => 
  options.Bootstrapper = new NancyBootstrapper());

傳送訊息

接收的消息是一半的工作,但在應用程式仍然需要一個過程來向他們發送。 這是一個過程,傳統上將生活在一個孤立的服務。 在此解決方案中統一我可以簡單地添加 SmsSender,當應用程式啟動時啟動。 我會將這添加到 SmsApplication 啟動方法 (在實際應用中,您應添加停止和處置這種資源的能力):

public void Start()
{
  WebApplication = WebApp.Start<WebPipeline>("http://localhost:5000");
  new SmsSender().Start();
}

在 SmsSender 上的啟動方法內, 我開始要發送郵件的長時間運行任務:

public class SmsSender
{
  public void Start()
  {
    Task.Factory.StartNew(SendMessages, 
      TaskCreationOptions.LongRunning);
  }
}

當 WebApi 發送操作接收一條消息時,它可以將其添加到訊息佇列,它是阻塞集合。 我創建的 SendMessages 方法來阻止,直到消息到達。 這可能是由於在 GetConsumingEnumerable 後面的抽象。 當一組的郵件到達時,它立即開始發送它們:

private static void SendMessages()
{
  foreach (var message in MessageQueue.Messages.GetConsumingEnumerable())
  {
    Console.WriteLine("Sending: " + message.Text);
  }
}

這將是微不足道,SmsSender,擴大能力來發送郵件的多個實例在旋轉。 在實際應用中,要將 CancellationToken 傳遞給 GetConsumingEnumerable 安全地停止枚舉。 如果你想要瞭解更多關於阻塞集合,你會發現很好的資訊 bit.ly/QgiCM7bit.ly/1m6sqlI

簡單、 涼風習習部署

制定一項綜合的服務和 Web 應用程式是相當簡單的多虧了武士刀和 Topshelf。 這個功能強大的組合令人敬畏的好處之一是一個簡單得可笑的部署過程。 我要給你看一個簡單的兩步部署使用 psake (github.com/psake/psake)。 這並不是只是一個魯棒的腳本,以便實際生產中使用 ; 我只是想要展示的進程是如何確實很簡單,無論什麼工具使用。

第一步是生成應用程式。 我創建一個生成任務,將會調用 msbuild 解決方案的路徑並創建發佈版本 (輸出最終會在 bin/釋放):

properties {
  $solution_file = "Sms.sln"
}
task build {
  exec { msbuild $solution_file /t:Clean /t:Build /p:Configuration=Release /v:q }
}

第二步是部署該應用程式作為一種服務。 我創建一個部署任務,取決於生成任務並聲明一個傳遞到安裝位置保存路徑目錄。 為簡單起見我只是將部署到本地目錄。 然後,我創建一個可執行檔的變數指向主控台應用程式可執行檔傳遞的目錄中:

task deploy -depends build {
  $delivery_directory = "C:\delivery"
  $executable = join-path $delivery_directory 'Sms.exe'

第一,部署任務將檢查交付目錄是否存在。 如果它發現交付的目錄,它將假定已經部署了該服務。 在這種情況下,部署任務將卸載該服務並刪除交付目錄:

if (test-path $delivery_directory) {
  exec { & $executable uninstall }
  rd $delivery_directory -rec -force 
}

下一步,部署任務複製生成的輸出內容到交付目錄部署新的代碼,然後將視圖和靜態資料夾複製到交付目錄:

copy-item 'Sms\bin\Release' $delivery_directory -force -recurse -verbose
copy-item 'Sms\views' $delivery_directory -force -recurse -verbose
copy-item 'Sms\static' $delivery_directory -force -recurse –verbose

最後,部署任務將安裝和啟動服務:

exec { & $executable install start }

當您部署該服務時,請確保您的 IsDevelopment 實現返回 false 或你會得到一個拒絕訪問的異常,如果找不到靜態資料夾中的檔案伺服器。 此外,還可以有問題有時每個部署上重新安裝該服務。 另一種策略是停止,更新然後啟動服務,如果它已經安裝。

正如您所看到的部署是簡單得可笑。 從方程 ; 完全刪除 IIS 和 InstallUtil 有一個部署過程而不是兩個 ; 和無需擔心層將 Web 和服務如何進行通信。 在您構建您的統一的 Web 和應用程式服務,可以重複運行此部署任務 !

展望未來

確定是否此組合的模型將會為你工作的最佳方式是找到一個低風險的專案,可以試試看。 它是這麼簡單,開發和部署具有這種體系結構的應用程式。 要那裡偶爾的學習曲線 (如果您使用南茜 MVC,例如)。 但關於使用浩然,即使聯合的辦法行不通,偉大的是你仍然可以承載 IIS 使用ASP.NET主機 (Microsoft.Owin.Host.SystemWeb) 內的浩然管道。 試一試,看看你的想法。

Wes McClure  利用他的專業知識,説明客戶迅速交付高品質軟體到呈指數級增加他們為客戶創造的價值。他喜歡談論一切與相關的軟體發展,是 Pluralsight 作者並且寫關於他的經驗在 devblog.wesmcclure.com。聯繫到他在 wes.mcclure@gmail.com

衷心感謝以下技術專家對本文的審閱:HowardDierking(Microsoft)、 達米安吻痕、Chris· 派特森 (RelayHealth)、ChrisRoss(Microsoft) 和特拉維斯 ·Smith
HowardDierking是在 Windows Azure 框架和工具,團隊的專案經理他的工作重點在哪裡ASP.NET、 NuGet 和 Web Api。 以前,Dierking擔任 MSDN 雜誌的主編,也跑了,開發人員認證計畫為 Microsoft 學習。 他花了 10 年前微軟作為一個開發人員和應用程式具有焦點建築師分散式系統。

ChrisRoss是在 Microsoft 軟體設計工程師主攻聯網和 OWIN 的所有事情。

Chris派特森是 RelayHealth,連接業務的 McKesson 公司,一名建築師,負責的體系結構和應用程式和連接病人、 供應商、 藥房、 和金融機構加快護理交付的服務的發展。 Chris是 Topshelf 和多半的主要貢獻,曾被評為最有價值專家獎 Microsoft 為他技術社區的貢獻。

達米安吻痕是重點放在 DDD\CQRS\ES 軟體發展商基於的應用程式。 他是.NET 開源軟體的宣導者,有助於南茜、 NEventStore 和其他人等各種專案。 他偶爾說話時,人們費神去聽著,偶爾在博客 HTTP://dhickey.ie。 聯繫到他在 dhickey@gmail.com / @randompunter

特拉維斯 ·Smith是開發人員宣導為七八年與 Atlassian 市場。 特拉維斯有助於促進一個打開的網站,polyglotism 和新興 web 技術。 特拉維斯是開放源碼專案,包括 Topshelf 和多半數目做出了貢獻。 可以在開發人員活動充滿激情地談論關於製作令人敬畏的軟體或在 Internet 上找到特拉維斯 HTTP://travisthetechie.com