Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
SignalR Core представляет библиотеку от компании Microsoft, которая предназначена для создания приложений, работающих в режиме реального времени. В частности, ее можно использовать вместе с 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, Safari (в том числе на iOS), Internet Explorer (только 11-я версия)
Приложение на .NET. Это может быть десктопное приложение WPF, Windows Forms, мобильное приложение Xamarin.
Приложение на языке Java
Также в прекрасном SignalR будущего ожидается поддержка приложений на C++ и Swift.
Создадим новый проект ASP.NET Core по типу Empty:
При работы с SignalR на стороне сервера необходимо создать специальную сущность - хаб (hub). По сути хаб представляет класс, наследующийся от класса Hub, который может обрабатывать запросы. Создадим новый хаб. Для этого добавим в проект следующий класс ChatHub:
using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRApp { public class ChatHub : Hub { public async Task Send(string message) { await this.Clients.All.SendAsync("Send", message); } } }
Класс хаба наследуется от класса Hub. И здесь определен один метод Send(), который получает некоторое отправленное сообщение в
виде параметра message и затем с помощью вызова await Clients.All.SendAsync("Send", message)
ретранслирует это сообщение всем подключенным клиентам.
Первый параметр метода SendAsync()
указывает на метод, который будет получать ответ от сервера, а второй параметр представляет набор значений, которые
посылаются в ответе клиенту. То есть метод Send на клиенте получит значение параметра message. То есть наш хаб будет просто получать сообщение и транслировать его всем подключенным клиентам.
Но чтобы SignalR и хаб ChatHub заработали, надо сконфигурировать класс Startup:
using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; namespace SignalRApp { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); } public void Configure(IApplicationBuilder app) { app.UseDeveloperExceptionPage(); app.UseDefaultFiles(); app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/chat"); }); } } }
Чтобы задействовать сервисы SignalR, в методе ConfigureServices вызывает метод:
services.AddSignalR();
А следующий вызов в методе Configure:
app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/chat"); });
У объекта IEndpointRouteBuilder вызывается метод MapHub, который позволяет связать запросы и класс хаба. В данном случае он устанавливает класс ChatHub в качестве обработчика запросов по пути "/chat". То есть, чтобы обратиться к хабу, строка запроса должна иметь вид типа "http://localhost:5000/chat".
Стоит отметить, что если адрес сервера и адрес клиента не будут совпадать, то, возможно, потребуется настроить поддержку CORS, о которой речь идет в следующей главе.
Для создания клиентской части можно использовать различные способы. Например, можно использовать javascript, либо же использовать typescript, определить приложение на .NET или Java. В данном случае мы будем использовать JavaScript, который будет выполняться на обычной странице html.
Для хранения статических файлов добавим в проект папку wwwroot.
Прежде всего на стороне клиента javascript нам потребуется подключить специальный js-скрипт. Для хранения всех необходимых javascript вначале создадим в папке wwwroot новый подкаталог js. Далее нажмем на эту папку правой кнопкой мыши и в контекстном меню выберем Add -> Client Side Library
Далее нам откроется окно добавления клиентских библиотек. Укажем в нем следующие опции:
В поле Provider укажем значение unpkg.
В поле Library в качестве названия пакета введем @microsoft/signalr@latest.
Поскольку пакет содержит много файлов, которые нам могут не понадобиться, то отметим пункт Choose specific files: и затем из списка файлов отметим только signalr.min.js, то есть минимизированную версию библиотеки. Но при желании и необходимости можно выбрать и другие файлы.
И в конце в поле Target location укажем расположение, по которому будет сохранена библиотека, то есть путь wwwroot/js/signalr.
В итоге в проекте по пути wwwroot/js/signalr/dist/browser/ мы сможем найти файл signalr.min.js
В качестве альтернативы, особенно, если мы работаем не в Visual Studio, можно было бы загрузить пакет "@microsoft/signalr" через пакетный менеджер NPM, например, определим следующий файл package.json:
{ "version": "1.0.0", "name": "asp.net", "private": true, "devDependencies": { "@microsoft/signalr": "3.1.0" } }
Теперь определим клиентскую часть. Добавим в папку wwwroot новый файл index.html. В итоге проект будет выглядеть следующим образом:
На странице index.html определим следующий код:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>SignalR Chat - Metanit.com</title> </head> <body> <div id="inputForm"> <input type="text" id="message" /> <input type="button" id="sendBtn" value="Отправить" /> </div> <div id="chatroom"></div> <script src="js/signalr/dist/browser/signalr.min.js"></script> <script> const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build(); hubConnection.on("Send", function (data) { let elem = document.createElement("p"); elem.appendChild(document.createTextNode(data)); let firstElem = document.getElementById("chatroom").firstChild; document.getElementById("chatroom").insertBefore(elem, firstElem); }); document.getElementById("sendBtn").addEventListener("click", function (e) { let message = document.getElementById("message").value; hubConnection.invoke("Send", message); }); hubConnection.start(); </script> </body> </html>
На странице определено текстовое поле для ввода сообщение и кнопка для его отправки. Под ними расположен блок chatroom, в который будут добавляться полученные сообщения.
Внизу страницы подключается скрипт "signalr.min.js". Далее в коде javascript определена основная логика взаимодействия клиента с хабом.
Вначале определяется переменная, с помощью которой устанавливается подключение:
const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build();
Для взаимодействия с хабом ChatHub с помощью метода build()
объекта HubConnectionBuilder создается объект hubConnection - объект подключения. Метод withUrl
устанавливает адрес, по котору приложение будет обращаться к хабу. Поскольку ChatHub на сервере
сопоставляется с адресом "/chat", то именно этот адрес и передается в withUrl.
Далее метод hubConnection.on
устанавливает метод на стороне клиента, который будет получать данные от сервера:
hubConnection.on("Send", function (data) { let elem = document.createElement("p"); elem.appendChild(document.createTextNode(data)); let firstElem = document.getElementById("chatroom").firstChild; document.getElementById("chatroom").insertBefore(elem, firstElem); });
В данном случае метод называется Send и фактически он представляют функцию, которая передается в качестве второго параметра. Эта функция принимает один параметр data - те данные, которые в хабе отправляются клиенту. В самой функции с помощью стандартных функций javascript создается элемент. В этот элемент помещается присланное с сервера сообщение. Затем элемент добавляется в начало элемента chatroom.
Далее устанавливается обработчик для кнопки, который вызывается при ее нажатии:
document.getElementById("sendBtn").addEventListener("click", function (e) { let message = document.getElementById("message").value; hubConnection.invoke("Send", message); });
Для отправки данных хабу на сервер вызывается метод hubConnection.invoke("Send", message)
, первый параметр которого представляет
метод хаба, обрабатывающий данный запрос, а второй параметр - данные, отправляемые на сервер.
И для начала соединения с сервером вызывается функция hubConnection.start()
.
После запуска приложения в разных браузерах при отправке сообщения каждый браузер будет получать отправленное сообщение и выводить его на веб-страницу:
Теперь модифицируем приложение, что кроме сообщения пользователя также передавалось и его имя. Вначале изменим код класса ChatHub:
using Microsoft.AspNetCore.SignalR; using System.Threading.Tasks; namespace SignalRApp { public class ChatHub : Hub { public async Task Send(string message, string userName) { await Clients.All.SendAsync("Send", message, userName); } } }
Теперь метод Send принимает два параметра и значения обоих параметров ретранслирует всем подключенным клиентам.
И изменим страницу index.html:
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>SignalR Chat - Metanit.com</title> </head> <body> <div id="loginBlock"> Введите логин:<br /> <input id="userName" type="text" /> <input id="loginBtn" type="button" value="Войти" /> </div><br /> <div id="header"></div><br /> <div id="inputForm"> <input type="text" id="message" /> <input type="button" id="sendBtn" value="Отправить" /> </div> <div id="chatroom"></div> <script src="js/signalr/dist/browser/signalr.min.js"></script> <script> const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build(); let userName = ''; // получение сообщения от сервера hubConnection.on('Send', function (message, userName) { // создаем элемент <b> для имени пользователя let userNameElem = document.createElement("b"); userNameElem.appendChild(document.createTextNode(userName + ': ')); // создает элемент <p> для сообщения пользователя let elem = document.createElement("p"); elem.appendChild(userNameElem); elem.appendChild(document.createTextNode(message)); var firstElem = document.getElementById("chatroom").firstChild; document.getElementById("chatroom").insertBefore(elem, firstElem); }); // установка имени пользователя document.getElementById("loginBtn").addEventListener("click", function (e) { userName = document.getElementById("userName").value; document.getElementById("header").innerHTML = '<h3>Welcome ' + userName + '</h3>'; }); // отправка сообщения на сервер document.getElementById("sendBtn").addEventListener("click", function (e) { let message = document.getElementById("message").value; hubConnection.invoke("Send", message, userName); }); hubConnection.start(); </script> </body> </html>
Так как хаб на сервере отправляет клиентам два значения - имя пользователя и его сообщение, то соответственно на стороне клиента в функции, которая регистрируется в методе hubConnection.on мы можем получить оба этих значения.
И теперь мы условно можем войти под разными пользователями в различных браузерах и отправлять друг другу сообщения: