SignalR: Как по-настоящему вызывать метод хаба с сервера/С#

Я пытаюсь улучшить свое приложение, которое потребует вызова хаба из С# вместо 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. Во-вторых, hubContext является экземпляром IHubContext, а не IHubCallerConnectionContext. Это означает, что я имею доступ только ко всем клиентам и не могу транслировать разные ответы на Caller и Others, как я сейчас делаю.

Есть ли способ по-настоящему вызвать метод хаба из С# и, в идеале, иметь доступ к различным параметрам вызова? В идеале я мог бы сделать что-то так же просто, как следующее из моего контроллера API (или, еще лучше, решение с DI):

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

Спасибо заранее.

Решение

Для потомков я думал, что включу свое полное решение в соответствии с предложением Wasp.

Сначала я изменил мою службу javascript (AngularJS), чтобы включить идентификатор соединения SignalR в заголовок настраиваемого запроса для вызова API, в этом случае INSERT:

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

Затем я получил идентификатор соединения из запроса в моем контроллере API после выполнения применимой операции CRUD, а затем вызвал мой концентратор:

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 для всех подключенных клиентов, который информирует их об изменении данных. Это никогда не должно происходить, но я просто включил его для полноты.

Ответы

Ответ 1

Чтобы действительно вызвать метод хаба, как вы его называете, вы должны подключиться к нему и вызвать это соединение. Вызывая что-то другое (ваш API), вы не можете совершать такой вызов, и поэтому вам приходится прибегать к серверным возможностям вещания, которые по своей природе не могут знать о том, что означает Caller, потому что нет вызывающего сигнала SignalR.

Тем не менее, если ваш клиент, вызывающий API (независимо от того, является ли он Javascript или С#) уже подключен к хабу при выполнении вызова, вы всегда можете украсить свой призыв к API с помощью connectionId вашего соединения концентратора (по строке запроса, по заголовкам,...). Если ваш API получает эту информацию, он может затем имитировать API Caller с помощью

Clients.Client(connectionId)

и он может сделать то же самое для Others с

Clients.AllExcept(connectionId)

над экземпляром IHubContext. Проверьте официальные документы .

Затем вы можете следовать предложению DDan об инкапсулировании использования IHubContext удобным централизованным способом или даже немного перестроить его, чтобы сделать его легко совместимым с DI.

Ответ 2

Я использую метод, объясненный в этом ответе.

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

Если вы предпочитаете это, на основе имени вашего метода.