Контекст хаба, подключение и отключение клиентов

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

В классе Hub определено свойство Context, которое представляет тип HubCallerContext и который позволяет нам получить некоторую информацию о подключении к хабу. Данная информация хранится в следующих его свойствах:

  • ConnectionId: возвращает уникальный идентификатор подключения в виде строки.

  • ConnectionAborted: возвращает объект CancellationToken, который извещает о закрытии подключения

  • User: возвращает объект ClaimsPrincipal, ассоциированный с текущим пользователем. По сути это аналог свойства HttpContext.User, которое доступно в констроллерах

  • UserIdentifier: возвращает идентификатор пользователя. По умолчанию индентификатор пользователя представляет клейм ClaimTypes.NameIdentifier объекта ClaimsPrincipal, который ассоциирован с данным подключением.

  • Items: возвращает словарь значений, ассоциированных с текущим подключением. Данный словарь позволяет хранить данные для определенного клиента между его запросами

  • Features: возвращает коллекцию возможностей HTTP, ассоциированных с текущим подключением

Также класс HubCallerContext определяет пару методов:

  • Abort(): принудительно завершает текущее подключение

  • GetHttpContext(): возвращает объект HttpContext для текущего подключения или null, если подключение не ассоциировано с запросом HTTP.

Подключение и отключение клиентов

Класс Hub определяет два метода, которые мы можем переопределить в классах-наследниках:

  • OnConnectedAsync(): срабатывает при подключении нового клиента

  • OnDisconnectedAsync(Exception exception): срабатывает при отключении клиента, в качестве параметра передается сообщение об ошибке, которая описывает, почему произошло отключение.

Реализуем эти методы и определим следующий класс хаба:

using Microsoft.AspNetCore.SignalR;

namespace SignalRApp
{
    public class ChatHub : Hub
    {
        public async Task Send(string message)
        {
            await Clients.All.SendAsync("Receive", message, Context.ConnectionId);
        }

        public override async Task OnConnectedAsync()
        {
            await Clients.All.SendAsync("Notify", $"{Context.ConnectionId} вошел в чат");
            await base.OnConnectedAsync();
        }
        public override async Task OnDisconnectedAsync(Exception? exception)
        {
            await Clients.All.SendAsync("Notify", $"{Context.ConnectionId} покинул в чат");
            await base.OnDisconnectedAsync(exception);
        }
    }
}

В данном случае вместо условного ника пользователя будет использоваться идентификатор подключения. В методах OnConnectedAsync и OnDisconnectedAsync мы будем уведомлять клиентов, о том, что какой-то клиент подключился или отключился от приложения.

Для теста определим следующую веб-страницу index.html:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Metanit.com</title>
</head>
<body>
    <div>
        <input type="text" id="message" />
        <input type="button" id="sendBtn" value="Отправить" />
    </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 message = document.getElementById("message").value;

            hubConnection.invoke("Send", message) // отправка данных серверу
                .catch(function (err) {
                    return console.error(err.toString());
                });
        });
        // получение данных с сервера
        hubConnection.on("Receive", function (message, connectionId) {

            // создаем элемент <b> для connectionId
            const userNameElem = document.createElement("b");
            userNameElem.textContent = `${connectionId}: `;

            // создает элемент <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.on("Notify", function (message) {

            // добавляет элемент для диагностического сообщения
            const notifyElem = document.createElement("b");
            notifyElem.textContent = message;
            const elem = document.createElement("p");
            elem.appendChild(notifyElem);
            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>

Здесь в javascript в функции "Send" получаем обычные сообщения от клиентов, а в функции "Notify" диагностические уведомления.

Таким образом, при подключение и отключение мы сможем уведомлять всех клиентов:

Подключение и отключение клиентов в SignalR в ASP.NET Core и C#

Стоит отметить, что методы OnConnectedAsync срабатывает еще до первой отправки сообщения пользователем. Метод OnDisconnectedAsync срабатывает, если клиент закроет вкладку браузера или перезагрузит страницу.

Также стоит отметить, что если мы откроем в одном браузере две вкладки со страницей, то у нас будут два отдельных подключенных клиента, которые будут иметь разные идентификаторы.

Получение информации о контексте HTTP

С помощью метода GetHttpContext() контекста хаба мы можем получить различную информацию о запросе, например, информацию о браузере пользователя, его ip, куки и т.д., вообщем различные заголовки http. Например:

using Microsoft.AspNetCore.SignalR;

namespace SignalRApp
{
    public class ChatHub : Hub
    {
        public async Task Send(string message)
        {
            await Clients.All.SendAsync("Receive", message, Context.ConnectionId);
        }

        public override async Task OnConnectedAsync()
        {
            var context = Context.GetHttpContext();
            if(context is not null)
            {
                // получаем кук name
                if (context.Request.Cookies.ContainsKey("name"))
                {
                    if (context.Request.Cookies.TryGetValue("name", out var userName))
                    {
                        Console.WriteLine($"name = {userName}");
                    }
                }
                // получаем юзер-агент
                Console.WriteLine($"UserAgent = {context.Request.Headers["User-Agent"]}");
                // получаем ip
                Console.WriteLine($"RemoteIpAddress = {context.Connection?.RemoteIpAddress?.ToString()}");

                await base.OnConnectedAsync();
            }
        }
    }
}

Следует отметить, что получить заголовки http таким образом мы можем, а вот установить заголовки или поменять их значение или установить те же куки нельзя.

Помощь сайту
Юмани:
410011174743222
Перевод на карту
Номер карты:
4048415020898850