web-dev-qa-db-fra.com

SignalR: comment appeler vraiment la méthode d'un concentrateur à partir du serveur / C #

J'essaie d'améliorer mon application qui nécessitera d'appeler un hub à partir de C # au lieu de javascript. Le flux de travail actuel pour ajouter une tâche dans mon application est le suivant:

  • effectuer un appel API pour ajouter les données à la base de données
  • renvoyer un nouvel enregistrement au contrôleur AngularJS
  • invoquer la méthode du concentrateur à partir du contrôleur
  • le hub diffuse les appels aux clients de manière appropriée

Ce que je voudrais faire, c'est contourner l'appel de la méthode du concentrateur à partir de mon contrôleur AngularJS et l'appeler directement à partir de ma méthode de contrôleur API.

Voici à quoi ressemble mon hub actuellement:

public class TaskHub : Hub
{
    public void InsertTask(TaskViewModel task)
    {
        Clients.Caller.onInsertTask(task, false);
        Clients.Others.onInsertTask(task, true);
    }
}

Il existe de nombreux SO threads sur le sujet, mais tout ce que j'ai lu me ferait ajouter le code suivant à mon contrôleur API:

var hubContext = GlobalHost.ConnectionManager.GetHubContext<TaskHub>();
hubContext.Clients.All.onInsertTask(task);

Il y a un certain nombre de problèmes avec cela. Tout d'abord, je veux que les appels de diffusion client existent dans une seule classe, et non directement depuis mon ou mes contrôleurs API. Deuxièmement, hubContext est une instance de IHubContext plutôt que IHubCallerConnectionContext. Cela signifie que je n'ai accès qu'à tous les clients et que je ne pouvais pas diffuser de réponses différentes aux Caller et Others comme je le fais actuellement.

Existe-t-il un moyen pour vraiment appeler la méthode d'un concentrateur à partir de C # et, idéalement, avoir accès aux différentes options d'appelant? Idéalement, je serais capable de faire quelque chose d'aussi simple que ce qui suit à partir de mon contrôleur API (ou mieux encore, une solution avec DI):

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

Merci d'avance.

SOLUTION

Pour la postérité, j'ai pensé inclure ma solution complète selon la suggestion de Wasp.

Tout d'abord, j'ai modifié mon service javascript (AngularJS) pour inclure l'ID de connexion SignalR dans un en-tête de demande personnalisé pour l'appel API, dans ce cas un INSERT:

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

Ensuite, j'ai récupéré l'ID de connexion de la demande dans mon contrôleur API après avoir effectué l'opération CRUD applicable, puis j'ai appelé mon concentrateur:

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

Mon hub ressemble à ceci où j'utilise maintenant uniquement des méthodes statiques appelées depuis mon contrôleur 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);
        }
    }
}

Comme vous pouvez le voir, j'ai une déclaration conditionnelle dans ma méthode hub pour gérer si l'appel hub n'a pas été initié à partir de la partie côté client de mon application. Ce serait le cas si une application/un service externe appelé mon API. Dans une telle situation, une connexion SignalR et bien sûr la valeur d'en-tête "ConnectionId" n'existeraient pas. Dans mon cas, cependant, je voudrais toujours appeler la méthode onInsertTask pour tous les clients connectés qui les informe du changement de données. Cela ne devrait jamais arriver, mais je l'ai simplement inclus pour être complet.

19
im1dermike

Pour vraiment appeler une méthode concentrateur, comme vous l'appelez, vous devez y être connecté et appeler via cette connexion. En appelant quelque chose de différent (votre API), vous ne pouvez pas faire ce genre d'appel, et vous devez donc recourir aux capacités de diffusion initiées par le serveur , qui par nature ne peut pas savoir ce qu'est le Caller car il n'y a pas d'appel de SignalR.

Cela dit, si votre client appelant l'API (que ce soit Javascript ou C #) soit déjà connecté au concentrateur lors de l'appel, vous pouvez toujours décorer votre appel vers l'API avec le connectionId de la connexion de votre hub (par chaîne de requête, par en-têtes, ...). Si votre API reçoit ces informations, elle peut alors simuler l'API Caller avec

Clients.Client(connectionId)

et il peut faire de même pour Others avec

Clients.AllExcept(connectionId)

sur une instance de IHubContext. Vérifiez les documents officiels .

Vous pouvez ensuite suivre la suggestion de DDan concernant l'encapsulation de l'utilisation de IHubContext d'une manière centralisée pratique, ou même la restructurer un peu pour la rendre facilement compatible DI.

7
Wasp

J'utilise la méthode expliquée dans this answer.

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

}

Le hub définit et utilise son hubContext, vous pouvez donc faire:

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

Ou:

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

Si vous préférez cela, en fonction du nom de votre méthode.

3
DDan