SignalR是一個自己蠻有興趣的東西,提供Server與Client端之間的即時通訊,而且使用也不算複雜。
SignalR所提供的功能有:
- 自動處理連接管理。
- 同時將訊息傳送給所有已連線的用戶端。 例如,聊天室。
- 將訊息傳送給特定用戶端或用戶端群組。
- 調整以處理增加的流量。
這次利用SignalR來做個訊息推播(Server to Client) & 廣播(Client to Client)的簡單範例。
- 後端 [後端程式]
1.建立SignalR Hub
using Microsoft.AspNetCore.SignalR;
namespace SignalR_Test
{
public class MyHub: Hub
{
public async Task BraodCast(string data)
{
await Clients.All.SendAsync("HubMethodBroadcast", data);
}
}
}
2.Program.cs註冊SignalR相關服務& 注入
using SignalR_Test;
var builder = WebApplication.CreateBuilder(args);
//CORS設定
builder.Services.AddCors(options =>
{
options.AddPolicy("CorsPolicy", builder => builder
.WithOrigins("http://localhost:4200")
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
// Add services to the container.
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
//註冊相關服務
builder.Services.AddSignalR();
builder.Services.AddSingleton<TimerManager>();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseSwagger();
app.UseSwaggerUI();
}
app.UseHttpsRedirection();
app.UseCors("CorsPolicy");
//設定SignalR Hub路由路徑
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapHub<MyHub>("/myHub");
});
app.UseAuthorization();
app.MapControllers();
app.Run();
2.建立時間模組(TimerManager),設定推播間隔秒數以及推播時間要多長
namespace SignalR_Test
{
public class TimerManager
{
private Timer? _timer;
private AutoResetEvent? _autoResetEvent;
private Action? _action;
public DateTime TimerStarted { get; set; }
public bool IsTimerStarted { get; set; }
public void PrepareTimer(Action action)
{
_action = action;
_autoResetEvent = new AutoResetEvent(false);
_timer = new Timer(Execute, _autoResetEvent, 1000, 5000);//1秒後開始執行,後續5秒執行推播一次
TimerStarted = DateTime.Now;
IsTimerStarted = true;
}
public void Execute(object? stateInfo)
{
_action();
//連線60秒後,中斷SignalR(Server to Client)的推播
if ((DateTime.Now - TimerStarted).TotalSeconds > 60)
{
IsTimerStarted = false;
_timer.Dispose();
}
}
}
}
3.建立API Controller Method
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.SignalR;
namespace SignalR_Test.Controllers
{
[ApiController]
[Route("api/[controller]/[action]")]
public class SignalRController : ControllerBase
{
private readonly IHubContext<MyHub> _hub;
private readonly TimerManager _timer;
public SignalRController(IHubContext<MyHub> hub, TimerManager timer)
{
_hub = hub;
_timer = timer;
}
public IActionResult Subscribe()
{
if (!_timer.IsTimerStarted)
{//是否在連線時限60秒內,是的話就調用Hub Method [HubMethodGetRandomNumber]
_timer.PrepareTimer(() => _hub.Clients.All.SendAsync("HubMethodGetRandomNumber", GetRandom()));
}
return Ok(new { Message = "Request Completed" });
}
public int GetRandom()
{
var rnd = new Random();
var Res = rnd.Next(0, 100);
return Res;
}
}
}
- 前端 [前端程式]
1.安裝 @microsoft/signalr
npm install @microsoft/signalr
2.建立SignalR Service
import { Injectable } from '@angular/core';
import * as signalR from '@microsoft/signalr'
import { environment } from 'src/environments/environment';
@Injectable({
providedIn: 'root'
})
export class SignalrService {
public broadcastMsg: string = '';
hubConn: signalR.HubConnection | undefined
url: string = `${environment.apiBaseUrl}`;
constructor() { }
//建立SignalR 連線
public startConnection = () => {
//建立SignalR Hub 連線,withUrl的參數要跟
this.hubConn = new signalR.HubConnectionBuilder().withUrl(`${this.url}/myHub`).build();
//開始連線
this.hubConn.start()
.then(() => {
console.log('signalr connection start');
})
.catch(
err => {
console.log(err);
}
)
}
//監聽Hub Method [HubMethodGetRandomNumber]並取得結果
public addMethodListener = () => {
this.hubConn?.on('HubMethodGetRandomNumber', (data) => {
console.log(data);
})
}
//透過調用server端MyHub的BraodCast方法,來調用Hub Method [HubMethodBroadcast]
public broadcastMessage(msg: string): void{
this.hubConn?.invoke('BraodCast', msg)
.catch(err => {
console.error(err);
})
}
//監聽Hub Method [HubMethodBroadcast]並取得結果
public broadcastMethodListener(): void{
this.hubConn?.on('HubMethodBroadcast', (data) => {
this.broadcastMsg = data;
console.log(data);
})
}
}
this.hubConn = new signalR.HubConnectionBuilder().withUrl(`${this.url}/myHub`).build();
的參數必須要跟後端的SignalR Hub路由路徑一致(大小寫沒有差),以本篇文章的範例來說,SignalR Hub的完整路徑為[https://localhost:7193/myHub]
his.hubConn?.on('HubMethodGetRandomNumber', (data) => {……})
的HubMethodGetRandomNumber
這個參數,跟後端Subscribe()
Method的 _hub.Clients.All.SendAsync("HubMethodGetRandomNumber", GetData())
的第一個參數的HubMethodGetRandomNumber要一樣,指的是前端要監聽的SignalR Hub Method的名稱,而GetRandom則是實際要執行的callback function。
3.頁面實際應用
import { HttpClient, HttpResponse } from '@angular/common/http';
import { Component, OnInit } from '@angular/core';
import { SignalrService } from 'src/app/core/services/signalr.service';
import { environment } from 'src/environments/environment';
@Component({
selector: 'app-home',
templateUrl: './home.component.html',
styleUrls: ['./home.component.scss']
})
export class HomeComponent implements OnInit {
constructor(
private _signalrService: SignalrService,
private _http: HttpClient
) { }
ngOnInit(): void
{
//建立SignalR 連線
this._signalrService.startConnection();
//監聽Hub Method [HubMethodGetRandomNumber]並取得結果
this._signalrService.addMethodListener();
//透過呼叫api,調用Hub Method [HubMethodGetRandomNumber],並取得結果
this._http.get<any>(`${environment.apiBaseUrl}/api/SignalR/Subscribe`).subscribe(res => {
console.log(res.message);
})
//監聽Hub Method [HubMethodGetRandomNumber]並取得結果
this._signalrService.broadcastMethodListener();
}
//發送廣播訊息
broadcastMessage(): void{
this._signalrService.broadcastMessage("1111");
}
}
4.應用頁面HTML,因應訊息廣播,增加一個Button來發送廣播訊息
<p>home works!</p>
<button (click)="broadcastMessage();">Broadcast.</button>
- 實際測試結果(Server to all Client)
可以看到分別開兩個不同的瀏覽器頁面,但所接收到的推播訊息都是一樣的。
- 實際測試結果(Client to all Client),按下Broadcast後,兩個瀏覽器的畫面都會分別收到1111的廣播訊息。
Ref:
1.NET Core with SignalR and Angular – Real-Time Charts
2.https://ithelp.ithome.com.tw/articles/10251470