Основы SignalR

SignalR Core. Первое приложение

Последнее обновление: 14.11.2022

Что такое SignalR

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:

Первый проект на SignalR в ASP.NET Core и C#

Если мы работаем в текстовом редаторе и используем .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. В итоге проект будет выглядеть следующим образом:

Первый проект signalr в Asp.Net Core и C#

На странице 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 и которая получит данные об ошибке и выведет их на консоль браузера.

Взаимодействие между клиентом и сервером в приложении SignalR на C#

После запуска приложения в разных браузерах при отправке сообщения каждый браузер будет получать отправленное сообщение и выводить его на веб-страницу:

Первое приложение на SignalR на ASP.NET Core и C#

Расширение приложения

Теперь модифицируем приложение, что кроме сообщения пользователя также передавалось и его имя. Вначале изменим код класса 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 мы можем получить оба этих значения.

И теперь мы условно можем войти под разными пользователями в различных браузерах и отправлять друг другу сообщения:

Создание чата с помощью SignalR Core в ASP.NET и C#
Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850