Данное руководство устарело. Актуальное руководство: Руководство по ASP.NET Core 7
Класс хаба - не единственное место, где мы можем отправлять сообщения клиентам. SignalR позволяет это делать также в контроллерах, страницах Razor Pages, компонентах middleware, сервисах благодаря внедрению зависимости IHubContext. Объект IHubContext реализует часть функциональности стандартного класса хаба, в частности, в нем определены такие же свойства Groups и Clients, которые позволяют манипулировать группами и отправлять сообщения всем подключенным клиентам.
Например, определим хаб ChatHub:
using Microsoft.AspNetCore.SignalR; namespace SignalRApp { public class ChatHub : Hub { } }
Несмотря на то, что класс хаба пустой, он нам понадобится для типизиации объекта IHubContext, кроме того, взаимодействие с клиентами будет идти через данный хаб.
Далее добавим в классе Startup функциональность SignalR и MVC:
using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.DependencyInjection; namespace SignalRApp { public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddSignalR(); services.AddControllersWithViews(); } public void Configure(IApplicationBuilder app) { app.UseStaticFiles(); app.UseRouting(); app.UseEndpoints(endpoints => { endpoints.MapHub<ChatHub>("/chat"); endpoints.MapDefaultControllerRoute(); }); } } }
Определим следующий контроллер HomeController:
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; namespace SignalRApp.Controllers { public class HomeController : Controller { IHubContext<ChatHub> hubContext; public HomeController(IHubContext<ChatHub> hubContext) { this.hubContext = hubContext; } public IActionResult Index() { return View(); } [HttpPost] public async Task<IActionResult> Create(string product) { await hubContext.Clients.All.SendAsync("Notify", $"Добавлено: {product} - {DateTime.Now.ToShortTimeString()}"); return RedirectToAction("Index"); } } }
Благодаря встроенному механизму внедрения зависимостей мы можем получить объект IHubContext в конструкторе контроллера. Основное ограничение в данном случае - этот объект должен быть типизирован классом хаба.
Затем в методах контроллера мы можем использовать данный IHubContext, например, отправив сообщение подключенным клиентам. К примеру, в методе Create происходит условное добавление некоторого товара, и все подключенные клиенты получают уведомление
Для тестирования определим представление Index.cshtml:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Главная</title> </head> <body> <form action='@Url.Action("Create")' method="post"> <input type="text" name="product" /> <input type="submit" value="Отправить" /> </form> <div id="notify"></div> <script src="js/signalr/dist/browser/signalr.min.js"></script> <script> const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build(); // получение сообщения от сервера hubConnection.on('Notify', function (message) { // создает элемент <p> для сообщения пользователя let elem = document.createElement("p"); elem.appendChild(document.createTextNode(message)); document.getElementById("notify").appendChild(elem); }); hubConnection.start(); </script> </body> </html>
В представлении определена форма для отправки данных на метод Create и блок для вывода сообщений, получаемых от хаба. Причем стоит отметить, что подключение идет по адресу "/chat" - то есть фактически к хабу ChatHub. При получении уведомлений от хаба будет вызываться функция notify.
И после отправки формы все остальные подключенные клиенты увидят информацию об отправленных данных.
Подобным образом можно получать сервис IHubContext в других сервисах, компонентах middleware и страницах Razor Pages.
Однако стоит отметить, что данный способ отправки сообщений клиентам имее некоторые недостатки. Например, у свойства Clients доступно только свойство All - то есть мы можем отправить сообщение только всем клиентам и нету таких свойств как Other или Caller, которые доступны при использовании в классе хаба. Также мы не можем получить из IHubContext id подключения. Поэтому если нам потребуется в контроллерах или других классах получить id подключения, то его можно получить при подключении на клиенте и затем пересылать тем или иным образом, например, через куки или через параметры запроса.
К примеру, определим следующий контроллер:
using System; using System.Threading.Tasks; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.SignalR; namespace SignalRApp.Controllers { public class HomeController : Controller { IHubContext<ChatHub> hubContext; public HomeController(IHubContext<ChatHub> hubContext) { this.hubContext = hubContext; } public IActionResult Index() { return View(); } [HttpPost] public async Task Create(string product, string connectionId) { await hubContext.Clients.AllExcept(connectionId).SendAsync("Notify", $"Добавлено: {product} - {DateTime.Now.ToShortTimeString()}"); await hubContext.Clients.Client(connectionId).SendAsync("Notify", $"Ваш товар добавлен!"); } } }
Теперь метод Create также получае id подключение - параметр connectionId и посылает клиентам в зависимости от id подключения разные сообщения.
Также изменим представление:
@{ Layout = null; } <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title>Главная</title> </head> <body> <form method="post"> <input type="text" name="product" id="productField" /> <input type="submit" value="Отправить" id="submitForm" /> </form> <div id="notify"></div> <script src="js/signalr/dist/browser/signalr.min.js"></script> <script> const hubConnection = new signalR.HubConnectionBuilder() .withUrl("/chat") .build(); let connectionId = ""; document.getElementById("submitForm") .addEventListener("click", function (e) { e.preventDefault(); const data = new FormData(); data.append("product", document.getElementById("productField").value); data.append("connectionId", connectionId); fetch("home/create", { method: "POST", body: data }) .catch(error => console.error("Error: ", error)); }); // получение сообщения от сервера hubConnection.on("Notify", function (message) { // создает элемент <p> для сообщения пользователя let elem = document.createElement("p"); elem.appendChild(document.createTextNode(message)); document.getElementById("notify").appendChild(elem); }); hubConnection.start().then(() => { // после соединения получаем id подключения console.log(hubConnection.connectionId); connectionId = hubConnection.connectionId; }); </script> </body> </html>
Теперь страница отправляет ajax-запросы к методу Create контроллера Home. После подключения к хабу через функцию
hubConnection.start()
получаем id подключения. И затем при отправке включаем его в тело запроса.