SignalR:如何从服务器/ C#真正调用集线器的方法

我正在尝试改进我的应用程序,这将需要从C#而不是javascript调用集线器。 在我的应用中添加任务的当前工作流程是:

  • 进行API调用以将数据添加到数据库
  • 将新记录返回给AngularJS控制器
  • 从控制器调用hub的方法
  • 集线器正确地向客户广播

我想做的是绕过从我的AngularJS控制器调用hub的方法,并直接从我的API控制器方法调用它。

这就是我的中心目前的样子:

public class TaskHub : Hub { public void InsertTask(TaskViewModel task) { Clients.Caller.onInsertTask(task, false); Clients.Others.onInsertTask(task, true); } } 

关于这个主题有很多SO线程,但是我读过的所有内容都会让我在API控制器中添加以下代码:

 var hubContext = GlobalHost.ConnectionManager.GetHubContext(); hubContext.Clients.All.onInsertTask(task); 

这有很多问题。 首先,我希望客户端广播调用存在于单个类中,而不是直接从我的API控制器调用。 其次, hubContexthubContext的一个实例,而不是IHubCallerConnectionContext 。 这意味着我只能访问所有客户端,并且无法像我正在做的那样向CallerOthers广播不同的响应。

有没有办法从C# 真正调用集线器的方法,理想情况下,可以访问不同的调用方选项? 理想情况下,我可以从我的API控制器(或者更好的DI解决方案)中执行以下操作:

 var taskHub = new TaskHub(); taskHub.InsertTask(task); 

提前致谢。

对于后代,根据Wasp的建议,我认为我会包含完整的解决方案。

首先,我修改了我的javascript(AngularJS)服务,将SignalR连接ID包含在API调用的自定义请求标头中,在本例中为INSERT:

 var addTask = function (task) { var config = { headers: { 'ConnectionId': connection.id } }; return $http.post('/api/tasks', task, config); }; 

然后,在执行适用的CRUD操作后,我在API控制器中从请求中检索了连接ID,然后调用了我的集线器:

 public HttpResponseMessage Post(HttpRequestMessage request, [FromBody]TaskViewModel task) { var viewModel = taskAdapter.AddTask(task); var connectionId = request.Headers.GetValues("ConnectionId").FirstOrDefault(); TaskHub.InsertTask(viewModel, connectionId); return request.CreateResponse(HttpStatusCode.OK, viewModel); } 

我的中心看起来像这样我现在只使用从我的API控制器调用的静态方法:

 public class TaskHub : Hub { private static IHubContext context = GlobalHost.ConnectionManager.GetHubContext(); public static void InsertTask(TaskViewModel task, string connectionId) { if (!String.IsNullOrEmpty(connectionId)) { context.Clients.Client(connectionId).onInsertTask(task, false); context.Clients.AllExcept(connectionId).onInsertTask(task, true); } else { context.Clients.All.onInsertTask(task, true); } } } 

正如您所看到的,我在hub方法中有一个条件语句来处理是否未从我的应用程序的客户端部分启动集线器调用。 如果外部应用程序/服务调用我的API,那就是这样。 在这种情况下,SignalR连接和当然“ConnectionId”标头值将不存在。 但就我而言,我仍然希望为所有连接的客户端调用onInsertTask方法,以便通知它们数据的变化。 这应该永远不会发生,但我只是为了完整性而加入它。

为了真正调用集线器方法,在调用它时,必须连接到它,并通过该连接进行调用。 通过调用不同的东西(您的API),您无法进行这种呼叫,因此您必须求助于服务器发起的广播function,这本身无法知道Caller是什么,因为没有SignalR的呼叫者。

也就是说,如果您的客户端调用API(无论是Javascript还是C#)在执行调用时已经连接到集线器,您始终可以使用集线器连接的connectionId装饰您对API的调用(通过查询字符串,通过标题,…)。 如果您的API收到该信息,则可以使用该模拟Caller API

 Clients.Client(connectionId) 

它可以为Others做同样的事情

 Clients.AllExcept(connectionId) 

IHubContext实例上。 查看官方文档 。

然后,您可以按照DDan的建议,以方便的集中方式封装IHubContext用法,或者甚至重新构建它以使其易于符合DI标准。

我正在使用本答案中解释的方法。

 public class NewsFeedHub : Hub { private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext(); // Call this from JS: hub.client.send(channel, content) public void Send(string groupName, string content) { Clients.Group(groupName).addMessage(content); } // Call this from C#: NewsFeedHub.Static_Send(groupName, content) public static void Static_Send(string groupName, string content) { hubContext.Clients.Group(groupName).addMessage(content); } } 

集线器定义并使用其hubContext,因此您可以:

 var newsFeedHub = new NewsFeedHub(); var newsFeedHub.Static_Send("ch1", "HELLO"); 

要么:

 var taskHub = new TaskHub(); var taskHub.InsertTask(task); 

如果您愿意,可以根据您的方法命名。