发现 ASP.NET Core SignalR
ASP.NET SignalR 是幾年前推出的工具,可供 ASP.NET 開發人員使用,以向應用程序添加實時功能。只要基于 ASP.NET 的應用程序必須接收來自服務器(從監視系統到游戲)的頻繁異步更新,就屬于典型的庫用例。這些年來,我還使用它來刷新 CQRS 體系結構方案中的 UI,以及在 socialware 應用程序中實現與 Facebook 類似的通知系統。從更具技術性的角度來看,SignalR 是抽象層,生成依據為一部分可以在完全兼容的客戶端和服務器之間建立實時連接的傳輸機制。客戶端通常為 Web 瀏覽器,服務器通常為 Web 服務器,但兩者都不僅限于此。
ASP.NET SignalR 屬于 ASP.NET Core 2.1。雖然庫的總體編程模型與經典 ASP.NET 的編程模型類似,但庫本身實際上已經完全重寫。盡管如此,只要開發人員適應各方面的變化,應該就可以快速熟練掌握新方案。本文將介紹如何在規范 Web 應用程序中使用新庫來監視可能會很漫長的遠程任務。
設置環境
可能需要以下多個 NuGet 包,才能使用庫:Microsoft.AspNetCore.SignalR 和 Microsoft.AspNetCore.SignalR.Client。前一個包提供核心功能;后一個包是 .NET 客戶端,且只有在生成 .NET 客戶端應用程序時才需要。此示例將通過 Web 客戶端來使用庫,因此改為需要 SignalR NPM 包。本文稍后將詳細介紹這一點。請注意,在基于 MVC 應用程序模型的 Web 應用程序的上下文中使用 SignalR 并不是一項強制性要求??梢灾苯油ㄟ^ ASP.NET Core 控制臺應用程序使用 SignalR 庫服務,還可以在控制臺應用程序中托管 SignalR 的服務器部分。
應用程序的啟動類需要包含一些特定代碼,這一點不足為奇。具體而言,將把 SignalR 服務添加到系統服務集合中,并將它配置為可供實際使用。圖 1?展示了使用 SignalR 的啟動類的典型狀態。
圖 1:SignalR ASP.NET Core 應用程序的啟動類
public class Startup {public void ConfigureServices(IServiceCollection services){services.AddMvc();services.AddSignalR();}public void Configure(IApplicationBuilder app){app.UseStaticFiles();app.UseDeveloperExceptionPage();// SignalR??? app.UseSignalR(routes =>{routes.MapHub<UpdaterHub>("/updaterDemo");routes.MapHub<ProgressHub>("/progressDemo");});app.UseMvcWithDefaultRoute();} }SignalR 服務配置包括一個或多個服務器路由的定義,這些路由綁定到服務器端環境中的一個或多個終結點。MapHub<T> 方法將請求 URL 中的指定名稱鏈接到 Hub 類的實例中。Hub 類既是實現 SignalR 協議的核心所在,也是處理客戶端調用的位置所在。為服務器端打算接受和處理的每組邏輯相關調用創建 Hub。SignalR 對話由雙方之間交換的消息組成,一方對另一方調用方法的結果可能是沒有響應,可能是收到一個或多個響應,也可能是僅收到錯誤通知。任何 ASP.NET Core SignalR 服務器實現都會公開一個或多個 Hub。在圖 1?中,有兩個 Hub 類(UpdaterHub 和 ProgressHub)綁定到唯一字符串,這些字符串在內部用于生成實際調用的 URL 目標。
Hub 類
SignalR 應用程序中的 Hub 類是普通的簡單類,繼承自 Hub 基類。此基類僅用于免去開發人員一次又一次編寫相同樣本代碼的麻煩。基類只提供某基礎結構,而不提供預定義行為。具體而言,它定義了圖 2?中的成員。
圖 2:Hub 基類的成員
| 成員 | 說明 |
| 客戶端 | 公開當前由 Hub 托管的客戶端列表的屬性。 |
| 上下文 | 公開當前調用方上下文的屬性,包括連接 ID 和用戶聲明(若有)等信息。 |
| 組 | 公開各客戶端子集的屬性,這些客戶端可能已經以編程方式定義為完整客戶端列表中的組。組通常創建用于向選定受眾廣播特定消息。 |
| OnConnectedAsync | 每當有新客戶端連接到 Hub 時調用的虛擬方法。 |
| OnDisconnectedAsync | 每當有新客戶端與 Hub 斷開連接時調用的虛擬方法。 |
最簡單的 Hub 類如下所示:
有趣的是,如果在 ASP.NET Core MVC 應用程序上下文中從控制器方法內使用它,就會直接采用 Hub 形式。幾乎所有的 ASP.NET Core SignalR 示例(包括聊天示例)往往都會在客戶端和 Hub 之間進行直接綁定和雙向綁定,無需控制器提供任何形式調解。在這種情況下,Hub 采用的形式將會更有形一點:
public class SampleChat : Hub {?????// Invoked from outside the hub? public void Say(string message){// Invoke method on listening client(s)??? return Clients.All.InvokeAsync("Said", message);} }與數十篇博客文章中換湯不換藥的規范 SignalR 聊天示例不同,本文中的示例其實并沒有在客戶端和服務器之間建立雙向對話。雖然連接是從客戶端建立的,但在此之后,客戶端就不會發送其他任何請求。相反,服務器會監視任務進度,并在適當時將數據推送回客戶端。也就是說,只有當用例要求客戶端直接調用公共方法時,Hub 類才必須像上面的代碼一樣使用這些方法。如果有點復雜難懂,下面的示例足以闡明這一點。
監視遠程任務
它的具體情形是這樣的:ASP.NET Core 應用程序為用戶提供了某 HTML 接口,以方便用戶觸發可能會很漫長的遠程任務(如創建報告)。因此,作為開發人員,需要顯示進度欄,以持續反饋進度(見圖 3)。
圖 3:使用 SignalR 監視遠程任務的進度
可以猜到,在此示例中,客戶端和服務器都在同一個 ASP.NET Core 項目的上下文中設置 SignalR 實時會話。在此開發階段中,MVC 項目功能齊全,它使用圖 1?中的啟動代碼進行了擴展。接下來,將設置客戶端框架。需要在與 SignalR 終結點交互的所有 Razor(或純 HTML)視圖中完成此設置。
若要在 Web 瀏覽器中與 SignalR 終結點進行通信,首先要添加對 SignalR JavaScript 客戶端庫的引用:
<script src="~/scripts/signalr.min.js"> </script>可以通過多種方式獲取此 JavaScript 文件。最值得推薦的方法是,使用幾乎所有開發計算機上都有的 Node.js 包管理器 (NPM) 工具(特別是在 Visual Studio 2017 版本推出后)。通過 NPM,查找并安裝名為 @aspnet/signalr 的 ASP.NET Core SignalR 客戶端。它會將許多 JavaScript 文件復制到磁盤,但其中只有一個文件才是大多數情況唯一需要的。不管怎樣,這就是簡單地鏈接 JavaScript 文件,還可以通過其他許多方式來獲取此文件,包括從舊版 ASP.NET Core SignalR 項目中復制它。然而,NPM 是團隊提供的唯一受支持的腳本獲取方式。另請注意,ASP.NET Core SignalR 不再依賴 jQuery。
在客戶端應用程序中,還需要另一段更具體的 JavaScript 代碼。特別是,需要如下代碼:
var progressConnection =new signalR.HubConnection("/progressDemo"); progressConnection.start();與 SignalR Hub 建立的連接與指定路徑匹配。更確切地說,以參數形式傳遞到 HubConnection 的名稱,應該是映射到啟動類中路由的名稱之一。在內部,HubConnection 對象準備了串聯當前服務器 URL 和給定名稱生成的 URL 字符串。只有當此 URL 與已配置的路由之一匹配時,才會處理它。另請注意,如果客戶端和服務器不是相同的 Web 應用程序,那么必須向 HubConnection 傳遞托管 SignalR Hub 的 ASP.NET Core 應用程序的完整 URL,外加 Hub名稱。
然后,必須通過 start 方法打開 JavaScript Hub 連接對象。可以使用 JavaScript 承諾(特別是 then 方法)或 TypeScript 中的 async/await 執行后續操作(如初始化某用戶界面)。SignalR 連接由字符串 ID 唯一標識。
請務必注意,如果傳輸連接或服務器失敗,ASP.NET Core SignalR 就不再支持自動重新連接。在舊版中,如果發生服務器故障,客戶端會嘗試根據計劃算法重新建立連接。如果成功,它會使用相同的 ID 重新打開連接。在 SignalR Core 中,如果連接中斷,客戶端只能通過 start 方法再次啟動連接,這就會生成連接 ID 不同的其他連接實例。
客戶端回調 API
需要的另一段基本 JavaScript 代碼是,Hub 回調的 JavaScript,用于刷新接口并在客戶端上反映服務器上的進度。雖然這段代碼在 ASP.NET Core SignalR 中的編寫方式與舊版中的略有不同,但意向是完全相同的。此示例中有以下三個方法能夠從服務器回調:initProgressBar、updateProgressBar 和 clearProgressBar。不用說,可以使用任意名稱和簽名。以下是實現示例:
progressConnection.on("initProgressBar", () => {setProgress(0);$("#notification").show(); }); progressConnection.on("updateProgressBar", (perc) => {setProgress(perc); }); progressConnection.on("clearProgressBar", () => {setProgress(100);$("#notification").hide(); });例如,如果從服務器回調 initProgressBar 方法,幫助程序 setProgress JavaScript 函數就會配置并顯示進度欄(此演示使用的是啟動進度欄組件)。請注意,代碼中使用了 jQuery 庫,但僅用于更新 UI。如前所述,客戶端 SignalR Core 庫不再是 jQuery 插件。也就是說,如果 UI 是基于 Angular 等,可能根本無需使用 jQuery。
服務器端事件
缺少的解決方案部分是,決定何時調用客戶端函數。主要有以下兩種方案。一種是在客戶端通過 Web API 或控制器終結點調用服務器操作時調用。另一種是在客戶端直接調用 Hub 時調用。最后,只需決定在哪里為回調客戶端的任務編寫代碼。
在規范聊天示例中,這一切都發生在 Hub 中:執行所需的全部邏輯,并將消息分派給相應連接。監視遠程任務是另一回事。它假設后臺正在運行某業務流程,以通過某種方式通知進度。從技術角度來講,可能會在 Hub 中編碼此流程,并從中建立與客戶端 UI 之間的對話。也可以讓控制器 (API) 觸發此流程,Hub 僅用于將事件傳遞給客戶端。比此示例更為實際的做法是,在低于控制器級別的層中編碼此流程。
總而言之,可以定義 Hub 類,并隨時可將它用于決定何時以及是否調用客戶端函數。有趣的地方在于,需要什么才能將 Hub 實例注入控制器或其他業務類。此演示在控制器中注入 Hub,但也會對其他更深級別的類執行完全相同的操作。示例 TaskController 是通過 JavaScript 直接從客戶端調用,以觸發進度欄將顯示其進度的遠程任務:
public class TaskController : Controller {private readonly IHubContext<ProgressHub> _progressHubContext;public TaskController(IHubContext<ProgressHub> progressHubContext){_progressHubContext = progressHubContext;}public IActionResult Lengthy(){// Perform the task and call back? } }通過 IHubContext<THub> 接口在控制器或其他任何類中注入 Hub。IHubContext 接口封裝 Hub 實例,但無法直接訪問它。從中可以將消息分派回 UI,但無法訪問連接 ID(舉個例子)。假設遠程任務是在 Lengthy 方法中執行,并需要在其中更新客戶端進度欄:
progressHubContext.Clients.Client(connId).InvokeAsync("updateProgressBar", 45);連接 ID 可以從 Hub 類中進行檢索,但無法像此示例一樣從通用 Hub 上下文中進行檢索。因此,最簡單的方法是,讓客戶端方法在啟動遠程任務時就傳遞連接字符串:
public void Lengthy([Bind(Prefix="id")] string connId) { … }最后,控制器類接收 SignalR 連接 ID,注入有 Hub 上下文,并使用通過非類型化通用 API 調用的上下文方法(InvokeAsync 方法)執行操作。這樣一來,Hub 類就無需包含任何方法!如果覺得這很奇怪,請參閱?bit.ly/2DWd8SV?中的代碼。
總結
本文介紹了如何在 Web 應用程序上下文中使用 ASP.NET Core SignalR 監視遠程任務。Hub 幾乎是空的,因為所有通知邏輯都被內置到控制器中,并使用通過 DI 注入的 Hub 上下文進行編排。這只是 ASP.NET Core SignalR 漫長旅程的起點。接下來,我將深入研究基礎結構,并探索類型化 Hub。
Dino Esposito?在他 25 年的職業生涯中撰寫了超過 20 本書籍和 1,000 篇文章。Esposito 不僅是舞臺劇《事業中斷》的作者,還是 BaxEnergy 的數字策略分析師,正忙于編寫有助于建設環保世界的軟件。可以在 Twitter 上關注他 (@despos)。
原文:https://msdn.microsoft.com/zh-cn/magazine/mt846469
.NET社區新聞,深度好文,歡迎訪問公眾號文章匯總 http://www.csharpkit.com
總結
以上是生活随笔為你收集整理的发现 ASP.NET Core SignalR的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: EFCore2.0@Xamarin.Fo
- 下一篇: 谈谈Circuit Breaker在.N