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

19

我想改进我的应用程序,需要从C#调用信号中心而不是javascript。我应用程序添加任务的当前工作流程为:

  • 调用API将数据添加到数据库中
  • 将新记录返回给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);
这里有一些问题。首先,我希望客户端广播调用存在于一个单独的类中,而不是直接从我的API控制器中调用。其次,hubContextIHubContext的实例,而不是IHubCallerConnectionContext。这意味着我只能访问所有客户端,并且无法像我目前正在做的那样向CallerOthers广播不同的响应。

有没有一种真正地从C#调用hub方法的方法,并最好能够访问不同的caller选项?理想情况下,我希望能够从我的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);
};

然后,在API控制器中执行适用的CRUD操作后,我从请求中检索了连接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);
        }
    }
}

你可以看到,在我的hub方法中,我有一个条件语句来处理如果hub调用不是从我的应用程序的客户端部分发起的。这将是如果外部应用程序/服务调用了我的API。在这种情况下,SignalR连接和当然"ConnectionId"头值将不存在。尽管如此,在我的情况下,我仍然希望为所有连接的客户端调用onInsertTask方法,通知他们数据变化。这绝不应该发生,但我只是为了完整性而包含它。


在 API 调用的上下文中,SignalR 不知道哪个客户端是“Caller”。通常 SignalR 调用是通过其自己的连接进行的,该连接具有该上下文。 - heavyd
在考虑选择广播受众的问题时,我确实考虑过这一点。我不确定是否有一种方法可以将呼叫者“附加”到API调用上,但是(大声思考)我猜您仍然需要在客户端上启动连接/中心。在呼叫者/观众无关紧要的情况下,我认为人们仍应该能够从C#(在我的情况下是API控制器)调用中心的方法... - mellis481
2个回答

7
为了真正调用一个hub方法,您必须连接到它并通过该连接调用。通过调用其他内容(您的API),您无法进行这种类型的调用,因此您必须使用服务器启动的广播功能,这种功能本质上无法知道Caller是什么,因为没有SignalR的caller。
也就是说,如果执行调用时客户端(无论是JavaScript还是C#)已连接到Hub,则始终可以使用该Hub连接的connectionId(通过查询字符串、标头等)来装饰调用API。如果您的API接收到该信息,则可以模拟Caller API。
Clients.Client(connectionId)

同时,它也可以对其他人进行相同的操作。

Clients.AllExcept(connectionId)

使用IHubContext实例可以实现跨越。请查看官方文档

然后,您可以遵循DDan的建议,将IHubContext用法封装在一个方便的集中方式中,甚至可以对其进行一些重构,使其易于DI兼容。


谢谢!我成功地使用了你的建议来实现解决方案,并将其包含在我的问题中以供后人参考。 :) - mellis481
@im1dermike 仅出于清晰起见,您为什么在说它有效并在其上构建了一个工作解决方案之后取消接受此答案?更重要的是,被接受的答案没有解释如何确定来自非SignalR方法的调用者,这正是OP所问的。那是“一种”解决方案,完全有效,但不符合OP设置的上下文,所以我不知道它如何适用。关键不是“保留我的积分”,而是拥有符合OP的答案,而不是你最终所做的事情。 - Wasp
不知道怎么会这样。我没有故意改变它。也许是别人做的?无论如何,我只是纠正了这个问题。再次感谢你的帮助。 - mellis481
啊,好的,没问题,我只是有点困惑 :) - Wasp

3

我正在使用这篇回答中所解释的方法。

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

如果您更喜欢这样,可以基于您的方法命名。

我将groupNameClient.Group(groupName)替换了。 我认为在你的情况下,Clients.Client(connectionId)Clients.AllExcept(connectionId)的组合将完美地发挥作用。 - DDan

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接