从 Controller 调用 SignalR Core Hub 方法

Call SignalR Core Hub method from Controller

如何从 Controller 调用 SignalR Core Hub 方法?
我正在使用 ASP.NET Core 2.0 和 Microsoft.AspNetCore.SignalR (1.0.0-alpha2-final).

我有 windows 与 Excel、SolidEdge 通信的服务...操作完成后,它 post 向我在 ASP.NET 核心应用程序中的控制器发出请求。现在我需要通知所有使用 SignalR 连接到服务器的客户端外部程序完成了一些任务。
我无法更改 window 服务的工作方式。 (无法从 window 服务连接到 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

另一种可能性是将 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" };
    }
}

当前答案没有回答提出的问题。

简单的答案是您不能直接从 MVC 控制器或其他地方调用集线器方法。这是设计使然。将集线器视为包含供 SignalR Core 客户端调用的端点,而不是服务器或控制器方法的端点。

这是 Microsoft says(这是 SignalR Core 之前的文档,但它仍然适用于 SignalR Core):

You don't instantiate the Hub class or call its methods from your own code on the server; all that is done for you by the SignalR Hubs pipeline. SignalR creates a new instance of your Hub class each time it needs to handle a Hub operation such as when a client connects, disconnects, or makes a method call to the server.

Because instances of the Hub class are transient, you can't use them to maintain state from one method call to the next. Each time the server receives a method call from a client, a new instance of your Hub class processes the message. To maintain state through multiple connections and method calls, use some other method such as a database, or a static variable on the Hub class, or a different class that does not derive from Hub. If you persist data in memory, using a method such as a static variable on the Hub class, the data will be lost when the app domain recycles.

If you want to send messages to clients from your own code that runs outside the Hub class, you can't do it by instantiating a Hub class instance, but you can do it by getting a reference to the SignalR context object for your Hub class...

如果集线器中有您需要调用的代码,最好将其放入外部 class 或可从任何地方访问的服务中。

下面是一个使用 ASP.NET 核心的简单内置 DI 框架的示例:

假设您需要调用的代码在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?}");
        });
    }
}

对于你的 hub class,注入单例,并在方法中使用它:

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(...)

现在是well-documentedhere

You can inject an instance of IHubContext into a controller by adding it to your constructor:

public class HomeController : Controller
{
    private readonly IHubContext<NotificationHub> _hubContext;

    public HomeController(IHubContext<NotificationHub> hubContext)
    {
        _hubContext = hubContext;
    }
}

Now, with access to an instance of IHubContext, you can call hub methods as if you were in the hub itself.

public async Task<IActionResult> Index()
{
    await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: {DateTime.Now}");
    return View();
}

可能的解决方案是使用 C# hub 客户端。 您只需创建一个新的 HubConnection 实例并使用它来调用所需的方法。它与从 javascript/typescript.

调用方法几乎相同
using (var hubConnection = new HubConnection("http://www.contoso.com/")) 
{
    IHubProxy hubproxy = hubConnection.CreateHubProxy("MyHub");

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

PS:我知道这是大材小用,但它真的只是对原题的正确答案

另一个不使用注入的答案在这里。

我设计我的集线器 class 如下所示。

public class NotificationHub : Microsoft.AspNetCore.SignalR.Hub
{
    public static IHubContext<NotificationHub> Current { get; set; }
}

在你的启动中class

public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
    NotificationHub.Current = app.ApplicationServices.GetService<IHubContext<NotificationFromServerHub>>();

}

因此,您可以在任何地方像这样使用。

public class MyBizClass
{
    public void DoSomething()
    {
        NotificationHub.Current.MyMethod(...);
    }
}

我将这种方法用于我的 OWIN 自托管应用程序,因为我没有设置依赖项注入。

它可能很难看,但客户端在启动时会调用 Hub 构造函数。

public class HostHub : Hub
{
    public static HostHub Instance { get; private set; }

    public HostHub()
    {
        Instance = this;
    }

    public void BroadcastMessage(string message)
    {
        Clients.All.NewMessage(message);
    }
}

可能的解决方案。

控制器

[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);
    }
}