Вызов метода SignalR Core Hub из контроллера

Как я могу вызвать метод SignalR Core Hub из Controller?
Я использую 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);
    }
}

Ответ 1

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

Наследовать от концентратора:

 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" };
    }
  }

Ответ 2

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

Ответ прост: вы не можете напрямую вызвать метод-концентратор из контроллера 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(...)

Ответ 3

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