【问题标题】:SignalR: How to truly call a hub's method from the server / C#SignalR:如何真正从服务器调用集线器的方法/C#
【发布时间】:2015-07-01 18:59:21
【问题描述】:

我正在尝试改进我的应用程序,这将需要从 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# 调用集线器的方法,并且在理想情况下可以访问不同的调用者选项?理想情况下,我可以通过我的 API 控制器(或者更好的是,使用 DI 的解决方案)执行以下操作:

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

提前致谢。

解决方案

对于后代,我想我会按照黄蜂的建议包括我的完整解决方案。

首先,我修改了我的 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<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);
        }
    }
}

如您所见,如果集线器调用不是从我的应用程序的客户端部分发起的,我的集线器方法中有一个条件语句来处理。如果外部应用程序/服务调用了我的 API,就会出现这种情况。在这种情况下,将不存在 SignalR 连接,当然还有“ConnectionId”标头值。不过,就我而言,我仍然想为所有连接的客户端调用onInsertTask 方法,以通知它们数据更改。这不应该发生,但我只是为了完整性而将其包含在内。

【问题讨论】:

  • 在您的 API 调用上下文中,SignalR 不知道哪个客户端是“调用者”。通常 SignalR 调用会通过它们自己的具有该上下文的连接。
  • 在考虑能够选择广播观众的问题时,我确实考虑到了这一点。我不确定是否有办法将调用者“附加”到 API 调用,但是(大声思考)我猜你无论如何都需要在客户端启动你的连接/集线器。在调用者/观众无关紧要的情况下,我认为仍然应该能够从 C# 调用集线器的方法(在我的例子中是 API 控制器)......

标签: c# signalr


【解决方案1】:

为了真正调用集线器方法,当您调用它时,您必须连接到它,并通过该连接调用。通过调用不同的东西(你的 API)你不能做那种调用,因此你必须求助于服务器启动广播功能,它本质上不知道Caller 是什么,因为有没有 SignalR 的调用者。

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

Clients.Client(connectionId)

它可以为Others 做同样的事情

Clients.AllExcept(connectionId)

通过IHubContext 实例。检查official docs

然后,您可以按照 DDan 的建议以方便的集中方式封装 IHubContext 用法,或者甚至对其进行一些重组以使其轻松兼容 DI。

【讨论】:

  • 谢谢!我能够成功地使用您的建议来实施解决方案,并将其包含在我的问题中以供后代使用。 :)
  • @im1dermike 只是为了清楚起见,为什么在说它有效并且您在此基础上构建了一个有效的解决方案之后不接受这个答案?更重要的是,接受的答案没有解释如何从非 SignalR 方法中确定谁是调用者,这是 OP 所要求的。那是“一个”解决方案,完全有效,但与 OP 设置的上下文不匹配,所以我看不出它是如何应用的。重点不是“保留我的观点”,而是要有一个与 OP 相匹配的答案,而不是你最终做了什么。
  • 不知道这是怎么发生的。我没有刻意改变它。也许其他人做到了?无论哪种情况,我都只是纠正了这个问题。再次感谢您的帮助。
  • 啊好吧,没问题,我只是有点困惑:)
【解决方案2】:

我正在使用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);
    }

}

hub 定义并使用它的 hubContext,所以你可以这样做:

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

或者:

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

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

【讨论】:

  • 我替换为groupNameClient.Gourp(groupName)。我认为在您的情况下,Clients.Client(connectionId)Clients.AllExcept(connectionId) 的组合可以正常工作。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2023-03-05
  • 2018-12-18
  • 1970-01-01
  • 2021-09-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多