SignalR:如何真正呼叫集线器';s方法

本文关键字:方法 集线器 何真正 呼叫 SignalR | 更新日期: 2023-09-27 18:00:38

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

  • 进行API调用以将数据添加到数据库
  • 向AngularJS控制器返回新记录
  • 从控制器调用集线器的方法
  • 集线器适当地向客户端广播呼叫

我想做的是绕过从我的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<TaskHub>();
hubContext.Clients.All.onInsertTask(task);

这有很多问题。首先,我希望客户端广播调用存在于单个类中,而不是直接从API控制器调用。其次,hubContextIHubContext而不是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<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方法,该方法会通知它们数据更改。这本不应该发生,但我只是为了完整性而将其包含在内。

SignalR:如何真正呼叫集线器';s方法

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

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

Clients.Client(connectionId)

并且它可以对具有的CCD_ 10执行同样的操作

Clients.AllExcept(connectionId)

在CCD_ 11实例上。查看官方文件。

然后,您可以遵循DDan的建议,以方便的集中式方式封装IHubContext的使用,甚至可以对其进行一些重组,使其易于符合DI。

我使用的是这个答案中解释的方法。

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

如果您喜欢,请根据您的方法命名。