En este post ascenderemos a un nivel de abstracción mucho mayor que el proporcionado por las conexiones persistentes y veremos cómo utilizar los Hubs, otro mecanismo proporcionado por SignalR que nos permitirá lograr una integración increíble entre el código cliente y de servidor, y hará aún más sencilla la implementación de este tipo de servicios.
En este momento ya deberíais tener claro qué cosas se pueden hacer con las conexiones persistentes de SignalR, e incluso cómo implementarlas:
- En el cliente:
- Conectar con un endpoint o servicio SignalR, manteniendo la conexión virtualmente abierta.
- Recibir asíncronamente (a través de un callback) los mensajes enviados desde el servidor mediante broadcasts o mensajes directos.
- Enviar mensajes al servicio usando el método
send()
.
- En el servidor:
- Detectar la conexión de nuevos clientes.
- Detectar la desconexión de clientes.
- Recibir los mensajes enviados por los clientes.
- Enviar objetos a todos los clientes conectados, a grupos de ellos, o a un cliente concreto.
Pues bien, con Hubs vamos a poder hacer exactamente las mismas cosas, pero usaremos una sintaxis mucho más fluida y directa tanto en cliente como en servidor, gracias a la mágica flexibilidad que aportan tecnologías como javascript y los tipos dinámicos de .NET.
Y para demostrarlo, implementaremos una sencilla hoja de cálculo multiusuario en tiempo real. Todos los usuarios conectados a la misma podrán editar celdas directamente, y las actualizaciones se irán propagando al resto de clientes de forma automática. Podéis descargar el proyecto para Visual Studio 2010 en el enlace que encontraréis al final del post, aunque en el siguiente vídeo se muestra la locura de resultado en ejecución con algunos clientes conectados editando celdas de forma aleatoria:
- en el lado cliente, cuando un usuario modifique el valor de una celda, enviar al servidor el nuevo valor V que ha introducido en la celda X, Y.
- en el lado servidor, ante la recepción del mensaje anterior, notificar a todos los clientes conectados que el usuario U ha establecido el nuevo valor V en la celda X, Y.
- de nuevo en el lado cliente, y ante la recepción del mensaje anterior, modificar la celda X, Y para que aparezca el nuevo valor V que introdujo el usuario U.
1. El lado servidor: creación de un Hub
En la práctica, se trata simplemente de una clase que hereda de la clase Hub
, definida en SignalR.Hubs
, en cuyo interior encontraremos los métodos que van a ser invocados directamente desde el cliente. En tiempo de ejecución, SignalR creará un proxy o representante del hub en el lado cliente, con métodos idénticos a los definidos en la clase, en cuyo cuerpo introducirá las llamadas Ajax necesarias para que se produzca la invocación de forma automática. Por tanto, si nuestra clase Hub tiene un método llamado “hacerAlgo()
” y el proxy que hemos creado en cliente se llama “hub
”, podemos invocarlo desde script haciendo un simple hub.hacerAlgo()
.
En nuestro ejemplo en el servidor lo único que tenemos que hacer es recibir las notificaciones de cambios de celda y enviárselas al resto de usuarios, lo que podemos conseguir con estas líneas:
public class MultiuserExcel: Hub
{
public void Update(int y, int x, string value)
{
this.Clients.updateCell(this.Caller.userName, y, x, value);
}
}
Es interesante también la propiedad Clients
de la clase Hub
. Ésta representa al representa al conjunto de clientes conectados, y nos permite llamar directamente a funciones que creemos en el lado cliente. O sea, que si desde el servidor hacemos una llamada a Clients.hacerAlgo()
, se ejecutará la función de script hacerAlgo()
en todos y cada uno de los clientes conectados. Ya ahí cada uno podrá procesar el mensaje y parámetros que enviemos.
Por último llamar la atención también sobre la propiedad Caller. Ésta, también heredada de la clase base Hub
, nos permite acceder a propiedades definidas a nivel de script en el cliente que ha realizado la llamada al método de servidor Update()
. En nuestro caso, será una propiedad en la que almacenaremos el nombre del usuario conectado, como veremos algo más adelante.
Fijaos que las clásicas fronteras entre cliente y servidor parecen haberse disuelto, es como si ambas capas se ejecutaran en el mismo proceso, aunque obviamente no es así: es SignalR el que se está encargando de mapear las llamadas entre ellas de forma transparente. Y aunque pueda parecer pura brujería, se trata simplemente de un uso ingeniosísimo de los tipos dinámicos de .NET 4 y de la flexibilidad de javascript.
2. El lado cliente
Lo primero que debemos hacer en el lado cliente es referenciar desde la vista o página dos archivos de script. El primero de ellos es el mismo que utilizábamos con las conexiones persistentes, /scripts/jquery.signalR.js, mientras que el segundo es un script dinámico generado por SignalR en la dirección /signalr/hubs. Para generarlo, SignalR localizará todas las clases descendientes de Hub
disponibles en el proyecto e incluirá en el script un proxy para cada una de ellas; en nuestro ejemplo, generará un objeto llamado $.connection.multiUserExcel
(el nombre del hub), que es el que podremos utilizar como proxy, aunque normalmente lo asignaremos a una variable para hacer más cómodo su uso.
Por tanto, un primer acercamiento al código de la vista específico para conectarse a un servicio SignalR podría ser algo así:
<script src="@Url.Content("~/scripts/jquery.signalR.min.js")" type="text/javascript"></script> <script src="signalr/hubs" type="text/javascript"></script> <script type="text/javascript"> $(function() { var hub = $.connection.multiuserExcel; // en “hub” tenemos el proxy // .. resto del código }); </script>
2.1. Envío de mensajes al servidor
Por tanto, volviendo a nuestro ejemplo, dado que hemos creado un método Update()
en el Hub (lado servidor), desde el cliente tendremos disponible el método update()
en el hub, que podemos utilizar directamente para enviar mensajes al servidor. Así, en nuestra hoja de cálculo podemos capturar el evento de cambio de cada celda y enviar la actualización al servidor para que la distribuya al resto de clientes conectados:
$("table.excel input").change(function() {
var newValue = $(this).val(); // Get current cell’s value
var x = $(this).data("x"); // Get current cell’s coords
var y = $(this).data("y");
hub.update(y, x, newValue); // Broadcast this change
updateTotal(x); // Update column total
});
2.2. ¿Variables de script accesibles desde el servidor?
Caller
del Hub
. Pues bien, para que esto sea así, las propiedades deben estar definidas sobre el proxy: $(function() {
var hub = $.connection.multiuserExcel;
hub.userName = prompt("Username:");
// .. resto del código
Caller.userName
desde el servidor para acceder al valor que se haya introducido en la misma desde el cliente que realice la llamada al hub. Ojo, que el acceso a las variables es sensible al uso de mayúsculas y minúsculas, deben escribirse igual en ambos extremos.
Otro comportamiento curioso de SignalR a este respecto es que también es capaz de propagar los cambios realizados en el servidor sobre estas variables, de forma que su nuevo valor pasará al lado cliente de forma automática:
this.Caller.message = "Current time: " + DateTime.Now.ToShortTimeString();
2.3. Recepción en cliente de mensajes enviados por el servidor
Clients
.
En nuestro ejemplo, si recordáis, en el servidor estamos enviando el mensaje a todos los clientes conectados al servicio de la siguiente forma:
this.Clients.updateCell(this.Caller.userName, y, x, value);
hub.updateCell = function(username, y, x, value) {
if (username != hub.userName) {
var elem = $("#cell" + y + "-" + x);
elem.val(value);
updateTotal(x); // Update column total
}
};
¡Y eso es todo!
Como os he recomendado otras veces, no dejéis de descargar el proyecto y probar el sistema con varias instancias del navegador abiertas, o desde varios equipos. Veréis lo sorprendente y espectacular que resulta y lo sencillo que es de implementar.
Publicado en: Variable not found.