наряду с клиентом для javascript библиотека SignalR также поддерживает создание клиентских приложений для .NET. Рассмотрим основные моменты клиента для .NET на примере простейшего приложения на WPF.
Сначала определим код сервера, с которым будет взаимодействовать клиент на .NET MAUI. Для этого создадим проект ASP.NET Core по типу Empty.
Определим в проекте следующий простейший класс хаба:
using Microsoft.AspNetCore.SignalR; namespace SignalRApp { public class ChatHub : Hub { public async Task Send(string username, string message) { await this.Clients.All.SendAsync("Receive", username, message); } } }
В методе Send хаб будет принимать имя пользователя и его сообщение и транслировать его на функцию Receive всех подключенных клиентов.
В файле Program.cs определим следующий код:
using SignalRApp; // пространство имен класса ChatHub var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR(); var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles(); app.MapHub<ChatHub>("/chat"); app.Run();
Прежде всего, создадим новый проект по типу WPF Application:
Допустим, проект будет называться SignalrWpfClient.
После создания проекта в первую очередь добавим в него Nuget-пакет Microsoft.AspNetCore.SignalR.Client.
По умолчанию проект WPF содержит определение главного окна в файлах MainWindow.xaml и MainWindow.xaml.cs. В файле MainWindow.xaml определим следующий интерфейс окна:
<Window x:Class="SignalrWpfClient.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:d="http://schemas.microsoft.com/expression/blend/2008" xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" mc:Ignorable="d" Title="METANIT.COM" Height="450" Width="300" Loaded="Window_Loaded"> <StackPanel Margin="4"> <Label Content="Введите логин"/> <TextBox x:Name="userTextBox" /> <Label Content="Введите сообщение"/> <TextBox x:Name="messageTextBox" /> <Button x:Name="sendBtn" Content="Отправить" Click="Button_Click" IsEnabled="False" /> <ListBox x:Name="chatbox" /> </StackPanel> </Window>
Здесь определены два текстовых поля для ввода имени пользователя и сообщения. Также здесь определена кнопка, по нажатию на которую введенные в текстовые поля данные будут отправляться хабу.
Кроме того, здесь определен элемент ListBox, который будет отображать полученные сообщения.
Теперь изменим код в файле MainWindow.xaml.cs:
using Microsoft.AspNetCore.SignalR.Client; using System; using System.Windows; namespace SignalrWpfClient { public partial class MainWindow : Window { HubConnection connection; // подключение для взаимодействия с хабом public MainWindow() { InitializeComponent(); // создаем подключение к хабу connection = new HubConnectionBuilder() .WithUrl("https://localhost:7098/chat") .Build(); // регистрируем функцию Receive для получения данных connection.On<string, string>("Receive", (user, message) => { Dispatcher.Invoke(() => { var newMessage = $"{user}: {message}"; chatbox.Items.Insert(0, newMessage); }); }); } // обработчик загрузки окна private async void Window_Loaded(object sender, RoutedEventArgs e) { try { // подключемся к хабу await connection.StartAsync(); chatbox.Items.Add("Вы вошли в чат"); sendBtn.IsEnabled = true; } catch (Exception ex) { chatbox.Items.Add(ex.Message); } } // обработчик нажатия на кнопку private async void Button_Click(object sender, RoutedEventArgs e) { try { // отправка сообщения await connection.InvokeAsync("Send", userTextBox.Text, messageTextBox.Text); } catch (Exception ex) { chatbox.Items.Add(ex.Message); } } } }
В данном случае у меня сервер запущен по адресу "https://localhost:7098/", поэтому я указываю этот адрес для подключения к хабу:
connection = new HubConnectionBuilder() .WithUrl("https://localhost:7098/chat")
Сначала запустим сервер, а затем скомпилируем приложение WPF и запустим несколько его копий. И мы сможем в разных приложениях, подключенных к хабу, посылать в чат сообщения:
Теперь рассмотрим подробнее основные моменты работы клиента Signalr для .NET.
Чтобы подключиться к хабу, применяется объект HubConnection:
HubConnection connection;
Для создания данного объекта применяется специальный класс-строитель HubConnectionBuilder:
connection = new HubConnectionBuilder() .WithUrl("https://localhost:7098/chat") .Build();
Через его метод WithUrl передается адрес, по которому доступен хаб. А метод Build() собственно создает объект подключения - объект HubConnection, через который мы можем взаимодействовать с хабом.
Далее регистрируем метод, которая будет получать данные от хаба с помощью метода connection.On()
connection.On<string, string>("Receive", (user, message) => { Dispatcher.Invoke(() => { var newMessage = $"{user}: {message}"; chatbox.Items.Insert(0, newMessage); }); });
Первый параметр метода connection.On() представляет имя метода, который будет получать данные от хаба, а второй параметр - собственно определение этого метода.
Например, выше в методе Send в хабе полученные данные транслировались на метод Receive всем подключенным клиентам:
await Clients.All.SendAsync("Receive", username, message);
Первый аргумент вызова SendAsync как раз представляет название метод в коде клиента, которая будет вызываться. Последующие аргументы передают данные, которые получит метод на клиенте.
Поэтому первый параметр метода connection.On
представляет строка "Receive", а второй параметр - метод или точнее лямбда-выражение получает отправленные хабом данные в виде
параметров. А поскольку хаб посылает две строки, то метод connection.On()
типизируется двумя типами string.
В данном случае при получении данных от хаба с помощью метода Dispatcher.Invoke мы можем обратиться к пользовательскому интерфейсу и добавить полученные данные в список chatbox, который представляет элемент ListBox.
После получения объекта HubConnection для подключения к хабу надо вызвать метод StartAsync(). В пример выше это делается в обработчике события загрузки окна:
private async void Window_Loaded(object sender, RoutedEventArgs e) { try { // подключемся к хабу await connection.StartAsync(); chatbox.Items.Add("Вы вошли в чат"); sendBtn.IsEnabled = true; } catch (Exception ex) { chatbox.Items.Add(ex.Message); } }
В случае успешного выполнения метода StartAsync пользователь может начать взаимодействовать с хабом.
Для отправки данных хабу у объекта HubConnection применяется метод InvokeAsync(). Например, выше в классе хаба ChatHub был определен метод Send, который принимает два параметра. В клиенте на WPF отправка этому методу происходит в обработчике нажатия кнопки:
private async void Button_Click(object sender, RoutedEventArgs e) { try { await connection.InvokeAsync("Send", userTextBox.Text, messageTextBox.Text); } catch (Exception ex) { chatbox.Items.Add(ex.Message); } }
Первый параметр метода представляет имя метода хаба, к которому идет обращения. Например, в данном случае это метод Send. Все последующие параметры передают данные для параметров метода хаба. Так, метод Send в хабе ChatHub принимает два строковых параметра. Соответственно в методе InvokeAsync мы можем передать для них данные.
Параметры передаются по позиции: второй аргумент метода InvokeAsync передает значение для первого параметра метода Send, третий аргумент в InvokeAsync - для второго параметра в Send и так далее.
При необходимости после подключения к хабу мы можем отключиться от него с помощью метода StopAsync() объекта HubConnection. Например, можно было бы определить для события Closing - события, которое возникает перед закрытием окна, следующий обработчик:
private async void Window_Closing(object sender, System.ComponentModel.CancelEventArgs e) { await connection.InvokeAsync("Send", "",$"Пользователь {userTextBox.Text} выходит из чата"); await connection.StopAsync(); // отключение от хаба }
HubConnection имеет ряд свойств, которые позволяют получить информацию о подключении или сконфигурировать клиент:
ConnectionId представляет индификатор текущего подключения
ServerTimeout представляет таймаут, в течение которого подключение считается активным. Если в течение этого периода сервер не присылает никакого сообщения, то клиент считает, что подключение к серверу разорвано. И в этом случае вызывается событие Closed().
KeepAliveInterval определяет интервал, в течение которого клиент посылает пинг-сообщения серверу. Отправка любого сообщения от клиента сбрасывает таймер для отслеживания этого интервала до нуля. Если клиент не отправит никакого сообщения в течении интервала, который устанавливается свойством ClientTimeoutInterval класса хаба на сервере, то сервер считает, что клиент отключился
События HubConnection:
Closed возникает после закрытия подключения
Reconnected возникает после переподключения к хабу.
Reconnecting возникает перед переподключением к хабу
Возможна ситуация, что соединение с хабом будет потеряно. Если мы хотим, чтобы подключение было восстановлено, то мы можем сделать это автоматически, используя у HubConnectionBuilder метод WithAutomaticReconnect():
connection = new HubConnectionBuilder() .WithUrl("https://localhost:7098/chat") .WithAutomaticReconnect() // автопереподключение .Build();
При необходимости можно указать временные интервалы для переподключения с помощью массива TimeSpan:
connection = new HubConnectionBuilder() .WithUrl("https://localhost:7098/chat") .WithAutomaticReconnect(new[] { TimeSpan.FromSeconds(10), TimeSpan.FromSeconds(20) }) .Build();
В данном случае клиент пытается переподключить сначала через 10, а потом через 20 секунд. Можно задать произвольное количество подобных интервалов.
Перед переподключением возникает событие Reconnecting, а после переподключения - событие Reconnected. Соответственно, если небходимо отследить переподключение, то можно обработать данные события:
connection.Reconnecting += error => { // обработка события return Task.CompletedTask; }; connection.Reconnected += connectionId => { // обработка события return Task.CompletedTask; };
В обработчик события Reconnecting передается информация об ошибке в виде объекта Exception, а в обработчик события Reconnected передается новый идентификатор подключения.
Также можно вручную переподключиться с помощью обработки события Closed:
connection.Closed += async (error) => { await Task.Delay(1000); // черех секунду переподключаемся await connection.StartAsync(); };
У объекта HubConnectionBuilder
есть метод ConfigureLogging(), в который передается делегат
Action<Microsoft.Extensions.Logging.ILoggingBuilder>
. Объект ILoggingBuilder позволяет настроить ряд параметров логгирования, в частности,
с помощью метода SetMinimumLevel()
устанавливается уровень логгирования:
connection = new HubConnectionBuilder() .WithUrl("https://localhost:7098/chat") .ConfigureLogging(logging => { logging.SetMinimumLevel(LogLevel.Information); }) .Build();
Все возможные уровни логгирования:
LogLevel.None
: логгирование отключено
LogLevel.Critical
: логгирование сообщений об ошибках, которые относятся ко всему приложению в целом
LogLevel.Error
: логгирование сообшений об ошибках, которые относятся к текущей операции
LogLevel.Warning
: логгирование сообщений, которые не представляют ошибки
LogLevel.Information
: логгирование информационных сообщений
LogLevel.Debug
: логгирование диагностических сообщений, используемых при отладке
LogLevel.Trace
: логгирование диагностических сообщений с детальной информацией