ASP.NET Core: Sử dụng hub trong SignalR cho ASP.NET Core
API Hub SignalR cho phép các máy khách được kết nối với các phương thức trên máy chủ. Máy chủ định nghĩa các phương thức được gọi từ máy khách và máy khách định nghĩa các phương thức được gọi từ máy chủ. SignalR chắc chắn nhận mọi thứ cần thiết để có thể giao tiếp giữa máy khách với máy chủ và máy chủ với máy khách theo thời gian thực.
Định cấu hình hub SignalR
Để đăng ký các dịch vụ mà hub SignalR yêu cầu, hãy gọi AddSignalR trong Program.cs
:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddRazorPages();
builder.Services.AddSignalR();
Để định cấu hình điểm cuối SignalR, hãy gọi MapHub, cũng như Program.cs
:
app.MapRazorPages();
app.MapHub<ChatHub>("/Chat");
app.Run();
Lưu ý
Các assembly phía máy chủ ASP.NET Core SignalR đã được cài đặt với .NET Core SDK. Xem Assembly SignalR trong framework chia sẻ để biết thêm thông tin.
Tạo và sử dụng hub
Ta tạo một hub bằng cách khai báo một lớp kế thừa từ Hub. Thêm phương thức public
vào lớp để làm cho chúng có thể được gọi từ máy khách:
public class ChatHub : Hub
{
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
}
Lưu ý
Các hub là tạm thời:
- Không lưu trữ trạng thái trong property của lớp hub. Mỗi lời gọi phương thức hub đều được thực hiện trên một thể hiện hub mới.
- Không khởi tạo một hub trực tiếp thông qua Dependency Injection. Để gửi tin nhắn đến ứng dụng khách từ nơi khác trong ứng dụng của bạn, hãy sử dụng IHubContext.
- Sử dụng
await
khi gọi các phương thức không đồng bộ phụ thuộc vào việc hub vẫn hoạt động. Ví dụ: một phương thức chẳng hạn nhưClients.All.SendAsync(...)
có thể thất bại nếu nó được gọi mà không cóawait
và phương thức hub hoàn thành trước khiSendAsync
kết thúc.
Đối tượng Context
Lớp Hub bao gồm thuộc tính Context chứa các property sau với thông tin về kết nối:
Property | Mô tả |
---|---|
ConnectionId | Nhận ID duy nhất cho kết nối, được chỉ định bởi SignalR. Có một ID kết nối cho mỗi kết nối. |
UserIdentifier | Nhận định danh người dùng. Theo mặc định, SignalR sử dụng ClaimTypes.NameIdentifier từ ClaimsPrincipal được liên kết với kết nối làm định danh người dùng. |
User | Nhận ClaimsPrincipal được liên kết với người dùng hiện tại. |
Items | Nhận một collection key/value có thể được sử dụng để chia sẻ dữ liệu trong phạm vi của kết nối này. Dữ liệu có thể được lưu trữ trong bộ sưu tập này và nó sẽ tồn tại cho kết nối qua các lời gọi phương thức hub khác nhau. |
Features | Nhận collection các tính năng có sẵn trên kết nối. Hiện tại, collection này không cần thiết trong hầu hết các trường hợp, vì vậy nó chưa được ghi lại chi tiết. |
ConnectionAborted | Nhận một CancellationToken để thông báo khi kết nối bị hủy bỏ. |
Hub.Context cũng chứa các phương thức sau:
Phương thức | Mô tả |
---|---|
GetHttpContext | Trả về HttpContext cho kết nối hoặc null nếu kết nối không được liên kết với yêu cầu HTTP. Đối với các kết nối HTTP, hãy sử dụng phương pháp này để lấy thông tin như tiêu đề HTTP và chuỗi truy vấn. |
Huỷ bỏ | Hủy bỏ kết nối. |
Đối tượng Client
Lớp Hub bao gồm property Clients chứa các property sau để liên lạc giữa máy chủ và máy khách:
Property | Mô tả |
---|---|
All | Gọi một phương thức trên tất cả các máy khách được kết nối |
Caller | Gọi một phương thức trên máy khách đã gọi phương thức hub |
Others | Gọi một phương thức trên tất cả các máy khách được kết nối ngoại trừ máy khách đã gọi phương thức đó |
Hub.Clients cũng chứa các phương thức sau:
Phương thức | Mô tả |
---|---|
AllExcept | Gọi một phương thức trên tất cả các máy khách được kết nối ngoại trừ các kết nối được chỉ định |
Client | Gọi một phương thức trên một máy khách được kết nối cụ thể |
Clients | Gọi một phương thức trên các máy khách được kết nối cụ thể |
Group | Gọi một phương thức trên tất cả các kết nối trong nhóm được chỉ định |
GroupExcept | Gọi một phương thức trên tất cả các kết nối trong nhóm được chỉ định, ngoại trừ các kết nối được chỉ định |
Groups | Gọi một phương thức trên nhiều nhóm kết nối |
OthersInGroup | Gọi một phương thức trên một nhóm các kết nối, ngoại trừ máy khách đã gọi phương thức hub |
User | Gọi một phương thức trên tất cả các kết nối được liên kết với một người dùng cụ thể |
Users | Gọi một phương thức trên tất cả các kết nối được liên kết với người dùng được chỉ định |
Mỗi thuộc tính hoặc phương thức trong các bảng trên trả về một đối tượng với một phương thức SendAsync
. Phương thức SendAsync
nhận tên của phương thức máy khách để gọi và bất kỳ tham số nào.
Đối tượng được trả về bởi các phương thức Client
và Caller
cũng chứa một phương thức InvokeAsync
, có thể được sử dụng để đợi kết quả từ máy khách.
Gửi tin nhắn cho khách hàng
Để thực hiện cuộc gọi đến các máy khách cụ thể, hãy sử dụng các thuộc tính của đối tượng Clients
. Trong ví dụ sau, có ba phương thức hub:
SendMessage
gửi một tin nhắn đến tất cả các máy khách được kết nối, sử dụngClients.All
.SendMessageToCaller
gửi tin nhắn lại cho người gọi, sử dụngClients.Caller
.SendMessageToGroup
gửi một tin nhắn cho tất cả các khách hàng trong nhómSignalR Users
.
public async Task SendMessage(string user, string message)
=> await Clients.All.SendAsync("ReceiveMessage", user, message);
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);
Hub định kiểu mạnh
Một nhược điểm của việc sử dụng SendAsync
là nó dựa vào một chuỗi để chỉ định phương thức máy khách sẽ được gọi. Điều này khiến code dễ bị lỗi thời gian chạy nếu tên phương thức bị sai chính tả hoặc thiếu từ ứng dụng khách.
Một cách khác để sử dụng SendAsync
là định kiểu mạnh lớp Hub bằng Hub<T>. Trong ví dụ sau, phương thức máy khách ChatHub
đã được trích xuất thành một giao diện có tên IChatClient
:
public interface IChatClient
{
Task ReceiveMessage(string user, string message);
}
Giao diện này có thể được sử dụng để cấu trúc lại ví dụ ChatHub
ở trên để định kiểu mạnh:
public class StronglyTypedChatHub : Hub<IChatClient>
{
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);
}
Việc sử dụng Hub<IChatClient>
cho phép kiểm tra thời gian biên dịch các phương thức máy khách. Điều này ngăn các sự cố do sử dụng chuỗi gây ra, vì Hub<T>
chỉ có thể cung cấp quyền truy cập vào các phương thức được xác định trong giao diện. Sử dụng định kiểu mạnh Hub<T>
sẽ vô hiệu hóa khả năng sử dụng SendAsync
.
Lưu ý
Hậu tố Async
không bị tước khỏi tên phương thức. Trừ khi một phương thức máy khách được định nghĩa bằng .on('MyMethodAsync')
, không sử dụng MyMethodAsync
làm tên.
Kết quả Client
Ngoài việc thực hiện lời gọi đến máy khách, máy chủ có thể yêu cầu kết quả từ máy khách. Điều này yêu cầu máy chủ sử dụng ISingleClientProxy.InvokeAsync
và máy khách trả về kết quả từ trình xử lý .On
của nó.
Có hai cách để sử dụng API trên máy chủ, cách thứ nhất là gọi Client(...)
hoặc Caller
trên property Clients
theo phương thức Hub:
public class ChatHub : Hub
{
public async Task<string> WaitForMessage(string connectionId)
{
var message = await Clients.Client(connectionId).InvokeAsync<string>(
"GetMessage");
return message;
}
}
Cách thứ hai là gọi Client(...)
trên một phiên bản của IHubContext<T>:
async Task SomeMethod(IHubContext<MyHub> context)
{
string result = await context.Clients.Client(connectionID).InvokeAsync<string>(
"GetMessage");
}
Hub định kiểu mạnh cũng có thể trả về các giá trị từ các phương thức giao diện:
public interface IClient
{
Task<string> GetMessage();
}
public class ChatHub : Hub<IClient>
{
public async Task<string> WaitForMessage(string connectionId)
{
string message = await Clients.Client(connectionId).GetMessage();
return message;
}
}
Clients trả lại kết quả trong trình xử lý .On(...)
của chúng, như được hiển thị bên dưới:
.NET client
hubConnection.On("GetMessage", async () =>
{
Console.WriteLine("Enter message:");
var message = await Console.In.ReadLineAsync();
return message;
});
Typescript client
hubConnection.on("GetMessage", async () => {
let promise = new Promise((resolve, reject) => {
setTimeout(() => {
resolve("message");
}, 100);
});
return promise;
});
Java client
hubConnection.onWithResult("GetMessage", () -> {
return Single.just("message");
});
Thay đổi tên của một phương thức hub
Theo mặc định, tên phương thức hub máy chủ là tên của phương thức .NET. Để thay đổi hành vi mặc định này cho một phương thức cụ thể, hãy sử dụng property HubMethodName. Client nên sử dụng tên này thay vì tên phương thức .NET khi gọi phương thức:
[HubMethodName("SendMessageToUser")]
public async Task DirectMessage(string user, string message)
=> await Clients.User(user).SendAsync("ReceiveMessage", user, message);
Inject dịch vụ vào một hub
Các hàm tạo của hub có thể chấp nhận các dịch vụ từ DI làm tham số, có thể được lưu trữ trong các property trên lớp để sử dụng trong phương thức hub.
Khi đưa vào nhiều dịch vụ cho các phương thức hub khác nhau hoặc như một cách viết code thay thế, các phương thức hub cũng có thể chấp nhận các dịch vụ từ DI. Theo mặc định, các tham số của phương thức hub được kiểm tra và giải quyết từ DI nếu có thể.
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);
}
}
Nếu không muốn có độ phân giải ngầm định của các tham số từ các dịch vụ, hãy tắt nó bằng DisableImplicitFromServicesParameters. Để chỉ định rõ ràng tham số nào được phân giải từ DI trong các phương thức hub, hãy sử dụng tùy chọn DisableImplicitFromServicesParameters và sử dụng attribute [FromServices]
hoặc attribute tùy chỉnh triển khai IFromServiceMetadata
trên các tham số của phương thức hub sẽ được phân giải từ DI.
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);
}
}
Lưu ý
Tính năng này sử dụng IServiceProviderIsService, được triển khai tùy chọn bởi triển khai DI. Nếu vùng chứa DI của ứng dụng không hỗ trợ tính năng này, thì việc đưa dịch vụ vào các phương thức hub sẽ không được hỗ trợ.
Xử lý các sự kiện cho một kết nối
API Hub SignalR cung cấp các phương thức ảo OnConnectedAsync và OnDisconnectedAsync để quản lý và theo dõi các kết nối. Ghi đè phương thức virtual OnConnectedAsync
để thực hiện các hành động khi máy khách kết nối với hub, chẳng hạn như thêm nó vào một nhóm:
public override async Task OnConnectedAsync()
{
await Groups.AddToGroupAsync(Context.ConnectionId, "SignalR Users");
await base.OnConnectedAsync();
}
Ghi đè phương thức ảo OnDisconnectedAsync
để thực hiện các hành động khi máy khách ngắt kết nối. Nếu máy khách cố ý ngắt kết nối, chẳng hạn như bằng cách gọi connection.stop()
, thì tham số exception
được đặt thành null
. Tuy nhiên, nếu máy khách ngắt kết nối do lỗi, chẳng hạn như lỗi mạng, thì tham số exception
chứa một ngoại lệ mô tả lỗi:
public override async Task OnDisconnectedAsync(Exception? exception)
{
await base.OnDisconnectedAsync(exception);
}
RemoveFromGroupAsync không cần phải được gọi trong OnDisconnectedAsync, nó sẽ tự động xử lý cho bạn.
Xử lý lỗi
Các ngoại lệ được ném vào các phương thức hub được gửi đến máy khách đã gọi phương thức đó. Trên ứng dụng khách JavaScript, phương thức invoke
trả về JavaScript Promise
. Clients có thể đính kèm trình xử lý catch
vào promise được trả lại hoặc sử dụng try
/ catch
với async
/ await
để xử lý các trường hợp ngoại lệ:
try {
await connection.invoke("SendMessage", user, message);
} catch (err) {
console.error(err);
}
Các kết nối không bị đóng khi một hub đưa ra một ngoại lệ. Theo mặc định, SignalR trả về một thông báo lỗi chung cho máy khách, như minh họa trong ví dụ sau:
Microsoft.AspNetCore.SignalR.HubException: An unexpected error occurred invoking 'SendMessage' on the server.
Các ngoại lệ không mong muốn thường chứa thông tin nhạy cảm, chẳng hạn như tên của máy chủ cơ sở dữ liệu trong một ngoại lệ được kích hoạt khi kết nối cơ sở dữ liệu không thành công. Theo mặc định, SignalR không hiển thị các thông báo lỗi chi tiết này như một biện pháp bảo mật. Để biết thêm thông tin về lý do tại sao các chi tiết ngoại lệ bị chặn, hãy xem Cân nhắc bảo mật trong ASP.NET Core SignalR.
Nếu một điều kiện ngoại lệ phải được truyền tới máy khách, hãy sử dụng lớp HubException. Nếu một HubException được ném vào một phương thức hub, thì SignalR sẽ gửi toàn bộ thông báo ngoại lệ tới máy khách, không sửa đổi:
public Task ThrowException()
=> throw new HubException("This error will be sent to the client!");
Lưu ý
SignalR chỉ gửi property
Message
của ngoại lệ cho client. Theo dõi ngăn xếp và các property khác trên ngoại lệ không có sẵn cho khách hàng.
Tài nguyên bổ sung
- Xem hoặc tải xuống code mẫu (cách tải xuống)
- Tổng quan về ASP.NET Core SignalR
- Máy khách JavaScript ASP.NET Core SignalR
- Xuất bản ứng dụng ASP.NET Core SignalR lên App Service Azure