Вызов метода 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);
    }
}

person Makla    schedule 24.10.2017    source источник


Ответы (6)


Решение 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);
    }
}

Вставьте ваш набранный хаб-контекст в ваш контроллер и работайте с ним:

[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" };
    }
}
person Stephu    schedule 24.10.2017
comment
Это отличный ответ. Спасибо, но это не совсем ответ на мой вопрос. Пока он это сделает. Но рано или поздно мне нужно будет вызвать метод Hub. Не отправлять сообщение всем клиентам. - person Makla; 25.10.2017
comment
@Makla: я изменил ответ см. Ссылку - person Stephu; 26.10.2017
comment
@Makla: Я нашел лучшее решение. См. Решение 2. Думаю, это вам поможет. - person Stephu; 06.11.2017
comment
Но есть ли способ вызвать метод .Send из контроллера. В этом случае внедрение зависимостей кажется немного бесполезным. В идеале я бы хотел установить имена сообщений в моем классе Hub, не определяя их в кучу разных мест. Но для этого мне нужно иметь возможность вызывать метод Send в хабе. - person adam3039; 08.12.2017
comment
@ adam3039 Вы нашли пример создания методов в хабе и их вызова? - person Mark Redman; 02.02.2018
comment
@MarkRedman Я не делал, нет. В итоге я обернул контекст Hub в службу, скажем HubService, а затем построил интерфейс для различных методов вызова. По крайней мере, он хранит все в одном месте. - person adam3039; 04.02.2018
comment
@ adam3039 Я тоже это ищу. Вы звоните в HubService через хаб? - person Sébastien Richer; 16.04.2018
comment
Это обходной путь, но он не отвечает на вопрос. У моего хаба есть состояние, которое я хочу учитывать при вызове клиента, и я действительно не хочу выносить эту логику из хаба. - person Baptiste Candellier; 20.09.2018
comment
@BaptisteCandellier: Это комментарий к правильному вопросу? - person Stephu; 20.09.2018
comment
@Stephu, этот ответ не отвечает на исходный вопрос о вызове типизированных методов на самом хабе (а не в клиенте хаба) - person Alex Wiese; 22.03.2021

Текущий ответ не отвечает на поставленный вопрос.

Простой ответ - вы не можете напрямую вызвать метод концентратора из контроллера MVC или из другого места. Это сделано намеренно. Думайте о концентраторе как о содержащем конечные точки для вызова клиентов SignalR Core, а не для методов сервера или контроллера.

Вот что Microsoft говорит (это документация до SignalR Core, но она по-прежнему применима к 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 (...)

person swiftest    schedule 29.10.2018
comment
В результате нужно ли вызывать метод Clients.All из контроллера и концентратора? Это кажется избыточным, и мне также интересно, как вызвать метод на стороне клиента. Должны ли мы вызывать их в Хабе, как обычно? - person ; 26.07.2019
comment
Как насчет использования этого подхода в Microsoft.AspNet.SignalR? Любая идея о SignalR: Как использовать IHubContext ‹ THub, T ›Интерфейс в ASP.NET MVC? - person ; 29.07.2019

Теперь это хорошо документировано здесь

Вы можете внедрить экземпляр 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();
}
person S.V.    schedule 23.06.2019
comment
Как насчет использования этого подхода в Microsoft.AspNet.SignalR? Любая идея о SignalR: Как использовать IHubContext ‹ THub, T ›Интерфейс в ASP.NET MVC? - person ; 29.07.2019
comment
@hexadecimal используйте GlobalHost, как указано здесь stackoverflow.com/questions/47902981/ - person Andrius Naruševičius; 06.08.2019
comment
@Stephu Это хорошая привычка вызывать метод концентратора из контроллера? Сначала я также вызвал свой метод концентратора из своего контроллера, но затем я вызвал их со стороны клиента, чтобы отделить проблемы. Что вы думаете? - person ; 27.08.2019
comment
это хорошо работает. если клиентов много, будет ли ждать _hubContext ... из-за того, что он будет работать медленнее? - person Nick Chan Abdullah; 09.09.2020
comment
@NickChanAbdullah Было бы разумно так предположить. Простое решение этой проблемы - не ждать отправки, а просто продолжить, поскольку это асинхронная операция. - person S.V.; 11.09.2020

Другой ответ, не использующий инъекцию, находится здесь.

Я проектирую свой хаб-класс, как показано ниже.

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(...);
    }
}
person Higty    schedule 14.04.2020

Возможное решение - использовать клиент-концентратор C #. Вам нужно только создать новый экземпляр HubConnection и использовать его для вызова необходимого метода. Это почти то же самое, что вызов метода из javascript / typescript.

using (var hubConnection = new HubConnection("http://www.contoso.com/")) 
{
    IHubProxy hubproxy = hubConnection.CreateHubProxy("MyHub");

    hubproxy.Invoke("TaskCompleted", id);
)

PS: Я знаю, что это перебор, но это действительно единственный правильный ответ на исходный вопрос.

person Lukáš Kmoch    schedule 04.03.2020

Я использовал этот подход для своего автономного приложения 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);
    }
}
person Michele mpp Marostica    schedule 11.02.2021