如何在ASP.NET Core中使用SignalR构建与Angular通信的实时通信应用程序
圖片
假設(shè)我們要?jiǎng)?chuàng)建一個(gè)監(jiān)視Web應(yīng)用程序,該應(yīng)用程序?yàn)橛脩?hù)提供了一個(gè)能夠顯示一系列信息的儀表板,這些信息會(huì)隨著時(shí)間的推移而更新。
第一種方法是在定義的時(shí)間間隔(輪詢(xún))定期調(diào)用API 以更新儀表板上的數(shù)據(jù)。
無(wú)論如何,還是有一個(gè)問(wèn)題:如果沒(méi)有更新的數(shù)據(jù),我們會(huì)因請(qǐng)求而不必要地增加網(wǎng)絡(luò)流量。
一種替代方法是長(zhǎng)輪詢(xún)技術(shù):如果服務(wù)器沒(méi)有可用數(shù)據(jù),則它可以使請(qǐng)求保持活動(dòng)狀態(tài),直到發(fā)生某種情況或達(dá)到預(yù)設(shè)的超時(shí)時(shí)間為止,而不是發(fā)送空響應(yīng)。如果存在新數(shù)據(jù),則完整的響應(yīng)將到達(dá)客戶(hù)端。完全不同的方法是反轉(zhuǎn)角色:當(dāng)有新數(shù)據(jù)可用(推送)時(shí),后端與客戶(hù)端聯(lián)系。
請(qǐng)記住,HTML 5具有標(biāo)準(zhǔn)化的WebSocket,這是一個(gè)永久的雙向連接,可以在兼容的瀏覽器中使用Javascript接口進(jìn)行配置。不幸的是,必須在客戶(hù)端和服務(wù)器端都對(duì)WebSocket提供完全支持,以使其可用。然后,我們需要提供替代系統(tǒng)(fallback),無(wú)論如何,該替代系統(tǒng)都允許我們的應(yīng)用程序運(yùn)行。
微軟于2013年發(fā)布了一個(gè)名為SignalR?for?ASP.NET的開(kāi)源庫(kù),該庫(kù)已于?2018年為ASP.NET Core進(jìn)行了重寫(xiě)。SignalR從與通信機(jī)制有關(guān)的所有細(xì)節(jié)中進(jìn)行抽象,并從可用的信息中選擇最佳的一種。
結(jié)果是有可能編寫(xiě)代碼,就像我們一直處于push-mode一樣。使用SignalR,服務(wù)器可以在其所有連接的客戶(hù)端或特定客戶(hù)端上調(diào)用JavaScript方法。
我們使用web-api模板創(chuàng)建一個(gè)ASP.NET Core項(xiàng)目,刪除已生成的示例控制器。使用NuGet,我們將Microsoft.AspNet.SignalR添加到項(xiàng)目中,以創(chuàng)建Hub。
集線(xiàn)器是能夠調(diào)用客戶(hù)端代碼,發(fā)送包含所請(qǐng)求方法的名稱(chēng)和參數(shù)的消息的高級(jí)管道。作為參數(shù)發(fā)送的對(duì)象將使用適當(dāng)?shù)膮f(xié)議反序列化。客戶(hù)端在頁(yè)面代碼中搜索與名稱(chēng)相對(duì)應(yīng)的方法,如果找到該名稱(chēng),則將其調(diào)用并傳遞反序列化的數(shù)據(jù)作為參數(shù)。
using Microsoft.AspNetCore.SignalR;namespace SignalR.Hubs {public class NotificationHub : Hub { } }您可能知道,在ASP.NET Core中,可以配置HTTP請(qǐng)求的管理管道,以添加一些中間件,該中間件可攔截請(qǐng)求,添加已配置的功能并使其進(jìn)入下一個(gè)中間件。必須預(yù)先配置SignalR中間件,在Startup?類(lèi)的ConfigureServices
方法中添加擴(kuò)展方法services.AddSignalR()。現(xiàn)在,我們可以使用Startup類(lèi)的
Configure方法中的擴(kuò)展方法app.UseSignalR()將中間件添加到管道中。
在此操作期間,我們可以傳遞配置參數(shù),包括集線(xiàn)器的路由:
app.UseSignalR(route => {route.MapHub<notificationhub>("/notificationHub"); })一個(gè)有趣的場(chǎng)景允許我們查看ASP.NET Core中的另一個(gè)有趣功能,即在后臺(tái)工作進(jìn)程上下文中托管SignalR Hub 。 假設(shè)我們要實(shí)現(xiàn)以下用例:
?運(yùn)行業(yè)務(wù)邏輯
?等一下?決定是停止還是重復(fù)該過(guò)程。
在ASP.NET Core中,我們可以使用框架提供的IHostedService接口在.NET Core應(yīng)用程序中在后臺(tái)實(shí)現(xiàn)進(jìn)程的執(zhí)行。方法要實(shí)現(xiàn)是StartAsync()和StopAsync() 。非常簡(jiǎn)單:StartAsync調(diào)用到主機(jī)啟動(dòng),而StopAsync調(diào)用到主機(jī)關(guān)閉。
然后,我們將一個(gè)類(lèi)DashboardHostedService添加到項(xiàng)目中,該類(lèi)實(shí)現(xiàn)
IHostedService。我們?cè)赟tartup類(lèi)的ConfigureServices方法中添加接口注冊(cè):
services.AddHostedService<dashboardhostedservice>();在類(lèi)構(gòu)造函數(shù)DashboardHostedService中,我們注入IHubContext
訪(fǎng)問(wèn)添加到我們應(yīng)用程序的集線(xiàn)器。在方法StartAsync中,我們?cè)O(shè)置了一個(gè)計(jì)時(shí)器,它將每?jī)擅腌娺\(yùn)行一次方法DoWork()中包含的代碼。此方法發(fā)送帶有四個(gè)隨意生成的字符串的消息。
但是它向誰(shuí)傳播呢?在我們的示例中,我們正在將消息發(fā)送到所有連接的客戶(hù)端。但是,SignalR提供了向單個(gè)用戶(hù)或用戶(hù)組發(fā)送消息的機(jī)會(huì)。在本文中[1],您將找到涉及ASP.NET Core中的身份驗(yàn)證和授權(quán)功能的詳細(xì)信息。
有趣的是,用戶(hù)可以同時(shí)在臺(tái)式機(jī)和移動(dòng)設(shè)備上連接。每個(gè)設(shè)備都有一個(gè)單獨(dú)的SignalR連接,但是它們都將與同一用戶(hù)關(guān)聯(lián)。
using Microsoft.AspNetCore.SignalR; using Microsoft.Extensions.Hosting; using SignalR.Hubs; using System; using System.Linq; using System.Threading; using System.Threading.Tasks;namespace SignalR {public class DashboardHostedService: IHostedService{private Timer _timer;private readonly IHubContext<notificationhub> _hubContext;public DashboardHostedService(IHubContext<notificationhub> hubContext){_hubContext = hubContext;}public Task StartAsync(CancellationToken cancellationToken){_timer = new Timer(DoWork, null, TimeSpan.Zero,TimeSpan.FromSeconds(2));return Task.CompletedTask;}private void DoWork(object state){_hubContext.Clients.All.SendAsync("SendMessage", new {val1 = getRandomString(),val2 = getRandomString(),val3 = getRandomString(),val4 = getRandomString()});}public Task StopAsync(CancellationToken cancellationToken){_timer?.Change(Timeout.Infinite, 0);return Task.CompletedTask;}} }讓我們看看如何管理客戶(hù)端部分。例如,我們使用Angular CLI的ng new SignalR命令創(chuàng)建Angular應(yīng)用程序。
然后我們安裝SignalR的包節(jié)點(diǎn)(
npm i @ aspnet / signalr
)。然后添加一個(gè)服務(wù),該服務(wù)使我們可以連接到先前創(chuàng)建的集線(xiàn)器并接收消息。在這里,第一種可能的方法是,基于服務(wù)getMessage()中Observable 的服務(wù),通過(guò)使用私有聲明的Subject?來(lái)返回(Message是與從Object返回的對(duì)象相對(duì)應(yīng)的Typescript接口。后端):
@Injectable({providedIn: 'root' }) export class SignalRService {private message$: Subject<message>;private connection: signalR.HubConnection;constructor() {this.message$ = new Subject<message>();this.connection = new signalR.HubConnectionBuilder().withUrl(environment.hubUrl).build();this.connect();}private connect() {this.connection.start().catch(err => console.log(err));this.connection.on('SendMessage', (message) => {this.message$.next(message);});}public getMessage(): Observable<message> {return this.message$.asObservable();}public disconnect() {this.connection.stop();} }在constructor()內(nèi)部,我們創(chuàng)建一個(gè)SignalR.HubConnection類(lèi)型對(duì)象,該對(duì)象將用于連接到服務(wù)器。我們通過(guò)使用文件environment.ts將其傳遞到其中心URL:
this.connection = new signalR.HubConnectionBuilder().withUrl(environment.hubUrl).build();構(gòu)造函數(shù)還負(fù)責(zé)調(diào)用connect()方法,該方法進(jìn)行實(shí)際連接,并在控制臺(tái)中記錄可能的錯(cuò)誤。
this.connection.start().catch(err => console.log(err)); this.connection.on('SendMessage', (message) => {this.message$.next(message); });想要顯示來(lái)自后端的消息的組件(將其注入到構(gòu)造函數(shù)中的服務(wù)),應(yīng)該訂閱getMessage()方法并管理到達(dá)的消息。以AppComponent為例,例如:
@Component({selector: 'app-root',templateUrl: './app.component.html',styleUrls: ['./app.component.css'] }) export class AppComponent implements OnDestroy {private signalRSubscription: Subscription;public content: Message;constructor(private signalrService: SignalRService) {this.signalRSubscription = this.signalrService.getMessage().subscribe((message) => {this.content = message;});}ngOnDestroy(): void {this.signalrService.disconnect();this.signalRSubscription.unsubscribe();} }使用主題允許我們同時(shí)管理更多組件,而無(wú)論從中心返回的消息(用于訂閱還是用于取消訂閱)都可以,但是我們必須注意對(duì)主題的粗心使用。讓我們考慮以下getMessage()版本:
public getMessage(): Observable<message> {return this.message$; }現(xiàn)在,該組件也可以使用以下簡(jiǎn)單代碼發(fā)送一條消息:
const produceMessage = this.signalrService.getMessage() as Subject<any>;produceMessage.next( {val1: 'a'}); </any>如果方法getMessage()返回SubjectasObservable,則此代碼將引發(fā)異常!我們可以在單個(gè)組件的情況下使用的第二種方法(更簡(jiǎn)單)對(duì)管理來(lái)自后端的消息感興趣:
@Injectable({providedIn: 'root' }) export class SignalrService {connection: signalR.HubConnection;constructor() {this.connection = new signalR.HubConnectionBuilder().withUrl(environment.hubAddress).build();this.connect();}public connect() {if (this.connection.state === signalR.HubConnectionState.Disconnected) {this.connection.start().catch(err => console.log(err));}}public getMessage(next) {this.connection.on('SendMessage', (message) => {next(message);});}public disconnect() {this.connection.stop();} }我們可以簡(jiǎn)單地將函數(shù)回調(diào)傳遞給方法getMessage,該函數(shù)將來(lái)自后端的消息作為參數(shù)。在這種情況下,AppComponent可以成為:
public content: IMessage; constructor(private signalrService: SignalrService) {this.signalrService.getMessage((message: IMessage) => {this.content = message;}); } ngOnDestroy(): void {this.signalrService.disconnect(); }最后幾行代碼分別位于app.component.html和app.component.css中
,以賦予一些時(shí)尚,并且該應(yīng)用程序已完成。
<div style="text-align:center"><h1>DASHBOARD</h1> </div> <div class="card-container"><div class="card"><div class="container"><h4><b>Valore 1</b></h4><p>{{content.val1}}</p></div></div><div class="card"><div class="container"><h4><b>Valore 2</b></h4><p>{{content.val2}}</p></div></div><div class="card"><div class="container"><h4><b>Valore 3</b></h4><p>{{content.val3}}</p></div></div><div class="card"><div class="container"><h4><b>Valore 4</b></h4><p>{{content.val4}}</p></div></div> </div>.card-container {display: flex;flex-wrap: wrap; }.card {box-shadow: 0 4px 8px 0 rgba(0,0,0,0.2);transition: 0.3s;width: 40%;flex-grow: 1;margin: 10px; }.card:hover {box-shadow: 0 8px 16px 0 rgba(0,0,0,0.2); }.container {padding: 2px 16px; }我們首先啟動(dòng)后端,然后啟動(dòng)前端并檢查最終結(jié)果:
看起來(lái)不錯(cuò)!您可以在這里找到代碼:https[2]?:?//github.com/AARNOLD87/SignalRWithAngular[3]
下次見(jiàn)!
References
[1]?本文中:?https://docs.microsoft.com/en-us/aspnet/core/signalr/groups?view=aspnetcore-2.2
[2]?https:?https://github.com/AARNOLD87/SignalRWithAngular
[3]?//github.com/AARNOLD87/SignalRWithAngular:?https://github.com/AARNOLD87/SignalRWithAngular
總結(jié)
以上是生活随笔為你收集整理的如何在ASP.NET Core中使用SignalR构建与Angular通信的实时通信应用程序的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 基于 Blazor 开发五子棋小游戏
- 下一篇: 关于技术文章“标题党”一事我想说两句