Класс хаба - не единственное место, где мы можем отправлять сообщения клиентам. SignalR позволяет это делать также в контроллерах, страницах Razor Pages, компонентах middleware, сервисах благодаря внедрению зависимости IHubContext. Объект IHubContext реализует часть функциональности стандартного класса хаба, в частности, в нем определены такие же свойства Groups и Clients, которые позволяют манипулировать группами и отправлять сообщения всем подключенным клиентам.
Например, определим хаб ChatHub:
using Microsoft.AspNetCore.SignalR; namespace SignalRApp { public class ChatHub : Hub { } }
Несмотря на то, что класс хаба пустой, он нам понадобится для типизиации объекта IHubContext, кроме того, взаимодействие с клиентами будет идти через данный хаб.
Далее в файле Program.cs определим конечную точку, которая будет принимать данные и, используя функционал хаба SignalR, регтранслировать эти данные всем подключенным клиентам:
using Microsoft.AspNetCore.SignalR; // для IHubContext using SignalRApp; // пространство имен класса ChatHub var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR(); var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles(); app.MapPost("create", async (Message message, IHubContext<ChatHub> hubContext) => { await hubContext.Clients.All.SendAsync("Receive", $"{message.Text} - {DateTime.Now.ToLongTimeString()}"); }); app.MapHub<ChatHub>("/chat"); app.Run(); record class Message(string Text);
Итак, здесь по прежнему клиенты подключаются к хабу ChatHub по пути "/chat". Но теперь данные получает конечная точка, которая определена с помощью метода MapPost и которая соответственно обрабатывает POST-запросы.
В качестве первого параметра обработчик конечной точки принимает собственно отправленные пользователем данные в виде объекта Message, который имеет строковое свойство Text.
В качестве второго параметра в обработчик конечной точки передается объект IHubContext, который мы можем получить благодаря встроенному механизму внедрения зависимостей. Основное ограничение в данном случае - этот объект должен быть типизирован классом хаба.
Затем мы можем использовать данный IHubContext, например, отправив сообщение подключенным клиентам. К примеру, в данном случае конечная точка получает отправленные данные в post-запросе по адресу "/create", добавляет к ним текущее время и отправляет их всем подключенным клиентам.
Для тестирования определим в папке wwwroot следующую веб-страницу 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",()=> { const message = document.getElementById("message").value; fetch("create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ "text": message }) }) .catch(error => console.error(error)); }); hubConnection.on("Receive", message =>{ const messageElement = document.createElement("p"); messageElement.textContent = message; // добавляем новый элемент в самое начало // для этого сначала получаем первый элемент const firstElem = document.getElementById("chatroom").firstChild; document.getElementById("chatroom").insertBefore(messageElement, firstElem); }); hubConnection.start() .then(() => document.getElementById("sendBtn").disabled = false) .catch((err) =>console.error(err) ); </script> </body> </html>
На данной веб-странице определено поле для ввода некоторого сообщения, и после нажатия на кнопку это сообщение с помощью функции fetch отправляется на сервер по адресу "/create". Причем стоит отметить, что также идет подключение идет по адресу "/chat" - то есть фактически к хабу ChatHub. При получении уведомлений от хаба будет вызываться функция receive.
И после отправки формы все остальные подключенные клиенты увидят информацию об отправленных данных.
Подобным образом можно получать сервис IHubContext также как и любyю другую зависимость в других сервисах, компонентах middleware, контроллерах MVC и страницах Razor Pages.
Однако стоит отметить, что данный способ отправки сообщений клиентам имее некоторые недостатки. Например, у свойства Clients доступно только свойство All - то есть мы можем отправить сообщение только всем клиентам и нету таких свойств как Other или Caller, которые доступны при использовании в классе хаба. Также мы не можем получить из IHubContext id подключения. Поэтому если нам потребуется в контроллерах или других классах получить id подключения, то его можно получить при подключении на клиенте и затем пересылать тем или иным образом, например, через куки или через параметры запроса.
К примеру, определим следующий код в файле Program.cs:
using Microsoft.AspNetCore.SignalR; // для IHubContext using SignalRApp; // пространство имен класса ChatHub var builder = WebApplication.CreateBuilder(args); builder.Services.AddSignalR(); var app = builder.Build(); app.UseDefaultFiles(); app.UseStaticFiles(); app.MapPost("create", async (Message message, IHubContext<ChatHub> hubContext) => { await hubContext.Clients.AllExcept(message.ConnectionId).SendAsync("Receive", $"{message.ConnectionId}: {message.Text}"); await hubContext.Clients.Client(message.ConnectionId).SendAsync("Receive", "Ваше сообщение добавлено!"); }); app.MapHub<ChatHub>("/chat"); app.Run(); record class Message(string Text, string ConnectionId);
Теперь конечная точка также получает объект Message, но который теперь содержит свойство ConnectionId для хранения id подключение отправителя. И в зависимости от того, кто отправитель, отправляет то или иное сообщение.
Также изменим веб-страницу 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> let connectionId = ""; // id подключения const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build(); document.getElementById("sendBtn").addEventListener("click",()=> { const message = document.getElementById("message").value; fetch("create", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ "text": message, "connectionid": connectionId }) }) .catch(error => console.error(error)); }); hubConnection.on("Receive", message =>{ const messageElement = document.createElement("p"); messageElement.textContent = message; const firstElem = document.getElementById("chatroom").firstChild; document.getElementById("chatroom").insertBefore(messageElement, firstElem); }); hubConnection.start() .then(() => { document.getElementById("sendBtn").disabled = false; // после соединения получаем id подключения console.log(hubConnection.connectionId); connectionId = hubConnection.connectionId; }) .catch((err) =>console.error(err) ); </script> </body> </html>
Теперь страница отправляет ajax-запросы к методу Create контроллера Home. После подключения к хабу через функцию
hubConnection.start()
получаем id подключения. И затем при отправке включаем его в тело запроса.