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