SignalR (III): El servidor se comunica con el cliente (la aplicación web al revés).


Normalmente las aplicaciones web utilizan el patrón de petición-respuesta. La comunicación fluye del cliente al servidor. Pensemos al revés, imaginemos la aplicación web desde el servidor. Si es posible!!! Hagamos que sea el servidor quien realice la petición al cliente. Básicamente se me ocurren dos tipos de escenarios:

  • Un proceso en ejecución en el servidor (ya sea lanzado por una petición o una tarea programada) del cual necesitamos ser notificados cuando finalice.
  • Arquitectura CQRS que queremos que se comporten de manera tradicional.

Tal como comentamos en anteriores entradas, SignalR permite las comunicaciones asíncronas. Cuando nuestro cliente se conecta al hub; automáticamente esta disponible para recibir peticiones.

Primera Propuesta

  1. En la parte cliente, insertar el código de conexión al Hub.
    $(function () {
        var backgroundProcessHub = $.connection.backgroundProcessHub;
    
        $.connection.hub.start();
    
        backgroundProcessHub.client.notifyTaskProcessed = function (message) {
    	alert(message);
        };
    });
    
  2. En la parte servidor, insertar en la tarea que finaliza en el servidor
    var hub = GlobalHost.ConnectionManager.GetHubContext<BackgroundProcessHub>();
    hub.Clients.All.NotifyTaskProcessed("Task Ended");
    

Esto funciona, únicamente tiene el inconveniente que todos los usuarios son notificados.

Segunda propuesta

SignalR tiene un registro de eventos en servidor; cuando se produce un conexión/desconexión con los clientes.
Aprovecharemos; esto para subscribir los clientes al servicio de notificación (de esta manera no nos hace falta realizar una petición de cliente al servidor con un método de suscripción ;-)).

  1. En la parte cliente no se ha modificar nada, hacer lo mismo que en el paso anterior.
  2. En la parte servidor, añadir los eventos de conexión/desconexión en el hub.
    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Linq;
    using System.Threading;
    using System.Web;
    using Microsoft.WindowsAzure.ServiceRuntime;
    using SignalR.Hubs;
    using System.Security.Principal;
    
    namespace Example.Backoffice.Hubs
    {
        public class BackgroundProcessHub : Hub, IDisconnect, IConnected
        {
    
            public static readonly ConcurrentDictionary<string, string> Users = new ConcurrentDictionary<String, String>(StringComparer.OrdinalIgnoreCase);
    
    			public System.Threading.Tasks.Task Disconnect()
    			{
    				var userId =  WindowsIdentity.GetCurrent().Name;
    				Users.TryRemove(Context.ConnectionId, out userId);
    				return Clients[userId].leave(Context.ConnectionId);
    			}
        
    			public System.Threading.Tasks.Task Connect()
    			{
    				var userId =  WindowsIdentity.GetCurrent().Name;
    				Users.TryAdd(Context.ConnectionId, userId);
    				return Groups.Add(Context.ConnectionId, userId);
    			}
    
    			public System.Threading.Tasks.Task  Reconnect(IEnumerable<string> groups)
    			{
    				var userId =  WindowsIdentity.GetCurrent().Name;
    				Users.TryAdd(Context.ConnectionId, userId);
    				return Groups.Add(Context.ConnectionId, userId);
    			}
    	}
    }
    
  3. En la parte servidor, insertar en la tarea que finaliza en el servidor.
             var hub = GlobalHost.ConnectionManager.GetHubContext<BackgroundProcessHub>();
             var userName = "admin";
             if (BackgroundProcessHub.Users.Values.Any(x=>x == userName ))
                   hub.Clients[userName].NotifyTaskProcessed("Task Ended");
    

Notificaría al usuario, si este esta conectado. Podríamos sofisticar más el sistema, guardando la notificación para enviársela cuando se conectara de nuevo.

Tercera propuesta

  1. En la parte cliente no se ha modificar nada, hacer lo mismo que en el primer paso.
  2. En la parte hub del servidor no se ha de modificar nada, hacer lo mismo que en el paso anterior.
  3. En la parte servidor, insertar en la tarea que finaliza en el servidor.
             RegisterTaskEnded("admin","Task Ended");
    
  4. En la parte servidor, añadir una tarea
    using System;
    using System.Linq;
    using SignalR;
    
    namespace Example.Backoffice.Hubs
    {
    
        public static class BackGroundProcessJob
        {
    
            public static void DoWork()
            {
    
                var hub = GlobalHost.ConnectionManager.GetHubContext<BackgroundProcessHub>();
                foreach(var group in BackgroundProcessHub.Users.Values.Distinct())
                {
                    var result = GetTaskEnded((String) group);
                    if (result != null) hub.Clients[group].NotifyTaskProcessed(result.Message);
                }                
            }
        }
    }
    

    La tarea la lanzamos con la clase JobHost con un intervalo de 5 segundos.

                ThreadPool.QueueUserWorkItem(
                    state => new JobHost(null, null).Run(BackGroundProcessJob.DoWork, 5000));
    

Esta clase JobHost se encuentra descrita en articulo anterior Ejecutando un servicio en BackGround en IIS (Azure).

Ahora tenemos una aplicación totalmente pensada al revés. Es el servidor que genera peticiones en el cliente, cuando el usuario usa o se conecta a la aplicación.

Anuncios
Esta entrada fue publicada en Desarrollo, SignalR, web y etiquetada . Guarda el enlace permanente.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s