SignalR .NET Client trong ASP.NET Core
Thư viện .NET client của ASP.NET Core SignalR cho phép bạn giao tiếp với các SignalR hub từ các ứng dụng .NET. Bài viết này mô tả cách sử dụng các API để kết nối với SignalR hub, và gọi các phương thức hub và client .NET. Mẫu code trong bài viết này là một ứng dụng Windows Presentation Foundation (WPF) sử dụng ASP.NET Core SignalR .NET client.
Xem hoặc tải code mẫu (cách tải)
Cài đặt gói SignalR .NET client
Gói Microsoft.AspNetCore.SignalR.Client là bắt buộc để các .NET client kết nối với các SignalR hub. Bạn có thể cài đặt thư viện client từ Visual Studio Package Manager Console hoặc bằng cách sử dụng .NET CLI.
Visual Studio
Chạy lệnh sau trong cửa sổ Package Manager Console:
Install-Package Microsoft.AspNetCore.SignalR.Client
.NET CLI
Chạy lệnh sau trong shell lệnh:
dotnet add package Microsoft.AspNetCore.SignalR.Client
Kết nối với hub
Để thiết lập kết nối, tạo một HubConnectionBuilder và gọi Build. URL hub, giao thức, loại transport (phương thức vận chuyển), mức log, header và các tùy chọn khác có thể được cấu hình khi xây dựng kết nối. Cấu hình bất kỳ tùy chọn nào cần thiết bằng cách chèn bất kỳ phương thức HubConnectionBuilder nào vào Build. Bắt đầu kết nối với StartAsync.
using System;
using System.Threading.Tasks;
using System.Windows;
using Microsoft.AspNetCore.SignalR.Client;
namespace SignalRChatClient
{
public partial class MainWindow : Window
{
HubConnection connection;
public MainWindow()
{
InitializeComponent();
connection = new HubConnectionBuilder()
.WithUrl("http://localhost:53353/ChatHub")
.Build();
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0,5) * 1000);
await connection.StartAsync();
};
}
private async void connectButton_Click(object sender, RoutedEventArgs e)
{
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
this.Dispatcher.Invoke(() =>
{
var newMessage = $"{user}: {message}";
messagesList.Items.Add(newMessage);
});
});
try
{
await connection.StartAsync();
messagesList.Items.Add("Connection started");
connectButton.IsEnabled = false;
sendButton.IsEnabled = true;
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
}
private async void sendButton_Click(object sender, RoutedEventArgs e)
{
try
{
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}
}
}
}Xử lý kết nối bị mất
Tự động kết nối lại (Automatically reconnect)
HubConnection có thể được cấu hình để tự động kết nối lại bằng cách sử dụng phương thức WithAutomaticReconnect trên HubConnectionBuilder. Mặc định, nó không tự động kết nối lại.
HubConnection connection= new HubConnectionBuilder()
.WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
.WithAutomaticReconnect()
.Build();Không có tham số nào, WithAutomaticReconnect() cấu hình client chờ lần lượt 0, 2, 10, và 30 giây trước khi thử mỗi lần kết nối lại. Nó dừng sau bốn lần thất bại.
Trước khi bắt đầu bất kỳ lần thử kết nối lại nào, HubConnection chuyển sang trạng thái HubConnectionState.Reconnecting và kích hoạt sự kiện Reconnecting. Điều này cung cấp cơ hội để cảnh báo người dùng rằng kết nối đã bị mất và vô hiệu hóa các phần tử UI. Các ứng dụng không tương tác có thể bắt đầu xếp hàng hoặc bỏ qua các thông điệp.
connection.Reconnecting += error =>
{
Debug.Assert(connection.State == HubConnectionState.Reconnecting);
// Thông báo cho người dùng rằng kết nối đã bị mất và client đang kết nối lại.
// Bắt đầu xếp hàng hoặc bỏ qua các thông điệp.
return Task.CompletedTask;
};Nếu client kết nối lại thành công trong bốn lần thử đầu tiên, HubConnection chuyển về trạng thái Connected và kích hoạt sự kiện Reconnected. Điều này cung cấp cơ hội để thông báo cho người dùng rằng kết nối đã được thiết lập lại và để dequeue (lấy ra khỏi hàng đợi) các thông điệp đã xếp hàng.
Vì kết nối trông hoàn toàn mới với server, một ConnectionId mới được cung cấp cho các event handler của Reconnected.
Cảnh báo: Tham số connectionId của event handler Reconnected là null nếu HubConnection được cấu hình để bỏ qua đàm phán (skip negotiation).
connection.Reconnected += connectionId =>
{
Debug.Assert(connection.State == HubConnectionState.Connected);
// Thông báo cho người dùng rằng kết nối đã được thiết lập lại.
// Bắt đầu dequeue các thông điệp đã xếp hàng trong khi kết nối lại nếu có.
return Task.CompletedTask;
};WithAutomaticReconnect() không cấu hình HubConnection để thử lại các lần khởi động ban đầu thất bại, do đó các lỗi khởi động cần được xử lý thủ công:
public static async Task<bool> ConnectWithRetryAsync(HubConnection connection, CancellationToken token)
{
// Tiếp tục thử cho đến khi chúng ta có thể khởi động hoặc token bị hủy.
while (true)
{
try
{
await connection.StartAsync(token);
Debug.Assert(connection.State == HubConnectionState.Connected);
return true;
}
catch when (token.IsCancellationRequested)
{
return false;
}
catch
{
// Kết nối thất bại, thử lại sau 5000 ms.
Debug.Assert(connection.State == HubConnectionState.Disconnected);
await Task.Delay(5000);
}
}
}Nếu client không kết nối lại thành công trong bốn lần thử đầu tiên, HubConnection chuyển sang trạng thái Disconnected và kích hoạt sự kiện Closed. Điều này cung cấp cơ hội để thử khởi động lại kết nối thủ công hoặc thông báo cho người dùng rằng kết nối hiện đã bị mất vĩnh viễn.
connection.Closed += error =>
{
Debug.Assert(connection.State == HubConnectionState.Disconnected);
// Thông báo cho người dùng rằng kết nối đã đóng hoặc thử khởi động lại kết nối thủ công.
return Task.CompletedTask;
};Để cấu hình số lần thử kết nối lại tùy chỉnh trước khi ngắt kết nối hoặc thay đổi thời gian kết nối lại, WithAutomaticReconnect chấp nhận một mảng các số đại diện cho độ trễ tính bằng mili giây để chờ trước khi bắt đầu mỗi lần thử kết nối lại.
HubConnection connection = new HubConnectionBuilder()
.WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
.WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.Zero, TimeSpan.FromSeconds(10) })
.Build();
// .WithAutomaticReconnect(new[] { TimeSpan.Zero, TimeSpan.FromSeconds(2), TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(30) }) cho ra hành vi mặc định.Ví dụ trên cấu hình HubConnection để bắt đầu thử kết nối lại ngay lập tức sau khi kết nối bị mất. Điều này cũng đúng với cấu hình mặc định.
- Nếu lần thử kết nối lại đầu tiên thất bại, lần thử kết nối lại thứ hai cũng bắt đầu ngay lập tức thay vì chờ 2 giây như được định nghĩa trong cấu hình mặc định.
- Nếu lần thử kết nối lại thứ hai thất bại, lần thử kết nối lại thứ ba bắt đầu sau 10 giây, đây là hành vi tương tự được định nghĩa trong cấu hình mặc định.
- Hành vi tùy chỉnh sau đó lại khác với hành vi mặc định bằng cách dừng lại sau lần thử kết nối lại thứ ba thất bại. Trong cấu hình mặc định, thêm một lần thử kết nối lại nữa sau 30 giây khác.
Để kiểm soát nhiều hơn về thời gian và số lần thử kết nối lại tự động, WithAutomaticReconnect chấp nhận một đối tượng triển khai interface IRetryPolicy, có một phương thức duy nhất tên là NextRetryDelay. NextRetryDelay nhận một đối số duy nhất với kiểu RetryContext. RetryContext có ba thuộc tính: PreviousRetryCount (kiểu long), ElapsedTime (kiểu TimeSpan), và RetryReason (kiểu Exception).
- Trước lần thử kết nối lại đầu tiên,
PreviousRetryCountvàElapsedTimeđều bằng không (0), vàRetryReasonlà Exception gây ra mất kết nối. - Sau mỗi lần thử thất bại,
PreviousRetryCounttăng thêm một,ElapsedTimecập nhật để phản ánh lượng thời gian đã dành để kết nối lại cho đến nay, vàRetryReasonlà Exception gây ra lần thử kết nối lại cuối cùng thất bại.
NextRetryDelay phải trả về một giá trị TimeSpan đại diện cho thời gian chờ trước lần thử kết nối lại tiếp theo hoặc null nếu HubConnection nên dừng kết nối lại.
public class RandomRetryPolicy : IRetryPolicy
{
private readonly Random _random = new Random();
public TimeSpan? NextRetryDelay(RetryContext retryContext)
{
// Nếu chúng ta đã kết nối lại được ít hơn 60 giây cho đến nay,
// chờ từ 0 đến 10 giây trước lần thử kết nối lại tiếp theo.
if (retryContext.ElapsedTime < TimeSpan.FromSeconds(60))
{
return TimeSpan.FromSeconds(_random.NextDouble() * 10);
}
else
{
// Nếu chúng ta đã kết nối lại được hơn 60 giây, dừng kết nối lại.
return null;
}
}
}HubConnection connection = new HubConnectionBuilder()
.WithUrl(new Uri("http://127.0.0.1:5000/chathub"))
.WithAutomaticReconnect(new RandomRetryPolicy())
.Build();Ngoài ra, bạn có thể viết code để kết nối lại client thủ công, như được trình bày trong phần tiếp theo.
Kết nối lại thủ công (Manually reconnect)
Cảnh báo: Trong các phiên bản trước 3.0, .NET client cho SignalR không tự động kết nối lại. Bạn phải viết code để kết nối lại client thủ công.
Sử dụng sự kiện Closed để phản hồi với kết nối bị mất. Ví dụ: bạn có thể muốn tự động hóa việc kết nối lại.
Sự kiện Closed yêu cầu một delegate trả về Task, cho phép code async chạy mà không cần dùng async void. Để đáp ứng chữ ký delegate trong event handler Closed chạy đồng bộ, hãy trả về Task.CompletedTask:
connection.Closed += (error) => {
// Thực hiện logic đóng kết nối của bạn.
return Task.CompletedTask;
};Lý do chính cho hỗ trợ async là để bạn có thể khởi động lại kết nối. Bắt đầu kết nối là một hành động async.
Trong handler Closed khởi động lại kết nối, hãy xem xét chờ một khoảng trễ ngẫu nhiên để tránh quá tải server, như được hiển thị trong ví dụ sau:
connection.Closed += async (error) =>
{
await Task.Delay(new Random().Next(0,5) * 1000);
await connection.StartAsync();
};Gọi các phương thức hub từ client
InvokeAsync gọi các phương thức trên hub. Truyền tên phương thức hub và bất kỳ đối số nào được định nghĩa trong phương thức hub vào InvokeAsync. SignalR là bất đồng bộ (asynchronous), vì vậy hãy sử dụng async và await khi thực hiện các lời gọi.
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);Phương thức InvokeAsync trả về một Task hoàn thành khi phương thức server trả về. Giá trị trả về, nếu có, được cung cấp như kết quả của Task. Bất kỳ exception nào được ném bởi phương thức trên server tạo ra một Task lỗi. Sử dụng cú pháp await để chờ phương thức server hoàn thành và cú pháp try...catch để xử lý lỗi.
Phương thức SendAsync trả về một Task hoàn thành khi thông điệp được gửi đến server. Không có giá trị trả về vì Task này không chờ cho đến khi phương thức server hoàn thành. Bất kỳ exception nào được ném ở phía client trong khi gửi thông điệp tạo ra một Task lỗi. Sử dụng cú pháp await và try...catch để xử lý lỗi gửi thông điệp.
Lưu ý: Gọi các phương thức hub từ client chỉ được hỗ trợ khi sử dụng Azure SignalR Service ở chế độ Default. Để biết thêm thông tin, xem Câu hỏi thường gặp (Frequently Asked Questions).
Gọi các phương thức client từ hub
Định nghĩa các phương thức mà hub gọi bằng cách sử dụng connection.On sau khi xây dựng, nhưng trước khi bắt đầu kết nối:
connection.On<string, string>("ReceiveMessage", (user, message) =>
{
this.Dispatcher.Invoke(() =>
{
var newMessage = $"{user}: {message}";
messagesList.Items.Add(newMessage);
});
});Code trên trong connection.On chạy khi code phía server gọi nó bằng cách sử dụng phương thức SendAsync:
public async Task SendMessage(string user, string message)
{
await Clients.All.SendAsync("ReceiveMessage", user, message);
}Lưu ý: Mặc dù phía hub của kết nối hỗ trợ messaging có kiểu mạnh (strongly typed), client phải đăng ký bằng cách sử dụng phương thức generic HubConnection.On với tên phương thức. Để xem ví dụ, hãy xem Host ASP.NET Core SignalR trong background service.
Xử lý lỗi và logging
Xử lý lỗi bằng câu lệnh try...catch. Kiểm tra đối tượng Exception để xác định hành động thích hợp cần thực hiện sau khi xảy ra lỗi:
try
{
await connection.InvokeAsync("SendMessage",
userTextBox.Text, messageTextBox.Text);
}
catch (Exception ex)
{
messagesList.Items.Add(ex.Message);
}