SignalR:如何真正从服务器/C# 调用集线器的方法
SignalR: How to truly call a hub's method from the server / C#
我正在尝试改进我的应用程序,这将需要从 C# 而不是 javascript 调用集线器。在我的应用程序中添加任务的当前工作流程是:
- 进行 API 调用以将数据添加到数据库
- return 新记录到 AngularJS 控制器
- 从控制器调用集线器的方法
- 集线器适当地向客户端广播呼叫
我想做的是绕过从我的 AngularJS 控制器调用集线器的方法并直接从我的 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<TaskHub>();
hubContext.Clients.All.onInsertTask(task);
这里有很多问题。首先,我希望客户端广播调用存在于单个 class 中,而不是直接从我的 API 控制器调用。其次,hubContext
是 IHubContext
而不是 IHubCallerConnectionContext
的实例。这意味着我只能访问所有客户端,无法像我目前所做的那样向 Caller
和 Others
广播不同的响应。
有没有办法真正 从 C# 调用集线器的方法,并且理想情况下,可以访问不同的调用方选项?理想情况下,我可以从我的 API 控制器(或者更好的是,使用 DI 的解决方案)做如下简单的事情:
var taskHub = new TaskHub();
taskHub.InsertTask(task);
提前致谢。
解决方案
为了后代,我想我会按照 Wasp 的建议包含我的完整解决方案。
首先,我修改了我的 javascript (AngularJS) 服务,以在 API 调用的自定义请求 header 中包含 SignalR 连接 ID,在这种情况下插入:
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<TaskHub>();
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);
}
}
}
如您所见,如果集线器调用不是从我的应用程序的客户端部分启动的,我在集线器方法中有一个条件语句来处理。如果外部 app/service 调用了我的 API,就会出现这种情况。在这种情况下,SignalR 连接和当然 "ConnectionId" header 值将不存在。不过,就我而言,我仍然想为所有连接的客户端调用 onInsertTask
方法,通知它们数据更改。这永远不应该发生,但为了完整起见,我只是将其包括在内。
我正在使用 this 答案中解释的方法。
public class NewsFeedHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<NewsFeedHub>();
// 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);
如果您愿意,基于您的方法命名。
为了真正调用集线器方法,正如您调用它一样,您必须连接到它,并通过该连接调用。通过调用不同的东西(你的API)你不能做那种调用,因此你必须求助于服务器启动的广播功能,它本质上不知道什么Caller
是因为没有 SignalR 的调用者。
也就是说,如果调用 API 的客户端(无论是 Javascript 还是 C#)在执行调用时已经连接到集线器,您始终可以 使用集线器连接的 connectionId
装饰 对 API 的调用(通过查询字符串,通过 headers,...)。如果您的 API 收到该信息,它可以 模拟 Caller
API 和
Clients.Client(connectionId)
它可以为 Others
和
做同样的事情
Clients.AllExcept(connectionId)
超过 IHubContext
个实例。检查 official docs.
然后您可以按照 DDan 的建议以方便的集中方式封装 IHubContext
用法,甚至对其进行一些重组以使其更容易 DI-compliant.
我正在尝试改进我的应用程序,这将需要从 C# 而不是 javascript 调用集线器。在我的应用程序中添加任务的当前工作流程是:
- 进行 API 调用以将数据添加到数据库
- return 新记录到 AngularJS 控制器
- 从控制器调用集线器的方法
- 集线器适当地向客户端广播呼叫
我想做的是绕过从我的 AngularJS 控制器调用集线器的方法并直接从我的 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<TaskHub>();
hubContext.Clients.All.onInsertTask(task);
这里有很多问题。首先,我希望客户端广播调用存在于单个 class 中,而不是直接从我的 API 控制器调用。其次,hubContext
是 IHubContext
而不是 IHubCallerConnectionContext
的实例。这意味着我只能访问所有客户端,无法像我目前所做的那样向 Caller
和 Others
广播不同的响应。
有没有办法真正 从 C# 调用集线器的方法,并且理想情况下,可以访问不同的调用方选项?理想情况下,我可以从我的 API 控制器(或者更好的是,使用 DI 的解决方案)做如下简单的事情:
var taskHub = new TaskHub();
taskHub.InsertTask(task);
提前致谢。
解决方案
为了后代,我想我会按照 Wasp 的建议包含我的完整解决方案。
首先,我修改了我的 javascript (AngularJS) 服务,以在 API 调用的自定义请求 header 中包含 SignalR 连接 ID,在这种情况下插入:
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<TaskHub>();
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);
}
}
}
如您所见,如果集线器调用不是从我的应用程序的客户端部分启动的,我在集线器方法中有一个条件语句来处理。如果外部 app/service 调用了我的 API,就会出现这种情况。在这种情况下,SignalR 连接和当然 "ConnectionId" header 值将不存在。不过,就我而言,我仍然想为所有连接的客户端调用 onInsertTask
方法,通知它们数据更改。这永远不应该发生,但为了完整起见,我只是将其包括在内。
我正在使用 this 答案中解释的方法。
public class NewsFeedHub : Hub
{
private static IHubContext hubContext = GlobalHost.ConnectionManager.GetHubContext<NewsFeedHub>();
// 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);
如果您愿意,基于您的方法命名。
为了真正调用集线器方法,正如您调用它一样,您必须连接到它,并通过该连接调用。通过调用不同的东西(你的API)你不能做那种调用,因此你必须求助于服务器启动的广播功能,它本质上不知道什么Caller
是因为没有 SignalR 的调用者。
也就是说,如果调用 API 的客户端(无论是 Javascript 还是 C#)在执行调用时已经连接到集线器,您始终可以 使用集线器连接的 connectionId
装饰 对 API 的调用(通过查询字符串,通过 headers,...)。如果您的 API 收到该信息,它可以 模拟 Caller
API 和
Clients.Client(connectionId)
它可以为 Others
和
Clients.AllExcept(connectionId)
超过 IHubContext
个实例。检查 official docs.
然后您可以按照 DDan 的建议以方便的集中方式封装 IHubContext
用法,甚至对其进行一些重组以使其更容易 DI-compliant.