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