从 Controller 调用 SignalR Core Hub 方法

Posted

技术标签:

【中文标题】从 Controller 调用 SignalR Core Hub 方法【英文标题】:Call SignalR Core Hub method from Controller 【发布时间】:2018-04-04 21:44:26 【问题描述】:

如何? 我正在使用带有 Microsoft.AspNetCore.SignalR (1.0.0-alpha2-final) 的 ASP.NET Core 2.0。

我有与 Excel、SolidEdge 通信的 Windows 服务......当操作完成后,它会在 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" ;
    

【讨论】:

这个答案很棒。谢谢,但它并没有真正回答我的问题。它现在会这样做。但迟早我需要调用 Hub 方法。不向所有客户端发送消息。 @Makla:我改了答案见链接 @Makla:我找到了更好的解决方案。请参阅解决方案 2。我认为这会对您有所帮助。 但是有没有办法从控制器调用 .Send 方法。这种方式依赖注入似乎有点没用。理想情况下,我想在我的 Hub 类中设置消息名称,而不必在一堆不同的地方定义它们。但要做到这一点,我需要能够在集线器中调用 Send 方法 @MarkRedman 我没有,不。我最终做的是将 Hub 上下文包装在一个服务中,比如 HubService,然后为不同的调用方法构建一个接口。至少它将所有内容保存在一个地方。【参考方案2】:

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

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

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

您无需实例化 Hub 类或从您自己的服务器代码调用其方法; SignalR Hubs 管道为您完成的所有工作。 SignalR 每次需要处理 Hub 操作(例如客户端连接、断开连接或对服务器进行方法调用时)都会创建 Hub 类的新实例。

由于 Hub 类的实例是瞬态的,因此您不能使用它们来维护从一个方法调用到下一个方法调用的状态。每次服务器接收到来自客户端的方法调用时,您的 Hub 类的新实例都会处理该消息。要通过多个连接和方法调用来维护状态,请使用其他方法,例如数据库,或 Hub 类上的静态变量,或不是从 Hub 派生的不同类。如果你在内存中持久化数据,使用Hub类上的静态变量等方法,应用域回收时数据会丢失。

如果您想通过在 Hub 类之外运行的您自己的代码向客户端发送消息,则无法通过实例化 Hub 类实例来实现,但您可以通过获取 SignalR 上下文对象的引用来实现你的 Hub 类...

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

下面是一个使用 ASP.NET Core 的简单内置 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?");
        );
    

对于您的中心类,注入单例,并在方法中使用它:

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

【讨论】:

结果,我们是否必须从 Controller 和 Hub 调用 Clients.All 方法?这似乎是多余的,我也想知道如何调用客户端方法。我们应该像通常所说的那样在 Hub 中调用它们吗? 在 Microsoft.AspNet.SignalR 中使用这种方法怎么样?关于SignalR : How to use IHubContext<THub,T> Interface in ASP.NET MVC? 的任何想法【参考方案3】:

这现在有据可查here

您可以通过添加将 IHubContext 的实例注入控制器 它给你的构造函数:

public class HomeController : Controller

    private readonly IHubContext<NotificationHub> _hubContext;

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

现在,通过访问 IHubContext 的实例,您可以调用 hub 方法就像你在中心本身一样。

public async Task<IActionResult> Index()

    await _hubContext.Clients.All.SendAsync("Notify", $"Home page loaded at: DateTime.Now");
    return View();

【讨论】:

在 Microsoft.AspNet.SignalR 中使用这种方法怎么样?关于SignalR : How to use IHubContext<THub,T> Interface in ASP.NET MVC? 的任何想法 @hexadecimal 使用 GlobalHost 作为回答这里***.com/questions/47902981/… @Stephu 从Controller调用Hub方法是个好习惯?起初我也从我的 Controller 中调用了我的 hub 方法,但后来我从客户端调用了它们以 分离关注点。你怎么看? 这很好用。如果有很多客户端,“await _hubContext...”会导致它运行速度变慢吗? @NickChanAbdullah 假设是合理的。一个简单的解决方法是不要等待发送,而是继续,因为它是一个异步操作。【参考方案4】:

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

我像下面这样设计我的中心类。

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

【讨论】:

NotificationHub.Current 不允许您访问NotificationHub 的实例,因此您不能在其上调用MyMethod。你可以访问它的context,然后你可以从那里向它的客户发送消息(我想......我正要尝试)。这感觉真的很hacky【参考方案5】:

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

using (var hubConnection = new HubConnection("http://www.contoso.com/")) 

    IHubProxy hubproxy = hubConnection.CreateHubProxy("MyHub");

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

PS:我知道这有点矫枉过正,但它实际上只是对原始问题的正确答案

【讨论】:

【参考方案6】:

我将这种方法用于我的 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);
    

【讨论】:

【参考方案7】:

可能的解决方案。

控制器

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

【讨论】:

以上是关于从 Controller 调用 SignalR Core Hub 方法的主要内容,如果未能解决你的问题,请参考以下文章

C#SignalR异常 - 在收到调用结果之前,连接开始重新连接

从不同的 AppDomain 调用 SignalR 方法

从 SignalR 调用特定客户端

是否可以从 Postman 调用 SignalR Hub

SignalR 与 Service Fabric 无状态 Web API

SignalR - 从服务器端代码调用 Hub 类方法(存在于单独的 MVC 项目中)