本章將和大家分享 ASP.NET Core SignalR 中的中心(服務端)。 本文大部分內容摘自微軟官網:https://learn.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-7.0 廢話不多說,我們直接來看一個De ...
本章將和大家分享 ASP.NET Core SignalR 中的中心(服務端)。
本文大部分內容摘自微軟官網:https://learn.microsoft.com/zh-cn/aspnet/core/signalr/hubs?view=aspnetcore-7.0
廢話不多說,我們直接來看一個Demo,Demo的目錄結構如下所示:
本Demo的Web項目為ASP.NET Core Web 應用程式(目標框架為.NET 7.0) MVC項目。
1、創建和使用中心
通過聲明繼承自 Hub 的類來創建中心。將方法添加到 public 類,使其可從客戶端調用:
using Microsoft.AspNetCore.SignalR; namespace SignalRChat.Hubs { /// <summary> /// Hub 類管理連接、組和消息 /// </summary> public class ChatHub : Hub { /// <summary> /// 可通過已連接客戶端調用 SendMessage,以向所有客戶端發送消息 /// </summary> public async Task SendMessage(string user, string message) { //Clients.All 向所有的客戶端發送消息(服務端調用客戶端) //ReceiveMessage 是客戶端監聽的方法 await Clients.All.SendAsync("ReceiveMessage", user, message); /* // 常用方法 // 給所有人發送消息 await Clients.All.SendAsync("ReceiveMessage", data); // 給組裡所有人發消息 await Clients.Group("Users").SendAsync("ReceiveMessage", data); // 給調用方法的那個人發消息 await Clients.Caller.SendAsync("ReceiveMessage", data); // 給除了調用方法的以外所有人發消息 await Clients.Others.SendAsync("ReceiveMessage", data); // 給指定connectionId的人發消息 await Clients.User(connectionId).SendAsync("ReceiveMessage", data); // 給指定connectionId的人發消息 await Clients.Client(connectionId).SendAsync("ReceiveMessage", data); // 給指定connectionId的人發消息,同時指定多個connectionId await Clients.Clients(IReadOnlyList<> connectionIds).SendAsync("ReceiveMessage", data); */ } } }
中心是暫時性的:
不要將狀態存儲在中心類的屬性中。每個中心方法調用都在新的中心實例上執行。
請勿通過依賴項註入直接實例化中心。若要從應用程式中的其他位置向客戶端發送消息,請使用 IHubContext 。
調用依賴於保持活動狀態的中心的非同步方法時請使用 await。例如,如果在沒有 await 的情況下進行調用,則 Clients.All.SendAsync(...) 這類方法會失敗,並且中心方法會在 SendAsync 完成之前完成。
2、配置 SignalR 中心
註冊中心所需的 SignalR 服務 以及 配置SignalR終結點,修改 Program.cs 文件的代碼,如下所示:
using SignalRChat.Hubs; namespace SignalRChat { public class Program { public static void Main(string[] args) { var builder = WebApplication.CreateBuilder(args); //服務註冊(往容器中添加服務) // Add services to the container. builder.Services.AddControllersWithViews(); builder.Services.AddSignalR(); //註冊中心所需的 SignalR 服務 var app = builder.Build(); //配置Http請求處理管道 // Configure the HTTP request pipeline. if (!app.Environment.IsDevelopment()) { app.UseExceptionHandler("/Home/Error"); } app.UseStaticFiles(); app.UseRouting(); app.UseAuthorization(); //配置MVC路由 app.MapControllerRoute( name: "areas", pattern: "{area:exists}/{controller=Home}/{action=Index}/{id?}"); app.MapControllerRoute( name: "default", pattern: "{controller=Home}/{action=Index}/{id?}"); //配置SignalR終結點 app.MapHub<ChatHub>("/chatHub"); //中心 app.MapHub<StronglyTypedChatHub>("/stronglyTypedChatHub"); //強類型中心 app.Run(); } } }
3、上下文對象
類 Hub 包含一個 Context 屬性,該屬性包含以下屬性以及有關連接的信息:
屬性 | 說明 |
ConnectionId | 獲取連接的唯一 ID(由 SignalR 分配)。每個連接都有一個連接 ID。 |
UserIdentifier | 獲取用戶標識符。 預設情況下,SignalR 使用與連接關聯的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作為用戶標識符。 |
User | 獲取與當前用戶關聯的 ClaimsPrincipal。 |
Items | 獲取可用於在此連接範圍內共用數據的鍵/值集合。數據可以存儲在此集合中,會在不同的中心方法調用間為連接持久保存。 |
Features | 獲取連接上可用的功能的集合。目前,在大多數情況下不需要此集合,因此未對其進行詳細記錄。 |
ConnectionAborted | 獲取一個 CancellationToken,它會在連接中止時發出通知。 |
Hub.Context 還包含以下方法:
方法 | 說明 |
GetHttpContext | 獲取 Http 請求的上下文對象,如果不是 Http 請求,則返回 null 。 |
Abort | 中止連接。 |
4、客戶端對象
類 Hub 包含一個 Clients 屬性,該屬性包含以下屬性,用於伺服器和客戶端之間的通信:
屬性 | 說明 |
All | 對所有連接的客戶端調用方法 |
Caller | 對調用了中心方法的客戶端調用方法 |
Others | 對所有連接的客戶端調用方法(調用了方法的客戶端除外) |
Hub.Clients 還包含以下方法:
方法 | 說明 |
AllExcept | 對所有連接的客戶端調用方法(指定連接除外) |
Client | 對連接的一個特定客戶端調用方法 |
Clients | 對連接的多個特定客戶端調用方法 |
Group | 對指定組中的所有連接調用方法 |
GroupExcept | 對指定組中的所有連接調用方法(指定連接除外) |
Groups | 對多個連接組調用方法 |
OthersInGroup | 對一個連接組調用方法(不包括調用了中心方法的客戶端) |
User | 對與一個特定用戶關聯的所有連接調用方法 |
Users | 對與多個指定用戶關聯的所有連接調用方法 |
以上表中的每個屬性或方法都返回具有 SendAsync 方法的對象。 方法 SendAsync 接收要調用的客戶端方法的名稱和任何參數。
5、向客戶端發送消息
若要對特定客戶端發出調用,請使用 Clients 對象的屬性。 在以下示例中,有三種中心方法:
using Microsoft.AspNetCore.SignalR; namespace SignalRChat.Hubs { /// <summary> /// Hub 類管理連接、組和消息 /// </summary> public class ChatHub : Hub { /// <summary> /// 可通過已連接客戶端調用 SendMessage,以向所有客戶端發送消息 /// </summary> public async Task SendMessage(string user, string message) { //Clients.All 向所有的客戶端發送消息(服務端調用客戶端) //ReceiveMessage 是客戶端監聽的方法 await Clients.All.SendAsync("ReceiveMessage", user, message); //將消息發送到所有連接的客戶端 /* // 常用方法 // 給所有人發送消息 await Clients.All.SendAsync("ReceiveMessage", data); // 給組裡所有人發消息 await Clients.Group("Users").SendAsync("ReceiveMessage", data); // 給調用方法的那個人發消息 await Clients.Caller.SendAsync("ReceiveMessage", data); // 給除了調用方法的以外所有人發消息 await Clients.Others.SendAsync("ReceiveMessage", data); // 給指定connectionId的人發消息 await Clients.User(connectionId).SendAsync("ReceiveMessage", data); // 給指定connectionId的人發消息 await Clients.Client(connectionId).SendAsync("ReceiveMessage", data); // 給指定connectionId的人發消息,同時指定多個connectionId await Clients.Clients(IReadOnlyList<> connectionIds).SendAsync("ReceiveMessage", data); */ } public async Task SendMessageToCaller(string user, string message) => await Clients.Caller.SendAsync("ReceiveMessage", user, message); //將消息發送回調用方 public async Task SendMessageToGroup(string user, string message) => await Clients.Group("SignalR Users").SendAsync("ReceiveMessage", user, message); //將消息發送給 SignalR Users 組中的所有客戶端 } }
6、強類型中心
使用 SendAsync 的缺點是它依賴於字元串來指定要調用的客戶端方法。 如果客戶端中的方法名稱拼寫錯誤或缺失,則這會使代碼可能出現運行時錯誤。
使用 SendAsync 的另一種方法是使用 Hub<T>強類型Hub類。 在以下示例中 ChatHub ,客戶端方法已提取到名為 的 IChatClient介面中:
namespace SignalRChat.Hubs { public interface IChatClient { /// <summary> /// 客戶端方法 /// </summary> Task ReceiveMessage(string user, string message); } }
此介面可用於將前面的 ChatHub 示例重構為強類型:
using Microsoft.AspNetCore.SignalR; namespace SignalRChat.Hubs { /// <summary> /// 強類型中心 /// 中心是暫時性的:不要將狀態存儲在中心類的屬性中。每個中心方法調用都在新的中心實例上執行。 /// </summary> public class StronglyTypedChatHub : Hub<IChatClient> { [HubMethodName("SendMessage")] //更改中心方法的名稱 public async Task SendMessage(string user, string message) => await Clients.All.ReceiveMessage(user, message); //將消息發送到所有連接的客戶端 public async Task SendMessageToCaller(string user, string message) => await Clients.Caller.ReceiveMessage(user, message); //將消息發送回調用方 public async Task SendMessageToGroup(string user, string message) => await Clients.Group("SignalR Users").ReceiveMessage(user, message); //將消息發送給 SignalR Users 組中的所有客戶端 } }
使用 Hub<IChatClient> 可以對客戶端方法進行編譯時檢查。 這可以防止使用字元串導致的問題,因為 Hub<T> 只能提供對 介面中定義的方法的訪問。 使用強類型 Hub<T> 會禁止使用 SendAsync。
備註:Async尾碼不會從方法名稱中去除。 除非使用 .on('MyMethodAsync')定義客戶端方法,否則不要使用 MyMethodAsync 作為名稱。
7、更改中心方法的名稱
預設情況下,伺服器中心方法名稱是 .NET 方法的名稱。若要更改特定方法的此預設行為,請使用 HubMethodName 特性。調用方法時,客戶端應使用此名稱而不是 .NET 方法名稱:
[HubMethodName("SendMessageToUser")] public async Task DirectMessage(string user, string message) => await Clients.User(user).SendAsync("ReceiveMessage", user, message);
8、將服務註入中心
中心構造函數可以接受 DI 中的服務作為參數,這些參數可以存儲在類的屬性中,以便在中心方法中使用。
為不同的中心方法註入多個服務或作為編寫代碼的替代方法時,中心方法也可以接受 DI 中的服務。 預設情況下,如果可能,將從 DI 檢查和解析中心方法參數。
services.AddSingleton<IDatabaseService, DatabaseServiceImpl>(); // ... public class ChatHub : Hub { public Task SendMessage(string user, string message, IDatabaseService dbService) { var userName = dbService.GetUserName(user); return Clients.All.SendAsync("ReceiveMessage", userName, message); } }
如果不需要從服務隱式解析參數,請使用 DisableImplicitFromServicesParameters 禁用它。 若要在中心方法中顯式指定從 DI 解析的參數,請使用 DisableImplicitFromServicesParameters 選項,並使用 [FromServices] 屬性或自定義屬性,該屬性在應從 DI 解析的中心方法參數上實現 IFromServiceMetadata 。
services.AddSingleton<IDatabaseService, DatabaseServiceImpl>(); services.AddSignalR(options => { options.DisableImplicitFromServicesParameters = true; }); // ... public class ChatHub : Hub { public Task SendMessage(string user, string message, [FromServices] IDatabaseService dbService) { var userName = dbService.GetUserName(user); return Clients.All.SendAsync("ReceiveMessage", userName, message); } }
9、為連接處理事件
SignalR 中心 API 提供 OnConnectedAsync 和 OnDisconnectedAsync 虛方法來管理和跟蹤連接。
/// <summary> /// 在客戶端連接到中心時執行操作 /// </summary> /// <returns></returns> public override async Task OnConnectedAsync() { await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users"); await base.OnConnectedAsync(); } /// <summary> /// 在客戶端斷開連接時執行操作 /// </summary> /// <param name="exception"></param> /// <returns></returns> public override async Task OnDisconnectedAsync(Exception? exception) { await base.OnDisconnectedAsync(exception); }
其中如果客戶端有意斷開連接(例如通過調用 connection.stop()),則 exception 參數為 null。但是,如果客戶端由於錯誤(例如網路故障)而斷開連接,則 exception 參數包含描述故障的異常。
RemoveFromGroupAsync 無需在 OnDisconnectedAsync 中調用,系統會自動處理它。
10、從中心外部發送消息
SignalR 中心是用於向連接到 SignalR 伺服器的客戶端發送消息的核心抽象。 你也可以使用 IHubContext 服務從應用中的其他位置發送消息。
備註:IHubContext 用於將通知發送到客戶端,而非用於調用 Hub 上的方法。
1)獲取 IHubContext 實例
在 ASP.NET Core SignalR 中,你可以通過依賴項註入來訪問 IHubContext 實例。 你可以將 IHubContext 實例註入控制器、中間件或其他 DI 服務。 使用該實例向客戶端發送消息。
2)在控制器中註入 IHubContext 實例
通過將 IHubContext 實例添加到構造函數,可以將其註入控制器:
public class HomeController : Controller { private readonly IHubContext<NotificationHub> _hubContext; public HomeController(IHubContext<NotificationHub> hubContext) { _hubContext = hubContext; } }
獲權訪問 IHubContext 實例後,就像在中心本身一樣調用客戶端方法:
public async Task<IActionResult> Index() { await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: {DateTime.Now}"); return View(); }
3)在中間件中獲取 IHubContext 實例
訪問中間件管道中的 IHubContext,如下所示:
app.Use(async (context, next) => { var hubContext = context.RequestServices .GetRequiredService<IHubContext<ChatHub>>(); //... if (next != null) { await next.Invoke(); } });
備註:當從 Hub 類外部調用客戶端方法時,沒有與該調用關聯的調用方。 因此,無法訪問 ConnectionId、Caller 和 Others 屬性。
4)從 IHost 獲取 IHubContext 實例
從 Web 主機訪問 IHubContext 對於與 ASP.NET Core 之外的區域集成很有用,例如,使用第三方依賴項註入框架:
public class Program { public static void Main(string[] args) { var host = CreateHostBuilder(args).Build(); var hubContext = host.Services.GetService(typeof(IHubContext<ChatHub>)); host.Run(); } public static IHostBuilder CreateHostBuilder(string[] args) => Host.CreateDefaultBuilder(args) .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); }); }
5)註入強類型 HubContext
若要註入強類型 HubContext,請確保中心繼承自 Hub<T>。 使用 IHubContext<THub, T> 介面而不是 IHubContext<THub> 進行註入。
public class ChatController : Controller { public IHubContext<ChatHub, IChatClient> _strongChatHubContext { get; } public ChatController(IHubContext<ChatHub, IChatClient> chatHubContext) { _strongChatHubContext = chatHubContext; } public async Task SendMessage(string user, string message) { await _strongChatHubContext.Clients.All.ReceiveMessage(user, message); } }
6)在泛型代碼中使用 IHubContext
註入的 IHubContext<THub> 實例可以強制轉換為 IHubContext,而無需指定泛型 Hub 類型。
class MyHub : Hub { } class MyOtherHub : Hub { } app.Use(async (context, next) => { var myHubContext = context.RequestServices .GetRequiredService<IHubContext<MyHub>>(); var myOtherHubContext = context.RequestServices .GetRequiredService<IHubContext<MyOtherHub>>(); await CommonHubContextMethod((IHubContext)myHubContext); await CommonHubContextMethod((IHubContext)myOtherHubContext); await next.Invoke(); } async Task CommonHubContextMethod(IHubContext context) { await context.Clients.All.SendAsync("clientMethod", new Args()); }
此操作在以下情況下十分有用:
- 編寫不引用應用正在使用的特定 Hub 類型的庫。
- 編寫可應用於多個不同 Hub 實現的泛型代碼。
11、管理 SignalR 中的用戶和組
SignalR 允許將消息發送到與特定用戶關聯的所有連接,以及指定的連接組。
1)SignalR 中的用戶
SignalR 中的單個用戶可以與一個應用建立多個連接。 例如,用戶可以在桌面和手機上進行連接。 每台設備都有一個單獨的 SignalR 連接,但它們都與同一個用戶關聯。 如果向用戶發送消息,則與該用戶關聯的所有連接都會收到消息。 可以通過中心內的 Context.UserIdentifier 屬性訪問連接的用戶標識符。
預設情況下,SignalR 使用與連接關聯的 ClaimsPrincipal 中的 ClaimTypes.NameIdentifier 作為用戶標識符。 若要自定義此行為,請參閱使用聲明自定義標識處理。
通過將用戶標識符傳遞給中心方法中的 User 函數,向特定用戶發送消息,如以下示例所示:
public Task SendPrivateMessage(string user, string message) { return Clients.User(user).SendAsync("ReceiveMessage", message); }
備註:用戶標識符區分大小寫。
2)SignalR 中的組
組是與名稱關聯的連接集合。你可以將消息發送到組中的所有連接。建議通過組發送到一個或多個連接,因為組由應用程式管理。一個連接可以是多個組的成員。組非常適合聊天應用程式之類的應用,其中每個聊天室都可以表示為一個組。可通過 AddToGroupAsync 和 RemoveFromGroupAsync 方法在組中添加或刪除連接。
public async Task AddToGroup(string groupName) { await Groups.AddToGroupAsync(Context.ConnectionId, groupName); await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has joined the group {groupName}."); } public async Task RemoveFromGroup(string groupName) { await Groups.RemoveFromGroupAsync(Context.ConnectionId, groupName); await Clients.Group(groupName).SendAsync("Send", $"{Context.ConnectionId} has left the group {groupName}."); }
重新連接時不會保留組成員身份。重新建立連接後,需要重新加入組。無法計算組的成員數,因為如果將應用程式擴展到多台伺服器,則無法獲取此信息。
若要在使用組時保護對資源的訪問,請使用 ASP.NET Core 中的身份驗證和授權功能。如果僅當憑據對組有效時才將用戶添加到該組,則發送到該組的消息將僅發送給授權用戶。但是,組不是一項安全功能。身份驗證聲明具有組不具備的功能,例如到期和撤銷。如果撤銷用戶對組的訪問許可權,應用必須從組中顯式刪除該用戶。
備註:組名稱區分大小寫。
12、SignalR API 設計註意事項
使用自定義對象參數確保向後相容性
將新的參數添加到 SignalR 客戶端或伺服器上的中心方法是一項重大更改。這意味著,較舊的客戶端/伺服器在嘗試調用沒有適當數量參數的方法時會出錯。但是,向自定義對象參數添加屬性不是一項中斷性變更。這可用於設計相容的 API,以適應客戶端或伺服器上的更改。
使用自定義對象作為參數可提供更大的靈活性,如下所示:
public class TotalLengthRequest { public string Param1 { get; set; } public string Param2 { get; set; } } public async Task GetTotalLength(TotalLengthRequest req) { var length = req.Param1.Length; if (req.Param2 != null) { length += req.Param2.Length; } return length; }
當舊客戶端發送單個參數時,額外的 Param2
屬性將保留為 null
。 你可以通過檢查 Param2
是否為 null
來檢測舊客戶端發送的消息並應用預設值。 新客戶端可以發送這兩個參數。
connection.invoke("GetTotalLength", { param1: "value1", param2: "value2" });
此技術同樣適用於客戶端上定義的方法。 你可以從伺服器端發送自定義對象:
public async Task Broadcast(string message) { await Clients.All.SendAsync("ReceiveMessage", new { Sender = Context.User.Identity.Name, Message = message }); }
舊客戶端不需要 Sender
值,因此會忽略它。 新客戶端可以通過更新為讀取新屬性來接受它:
connection.on("ReceiveMessage", (req) => { let message = req.message; if (req.sender) { message = req.sender + ": " + message; } appendMessageToChatWindow(message); });
在這種情況下,新客戶端也可以容忍不提供 Sender
值的舊伺服器。由於舊伺服器不提供 Sender
值,因此客戶端在訪問它之前會檢查它是否存在。
Demo源碼:
鏈接:https://pan.baidu.com/s/1AbGaPRfv2vAskHRAnOlvYA 提取碼:456q
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/17536693.html
版權聲明:如有雷同純屬巧合,如有侵權請及時聯繫本人修改,謝謝!!!