SignalR Core представляет библиотеку от компании Microsoft, которая работает поверх ASP.NET и которая предназначена для создания приложений, работающих в режиме реального времени. В частности, ее можно использовать вместе с ASP.NET Core. SignalR использует двунаправленную связь для обмена сообщениями между клиентом и сервером, благодаря чему сервер может отправлять в режиме реального времени всем клиентам некоторые данные, а клиенты также в режиме реального времени могут передавать данные серверу и другим клиентам.
Где может использоваться SignalR? Прежде всего это приложения, которые получают данные в реальном режиме времени, например, чаты, социальные сети, игровые приложения, карты, приложения для аукционов, голосований и карт, панели управления, приложения для мониторинга данных и так далее.
Для обмена сообщениями между клиентом и сервером SignalR использует ряд механизмов:
WebSockets
Server-Side Events
Long Polling
Исходя из возможностей клиента и сервера инфраструктура SignalR выбирает наилучший механизм для взаимодействия. В частности, наиболее оптимальным является WebSockets, соответственно если и клиент, и сервер позволяют использовать этот механизм, то взаимодействие идет через WebSockets. Однако если WebSockets не поддерживается, то применяется Server-Side Events. И если SSE не поддерживается, то применяется Long Polling.
SignalR обеспечивает взаимодействие клиента с сервером. Если на стороне сервера ожидаемое это приложение ASP.NET Core, то на стороне клиента все намного интереснее. В частности, в качестве клиента в SignalR может выступать:
Приложение на JavaScript, запущенное на Node.js (поддерживается версия Node.js 8 и выше)
Приложение на JavaScript, которое работает в рамках браузеров Google Chrome (в том числе на Android), Microsoft Edge, Mozilla Firefox, Opera, Apple Safari (MacOS/iOS)
Приложение на .NET. Это может быть десктопное приложение WPF, Windows Forms, мобильное приложение .NET MAUI.
Приложение на языке Java
Есть экспериментальная поддержка для приложений на языках C++ и Swift
Создадим новый простейший проект ASP.NET Core. Если мы работаем в Visual Studio, то надо создать проект по типу ASP.NET Core Empty:
Если мы работаем в текстовом редаторе и используем .NET CLI, то надо создать проект по шаблону web
:
dotnet new web
При работы с SignalR на стороне сервера необходимо создать специальную сущность - хаб (hub). По сути хаб представляет класс, наследующийся от класса Hub, который может обрабатывать запросы. Создадим новый хаб. Для этого добавим в проект следующий класс ChatHub:
using Microsoft.AspNetCore.SignalR; namespace SignalRApp { public class ChatHub : Hub { public async Task Send(string message) { await this.Clients.All.SendAsync("Receive", message); } } }
Класс хаба наследуется от класса Hub. И здесь определен один метод Send(), который получает некоторое отправленное сообщение в
виде параметра message и затем с помощью вызова await Clients.All.SendAsync("Send", message)
ретранслирует это сообщение всем подключенным клиентам.
Первый параметр метода SendAsync()
указывает на метод, который будет получать ответ от сервера, а второй параметр представляет набор значений, которые
посылаются в ответе клиенту. То есть метод Receive на клиенте получит значение параметра message. То есть наш хаб будет просто получать сообщение и транслировать его всем подключенным клиентам.
Но чтобы SignalR и хаб ChatHub заработали, необходимо подключить необходимые службы и настроить маршруты в приложении. Для этого откроем файл Program.cs и изменим его код следующим образом:
using SignalRApp; // пространство имен класса ChatHub var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR(); // подключема сервисы SignalR var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles(); app.MapHub<ChatHub>("/chat"); // ChatHub будет обрабатывать запросы по пути /chat app.Run();
Прежде всего чтобы добавить в приложение сервисы SignalR, вызывается метод:
services.AddSignalR();
Затем устанавливаем маршруты для хаба ChatHub:
app.MapHub<ChatHub>("/chat");
У объекта IEndpointRouteBuilder вызывается метод MapHub, который позволяет связать запросы и класс хаба. В данном случае он устанавливает класс ChatHub в качестве обработчика запросов по пути "/chat". То есть, чтобы обратиться к хабу, строка запроса должна иметь вид типа "https://localhost:5000/chat".
Стоит отметить, что если адрес сервера и адрес клиента не будут совпадать, то, возможно, потребуется настроить поддержку CORS. В данном же случае серверная и клиентская части будут работать в рамках одного приложения, поэтому настройки CORS не потребуется.
Для создания клиентской части можно использовать различные способы. Например, можно использовать javascript, либо же использовать typescript, определить приложение на .NET или Java. В данном случае мы будем использовать JavaScript, который будет выполняться на обычной странице html.
Для хранения статических файлов добавим в проект папку wwwroot. Затем добавим в папку wwwroot новый файл index.html. В итоге проект будет выглядеть следующим образом:
На странице index.html определим следующий код:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Metanit.com</title> </head> <body> <div id="inputForm"> <input type="text" id="message" /> <input type="button" id="sendBtn" value="Отправить" disabled="disabled" /> </div> <div id="chatroom"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script> <script> const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build(); document.getElementById("sendBtn").addEventListener("click", function () { let message = document.getElementById("message").value; hubConnection.invoke("Send", message) .catch(function (err) { return console.error(err.toString()); }); }); hubConnection.on("Receive", function(message) { let messageElement = document.createElement("p"); messageElement.textContent = message; document.getElementById("chatroom").appendChild(messageElement); }); hubConnection.start() .then(function () { document.getElementById("sendBtn").disabled = false; }) .catch(function (err) { return console.error(err.toString()); }); </script> </body> </html>
На странице определено текстовое поле для ввода сообщение и кнопка для его отправки. Под ними расположен блок chatroom, в который будут добавляться полученные сообщения.
Внизу страницы подключается скрипт "signalr.min.js". Далее в коде javascript определена основная логика взаимодействия клиента с хабом.
Вначале определяется переменная, с помощью которой устанавливается подключение:
const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build();
Для взаимодействия с хабом ChatHub с помощью метода build()
объекта HubConnectionBuilder создается объект hubConnection - объект подключения. Метод withUrl
устанавливает адрес, по которому приложение будет обращаться к хабу. Поскольку ChatHub на сервере
сопоставляется с адресом "/chat", то именно этот адрес и передается в withUrl.
Далее устанавливается обработчик для кнопки, который вызывается при ее нажатии:
document.getElementById("sendBtn").addEventListener("click", function () { let message = document.getElementById("message").value; hubConnection.invoke("Send", message) .catch(function (err) { return console.error(err.toString()); }); });
Для отправки данных хабу на сервер вызывается метод hubConnection.invoke("Send", message)
, первый параметр которого представляет
метод хаба, обрабатывающий данный запрос, а второй параметр - данные, отправляемые на сервер.
В случае если при отправке возникнет ошибка, сработает функция, которая передается в метод catch()
Далее метод hubConnection.on
устанавливает метод на стороне клиента, который будет получать данные от сервера:
hubConnection.on("Receive", function(message) { let messageElement = document.createElement("p"); messageElement.textContent = message; document.getElementById("chatroom").appendChild(messageElement); });
В данном случае метод называется Receive и фактически он представляют функцию, которая передается в качестве второго параметра. Эта функция принимает один параметр - message
- те данные, которые в хабе отправляются клиенту. В самой функции с помощью стандартных функций javascript создается
элемент. В этот элемент помещается присланное с сервера сообщение. Затем элемент добавляется в начало элемента chatroom.
И для начала соединения с сервером вызывается функция hubConnection.start()
.
hubConnection.start() .then(function () { document.getElementById("sendBtn").disabled = false; }) .catch(function (err) { return console.error(err.toString()); });
Если подключение к хабу успешно установлено, то мы можем использовать метод then
, чтобы выполнить некоторые действия. Так, в данном случае при успешном подключении
делаем кнопку отправки доступной для нажатия.
Если же в процессе подключения к серверу возникнет ошибка, то сработает функция, которая передается в метод catch и которая получит данные об ошибке и выведет их на консоль браузера.
После запуска приложения в разных браузерах при отправке сообщения каждый браузер будет получать отправленное сообщение и выводить его на веб-страницу:
Теперь модифицируем приложение, что кроме сообщения пользователя также передавалось и его имя. Вначале изменим код класса ChatHub:
using Microsoft.AspNetCore.SignalR; namespace SignalRApp { public class ChatHub : Hub { public async Task Send(string message, string userName) { await Clients.All.SendAsync("Receive", message, userName); } } }
Теперь метод Send принимает два параметра и значения обоих параметров ретранслирует всем подключенным клиентам.
И изменим страницу index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Metanit.com</title> </head> <body> <div> Введите логин:<br /> <input id="userName" type="text" /><br/><br/> Введите сообщение:<br /> <input type="text" id="message" /><br/><br/> <input type="button" id="sendBtn" value="Отправить" disabled="disabled" /> </div> <div id="chatroom"></div> <script src="https://cdnjs.cloudflare.com/ajax/libs/microsoft-signalr/6.0.1/signalr.js"></script> <script> const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build(); document.getElementById("sendBtn").addEventListener("click", function () { const userName = document.getElementById("userName").value; // получаем введенное имя const message = document.getElementById("message").value; hubConnection.invoke("Send", message, userName) // отправка данных серверу .catch(function (err) { return console.error(err.toString()); }); }); // получение данных с сервера hubConnection.on("Receive", function (message, userName) { // создаем элемент <b> для имени пользователя const userNameElem = document.createElement("b"); userNameElem.textContent = `${userName}: `; // создает элемент <p> для сообщения пользователя const elem = document.createElement("p"); elem.appendChild(userNameElem); elem.appendChild(document.createTextNode(message)); // добавляем новый элемент в самое начало // для этого сначала получаем первый элемент const firstElem = document.getElementById("chatroom").firstChild; document.getElementById("chatroom").insertBefore(elem, firstElem); }); hubConnection.start() .then(function () { document.getElementById("sendBtn").disabled = false; }) .catch(function (err) { return console.error(err.toString()); }); </script> </body> </html>
Так как хаб на сервере отправляет клиентам два значения - имя пользователя и его сообщение, то соответственно на стороне клиента в функции, которая регистрируется в методе hubConnection.on мы можем получить оба этих значения.
И теперь мы условно можем войти под разными пользователями в различных браузерах и отправлять друг другу сообщения: