从Controller

时间:2017-10-24 07:31:49

标签: c# signalr asp.net-core-2.0 asp.net-core-signalr

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

我有与Excel,SolidEdge通信的Windows服务...当操作完成后,它会在ASP.NET核心应用程序中向我的控制器发送请求。现在我需要通过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);
    }
}

6 个答案:

答案 0 :(得分:44)

解决方案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" };
    }
  }

答案 1 :(得分:14)

当前答案无法回答提出的问题。

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

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

  

您无需实例化Hub类或从服务器上自己的代码中调用其方法; SignalR Hubs管道为您完成了所有这些工作。每当需要处理Hub操作(例如客户端连接,断开连接或对服务器进行方法调用)时,SignalR都会为您的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();
    }
}

当然,您的Java脚本或其他客户端应该配置了show_data回调。

请注意,我们正在使用注入的中心上下文将数据发送到所有SignalR客户端:_hub.Clients.All.SendAsync(...)

答案 2 :(得分:2)

现在已充分记录here

  

您可以通过添加IHubContext实例到控制器中   交给您的构造函数:

SOLVEPNP_EPNP
     

现在,可以访问IHubContext的实例,您可以调用集线器   就像您在集线器中一样。

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

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

答案 3 :(得分:2)

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

我如下设计我的中心类。

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

在您的启动课程中

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

}

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

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

答案 4 :(得分:1)

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

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

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

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

答案 5 :(得分:1)

我在我的 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);
    }
}