tutorial mvc hubs example c# signalr

mvc - SignalR: Cómo llamar verdaderamente al método de un hub desde el servidor/C#



signalr websocket (2)

Estoy tratando de mejorar mi aplicación que requerirá llamar a un hub desde C # en lugar de javascript. El flujo de trabajo actual para agregar una tarea en mi aplicación es:

  • Hacer una llamada a la API para agregar los datos a la base de datos
  • devolver el nuevo registro al controlador AngularJS
  • invocar el método del hub desde el controlador
  • hub centraliza la llamada a los clientes adecuadamente

Lo que me gustaría hacer es evitar llamar al método del hub desde mi controlador AngularJS y llamarlo directamente desde el método de mi controlador API.

Así es como se ve mi hub actualmente:

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

Hay muchos subprocesos de SO sobre el tema, pero todo lo que he leído me hace agregar el siguiente código a mi controlador de API:

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

Hay varios problemas con esto. En primer lugar, quiero que las llamadas de difusión del cliente existan en una sola clase, no llamadas directamente desde mi (s) controlador (es) API. En segundo lugar, hubContext es una instancia de IHubContext lugar de IHubCallerConnectionContext . Esto significa que solo tengo acceso a todos los clientes y no pude transmitir respuestas diferentes a la persona que Caller y Others como lo estoy haciendo actualmente.

¿Hay alguna manera de llamar realmente al método de un hub desde C # y, idealmente, tener acceso a las diferentes opciones de llamadas? Lo ideal sería poder hacer algo tan fácil como lo siguiente desde mi controlador API (o mejor aún, una solución con DI):

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

Gracias por adelantado.

SOLUCIÓN

Para la posteridad, pensé que incluiría mi solución completa según la sugerencia de Wasp.

Primero, modifiqué mi servicio javascript (AngularJS) para incluir el ID de conexión de SignalR en un encabezado de solicitud personalizado para la llamada a la API, en este caso un INSERT:

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

Luego, recuperé la ID de conexión de la solicitud en mi controlador API después de realizar la operación CRUD correspondiente y luego llamé a mi concentrador:

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

Mi concentrador tiene este aspecto y ahora solo uso métodos estáticos llamados desde mi controlador 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); } } }

Como puede ver, tengo una declaración condicional en mi método de concentrador para controlar si la llamada al concentrador no se inició desde la parte del lado del cliente de mi aplicación. Esto sería si una aplicación / servicio externo llamara mi API. En tal situación, no existiría una conexión SignalR y, por supuesto, el valor del encabezado "ConnectionId". En mi caso, sin embargo, todavía me gustaría llamar al método onInsertTask para todos los clientes conectados que les informa del cambio de datos. Esto nunca debería suceder, pero simplemente lo incluí para completar.


Estoy usando el método explicado en this respuesta.

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

El hub define y usa su hubContext, por lo que puede hacer:

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

O:

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

Si lo prefieres, en función de tu nombre de método.


Para poder realmente llamar a un método de hub, como lo llamas, debes estar conectado a él y llamar a través de esa conexión. Al llamar a algo diferente (su API) no puede hacer ese tipo de llamada y, por lo tanto, tiene que recurrir a las capacidades de transmisión iniciadas por el servidor , que por naturaleza no puede saber qué es el Caller porque no hay un llamador de SignalR.

Dicho esto, si su cliente que llama a la API (sin importar si es Javascript o C #) ya está conectado al concentrador cuando realiza la llamada, siempre puede decorar su llamada hacia la API con el connectionId de connectionId de la connectionId de su concentrador (por cadena de consulta, por encabezados, ...). Si su API recibe esa información, puede simular la API de la persona que Caller con

Clients.Client(connectionId)

y puede hacer lo mismo para Others con

Clients.AllExcept(connectionId)

sobre una instancia IHubContext . Consulta los documentos oficiales .

Luego puede seguir la sugerencia de DDan acerca de encapsular el uso de IHubContext de una manera centralizada conveniente, o incluso reestructurarlo un poco para que sea fácilmente compatible con DI.