Вызов метода SignalR Core Hub из контроллера
Как я могу вызвать метод SignalR Core Hub из контроллера?
Я использую ASP.NET Core 2.0 с Microsoft.AspNetCore.SignalR (1.0.0-alpha2-final).
У меня есть служба Windows, которая взаимодействует с Excel, SolidEdge ... Когда операция завершена, отправьте запрос на мой контроллер в приложении ASP.NET Core. Теперь мне нужно сообщить всем клиентам, подключенным к серверу с SignalR, что внешняя программа выполнила какое-то задание.
Я не могу изменить способ работы оконного сервиса. (Не удается подключиться к SignalR из оконного сервиса).
Я нашел много решений для старого SignalR (GlobalHost.ConnectionManager.GetHubContext
), но многое изменилось, и эти решения больше не работают.
Мой контроллер:
[Route("API/vardesigncomm")]
public class VarDesignCommController : Controller
{
[HttpPut("ProcessVarDesignCommResponse/{id}")]
public async Task<IActionResult> ProcessVarDesignCommResponse(int id)
{
//call method TaskCompleted in Hub !!!! How?
return new JsonResult(true);
}
}
Мой хаб:
public class VarDesignHub : Hub
{
public async Task TaskCompleted(int id)
{
await Clients.All.InvokeAsync("Completed", id);
}
}
8 ответов
Решение 1
Другая возможность - внедрить ваш HubContext в ваш контроллер, например:
public VarDesignCommController(IHubContext<VarDesignHub> hubcontext)
{
HubContext = hubcontext;
...
}
private IHubContext<VarDesignHub> HubContext
{
get;
set;
}
Тогда вы также можете позвонить
await this.HubContext.Clients.All.InvokeAsync("Completed", id);
Но тогда вы будете направлять методы вызова на всех клиентов.
Решение 2
Вы также можете работать с типизированными концентраторами: просто создайте интерфейс, где вы определяете, какие методы ваш сервер может вызывать на клиентах:
public interface ITypedHubClient
{
Task BroadcastMessage(string name, string message);
}
Наследовать от Hub:
public class ChatHub : Hub<ITypedHubClient>
{
public void Send(string name, string message)
{
Clients.All.BroadcastMessage(name, message);
}
}
Введите введенный вами hubcontext в ваш контроллер и работайте с ним:
[Route("api/demo")]
public class DemoController : Controller
{
IHubContext<ChatHub, ITypedHubClient> _chatHubContext;
public DemoController(IHubContext<ChatHub, ITypedHubClient> chatHubContext)
{
_chatHubContext = chatHubContext;
}
// GET: api/values
[HttpGet]
public IEnumerable<string> Get()
{
_chatHubContext.Clients.All.BroadcastMessage("test", "test");
return new string[] { "value1", "value2" };
}
}
Текущий ответ не отвечает на поставленный вопрос.
Ответ прост: вы не можете напрямую вызвать метод-концентратор из контроллера MVC или из другого места. Это по замыслу. Представьте, что концентратор содержит конечные точки, которые должны вызывать клиенты SignalR Core, а не методы сервера или контроллера.
Вот что говорит Microsoft (это предварительная документация по Core SignalR, но она по-прежнему применима к SignalR Core):
Вы не создаете экземпляр класса Hub и не вызываете его методы из своего собственного кода на сервере; все, что делается для вас с помощью конвейера SignalR Hubs. SignalR создает новый экземпляр вашего класса Hub каждый раз, когда ему необходимо обработать операцию Hub, например, когда клиент подключается, отключается или выполняет вызов метода к серверу.
Поскольку экземпляры класса Hub являются временными, их нельзя использовать для поддержания состояния от одного вызова метода к другому. Каждый раз, когда сервер получает вызов метода от клиента, новый экземпляр вашего класса Hub обрабатывает сообщение. Чтобы поддерживать состояние через несколько соединений и вызовов методов, используйте какой-либо другой метод, например базу данных, или статическую переменную в классе Hub, или другой класс, который не является производным от Hub. Если вы сохраняете данные в памяти, используя метод, такой как статическая переменная в классе Hub, данные будут потеряны при перезагрузке домена приложения.
Если вы хотите отправлять сообщения клиентам из своего собственного кода, который выполняется за пределами класса Hub, вы не можете сделать это, создав экземпляр класса Hub, но вы можете сделать это, получив ссылку на объект контекста SignalR для вашего класса Hub....
Если в хабе есть код, который нужно вызвать, лучше поместить его во внешний класс или службу, доступную из любого места.
Итак, вот пример использования простой встроенной инфраструктуры DI для ASP.NET Core:
Предполагая, что код, который вам нужно вызвать, находится в DoStuff.cs:
public class DoStuff : IDoStuff
{
public string GetData()
{
return "MyData";
}
}
public interface IDoStuff
{
string GetData();
}
В Startup.cs настройте синглтон с помощью встроенного контейнера:
services.AddSingleton<IDoStuff, DoStuff>();
Полный Startup.cs выглядит так:
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddSignalR();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
services.AddSingleton<IDoStuff, DoStuff>();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseSignalR(routes =>
{
routes.MapHub<MyHub>("/myhub");
});
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
Для вашего класса-концентратора введите одиночный код и используйте его в методе:
public class MyHub : Hub
{
private readonly IDoStuff _doStuff;
public MyHub(IDoStuff doStuff)
{
_doStuff = doStuff;
}
public string GetData()
{
return _doStuff.GetData();
}
}
Затем в вашем контроллере введите IHubContext и синглтон:
public class HomeController : Controller
{
private readonly IDoStuff _doStuff;
private readonly IHubContext<MyHub> _hub;
public HomeController(IDoStuff doStuff, IHubContext<MyHub> hub)
{
_doStuff = doStuff;
_hub = hub;
}
public async Task<IActionResult> Index()
{
var data = _doStuff.GetData();
await _hub.Clients.All.SendAsync("show_data", data);
return View();
}
}
Конечно, ваш Javascript или другой клиент должен иметь настроенный обратный вызов show_data.
Обратите внимание, что мы используем внедренный контекст-концентратор для отправки данных всем клиентам SignalR: _hub.Clients.All.SendAsync(...)
Это теперь хорошо задокументировано здесь
Вы можете внедрить экземпляр IHubContext в контроллер, добавив его в свой конструктор:
public class HomeController : Controller { private readonly IHubContext<NotificationHub> _hubContext; public HomeController(IHubContext<NotificationHub> hubContext) { _hubContext = hubContext; } }
Теперь, имея доступ к экземпляру IHubContext, вы можете вызывать методы концентратора, как если бы вы находились в самом концентраторе.
public async Task<IActionResult> Index() { await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: {DateTime.Now}"); return View(); }
Другой ответ не использовать инъекцию здесь.
Я проектирую свой хаб-класс, как показано ниже.
public class NotificationHub : Microsoft.AspNetCore.SignalR.Hub
{
public static IHubContext<NotificationHub> Current { get; set; }
}
В вашем классе Startup
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
NotificationHub.Current = app.ApplicationServices.GetService<IHubContext<NotificationFromServerHub>>();
}
Таким образом, вы можете использовать его откуда угодно.
public class MyBizClass
{
public void DoSomething()
{
NotificationHub.Current.MyMethod(...);
}
}
Возможное решение - использовать клиент-концентратор C#. Вам нужно только создать новый экземпляр HubConnection и использовать его для вызова необходимого метода. Это почти то же самое, что вызов метода из javascript / typescript.
using (var hubConnection = new HubConnection("http://www.contoso.com/"))
{
IHubProxy hubproxy = hubConnection.CreateHubProxy("MyHub");
hubproxy.Invoke("TaskCompleted", id);
)
PS: Я знаю, что это перебор, но это действительно единственный правильный ответ на исходный вопрос.
Я использовал этот подход для своего автономного приложения OWIN, поскольку у меня не настроено внедрение зависимостей.
Это может быть некрасиво, но клиенты будут вызывать конструкторы концентратора при запуске.
public class HostHub : Hub
{
public static HostHub Instance { get; private set; }
public HostHub()
{
Instance = this;
}
public void BroadcastMessage(string message)
{
Clients.All.NewMessage(message);
}
}
Вы можете добавлять методы расширения в свой контекст и вызывать их.
IExampleHubClient.cs
public interface IExampleHubClient
{
void ExampleMethod();
}
ПримерHub.cs
[Authorize]
public class ExampleHub : Hub<IExampleHubClient>
{
public override async Task OnConnectedAsync()
{
var userId = Context.User!.Identity!.Name!;
await Groups.AddToGroupAsync(Context.ConnectionId, userId);
await base.OnConnectedAsync();
}
}
ПримерHubExtensions.cs
public static class ExampleHubExtensions
{
public static void ExampleMethod(this IHubContext<ExampleHub, IExampleHubClient> context, IEnumerable<string> userIds)
{
context.Clients.Groups(userIds).ExampleMethod();
}
}
ПримерController.cs
[ApiController, Route("[controller]/[action]")]
public class ExampleController : Controller
{
private readonly IHubContext<ExampleHub, IExampleHubClient> _context;
public ExampleController(IHubContext<ExampleHub, IExampleHubClient> context)
{
_context = context;
}
[HttpGet]
public IActionResult ExampleAction()
{
_context.ExampleMethod(new string[] { "1234", "2345" });
return Ok();
}
}
Возможное решение.
Контроллер
[Route("API/vardesigncomm")]
public class VarDesignCommController : Controller
{
private IHubContext<ChatHub> _hubContext;
public VarDesignCommController (IHubContext<ChatHub> hubContext){
_hubContext=hubContext
}
[HttpPut("ProcessVarDesignCommResponse/{id}")]
public async Task<IActionResult> ProcessVarDesignCommResponse(int id)
{
//call method TaskCompleted in Hub !!!! How?
await ChatHub.TaskCompleted(_hubContext,id);
return new JsonResult(true);
}
}
Создайте статический метод в HubClass, который получает контекст концентратора.
public class ChatHub : Hub<ITypedHubClient>
{
public static async Task TaskCompleted(IHubContext<ChatHub> hubContext,int id)
{
await hubContext.Clients.All.InvokeAsync("Completed", id);
}
}