Registro centralizado de scripts en MVC 4 y Webpages 2

ASP.NET MVCUna novedad que descubro en los tutoriales preliminares de la segunda versión de WebPages, y que por tanto tendremos disponible en las futuras versiones de WebMatrix y ASP.NET MVC 4, es la posibilidad de registrar los scripts y estilos que necesitan nuestros componentes visuales (sean layouts, vistas completas, parciales o helpers), centralizando su carga y evitando duplicidades.

Por ejemplo, imaginad que tenemos una vista parcial o helper que requiere la inclusión de una biblioteca de scripts concreta. Si introducimos los correspondientes tags <script>, y por cualquier causa necesitamos utilizar este componente más de una vez sobre la misma página, estaremos realizando una doble carga del archivo externo, lo que además de lento puede generar errores. Otros escenarios igualmente incómodos son cuando si hay varias parciales distintas que usan una misma biblioteca común, o cuando queremos generar los scripts en un lugar determinado del HTML (por ejemplo, al final de la página) también desde una vista parcial o un helper. Y por supuesto, lo mismo ocurre con los estilos CSS.

El Asset Manager es un componente introducido en WebPages 2, también disponible para MVC 4, que va a poner un poco de orden ahí, usando un mecanismo bastante parecido al disponible en Webforms desde hace bastante tiempo.

Así, el objeto estático Asset (definido en System.Web.WebPages) permite registrar bloques de script inline, referencias a archivos de script externos, bloques de estilos, y referencias a archivos de hojas de estilo usando métodos tan simples como los siguientes:

    Assets.AddScriptBlock("alert('hi!');", true); // Include <script> tag
    Assets.AddScript("~/scripts/jquery.1.6.2.js");
    Assets.AddScript("~/scripts/jquery.1.6.2.js");
    
    Assets.AddStyleBlock("p { font-size: 4em}", true); // Include <style> tag
    Assets.AddStyle("~/content/site.css");

Este registro se puede implementar en cualquier parte dentro del ciclo de ejecución de la vista: en la página de contenido, parciales, helpers, de forma que se irán registrando todos los elementos que puedan hacer falta. Más adelante, para generar los bloques almacenados basta, con hacer lo siguiente por ejemplo en el Layout:

            [...]
            </div>
        </div>
    </footer>
    @Assets.GetScripts()
</body>
</html>

El resultado en ejecución sería:

            [...]
            </div>
        </div>
    </footer>
    <script type="text/javascript">alert('hi!');</script>
    <script src="/scripts/jquery.1.6.2.js" type="text/javascript"></script>
</body>
</html>

Y de la misma forma, tenemos disponible un método @Assets.GetStyles() para obtener todos los bloques relativos a estilos de la página que hemos ido registrando, y que podríamos generar en el interior de la etiqueta <head>, por ejemplo.

Es interesante saber que, al generar el código, Asset detectará si existen dos referencias hacia el mismo archivo de script o estilos, en cuyo caso la referencia se generará sobre la página una única vez, aunque por razones obvias este control no se realizará sobre los bloques de scripts o estilos inline.

Publicado en: Variable not found.

SignalR (IV): Hubs

Como vengo comentando desde hace un tiempo, SignalR es un framework realmente impresionante y aporta unas posibilidades enormes en prácticamente cualquier tipo de aplicación. Ya hemos visto qué es y las bases en las que se sustenta, y también hemos visto algunos ejemplos de uso utilizando conexiones persistentes (aquí y aquí), que es el enfoque de menor nivel disponible a la hora de desarrollar servicios basados en esta plataforma.

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.
Podemos hacer algunas cosillas más, como capturar los errores tanto en cliente como en servidor; aunque no lo hemos visto en los anteriores posts, son aspectos bastante triviales.

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:

Hoja de cálculo multiusuario en ejecución
Veremos que teniendo las herramientas apropiadas es algo bastante sencillo. Conceptualmente, sólo tenemos que hacer lo siguiente:
  • 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.
Obviamente, esto irá acompañado por algún script más para conseguir que se puedan editar las celdas de forma similar a una hoja de cálculo, o para mantener actualizados los sumatorios de la última fila, pero no nos centraremos en ello. En cualquier caso, siempre podéis verlo descargando el proyecto de prueba.

1. El lado servidor: creación de un Hub

En SignalR, un Hub es la clase donde se implementa el servicio, es decir, el punto desde el cual se gestionarán las peticiones enviadas por todos los clientes conectados al mismo, y desde donde se emitirán los mensajes destinados a actualizar su estado.

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);
    }
}
Voy a destacar varios aspectos de este código, comenzando por su extrema simplicidad. Casi sin haber visto antes nada de SignalR se puede intuir lo que hace. Observad también que hemos llegado a un punto de abstracción tal que no vemos nada relativo a conexiones ni desconexiones, simplemente implementamos lógica de nuestro servicio.

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

Como comentaba anteriormente, voy a saltarme el código destinado a hacer que funcionen los mecanismos básicos de edición de la hoja de cálculo, y nos centraremos en lo que nos interesa en este momento, la implementación de la comunicación con el servidor.

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>
Una consecuencia derivada de la inclusión del script dinámico generado por SignalR es que, a diferencia de lo que ocurría con las conexiones persistentes, no será necesario modificar la tabla de rutas de nuestra aplicación, puesto que los proxies contienen toda la información necesaria para que los servicios puedan ser utilizados de forma directa.

2.1. Envío de mensajes al servidor

Si analizamos los objetos generados de forma dinámica por SignalR para representar al hub en el lado cliente (proxies) podremos ver que éstos incluyen métodos exactamente con el mismo nombre y parámetros que los que hemos implementado en el lado servidor. Es decir, si en nuestra clase Hub tenemos métodos X() e Y(), el proxy en cliente dispondrá de estos dos mismos métodos, aunque en su implementación únicamente se realizarán las llamadas vía Ajax a sus respectivos equivalentes en 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
        });
Un detalle importante a tener en cuenta es que para adaptarse a las convenciones de nombrado de Javascript, aunque el método en servidor comience por una mayúscula, su correspondencia en cliente comienza en minúscula. Esta conversión sólo se realiza en el primer carácter, el resto debe escribirse exactamente igual en ambos extremos.

2.2. ¿Variables de script accesibles desde el servidor?

Recordad que decíamos que era posible acceder desde el servidor a propiedades existentes a nivel de script simplemente referenciándolas mediante la propiedad 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
A partir de ese momento podremos hacer uso de 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();
Obviamente, podemos crear tantas propiedades como necesitemos sobre el proxy, y todas ellas las tendremos disponibles en el servidor de la misma forma, tanto para consultar su valor como para modificarlo.

2.3. Recepción en cliente de mensajes enviados por el servidor

A diferencia de lo que habíamos visto usando conexiones persistentes, utilizando hubs no es necesario implementar evento, es mucho más simple. Lo único que debemos hacer es definir sobre el objeto que representa al hub en cliente el método o función a la que estamos llamando desde el servidor utilizando el objeto dinámico 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);
Por lo tanto, en cliente debemos implementar un método exactamente con el mismo nombre, y que reciba justo los parámetros que se envían desde el servidor (el usuario que realiza el cambio, la celda modificada y el nuevo valor de ésta):
    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!

En el proyecto de demostración encontraréis bastante más código que el que hemos visto en este post, pero principalmente va destinado a conseguir un look&feel similar a las hojas de cálculo tradicionales (bueno, salvando las distancias, claro!). En lo que respecta a la comunicación asíncrona desde y hacia el servidor, estas pocas líneas que hemos visto aquí son prácticamente todo lo que necesitamos para el sistema funcione y el resultado sea espectacular.

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.

ASP.NET MVC, WebAPI y Razor, ahora mucho más open source

¿Ein? ¿Pero no lo eran ya? Bueno, sí… pero no en toda la amplitud que puede ofrecer este término.

El progresivo acercamiento de ASP.NET al mundo del open source es algo que llevamos observando bastante tiempo. Desde hace unos años es posible acceder al código fuente de muchos productos, y también hemos visto cómo determinados proyectos puramente libres como jQuery eran incluidos con todos los honores en el conjunto de tecnologías de desarrollo oficiales de la Microsoft. Ahora vamos un paso más allá.

Open Source InitiativeEl código fuente de la primera versión de ASP.NET MVC fue publicado en 2009, poco después de su lanzamiento, bajo licencia MS-PL, un modelo de licencia open source aprobado por la OSI (Open Source Initiative). Desde ese momento, cada lanzamiento del framework iba acompañado de la publicación del código fuente en CodePlex, lo que nos permitía descargarlo, estudiar su funcionamiento, compilarlo, y retocarlo libremente. Lo mismo ocurría con la primera versión de WebPages, y previsiblemente lo mismo iba a ocurrir con las nuevas versiones de MVC, WebPages y Web API.

Sin embargo, el desarrollo de estos productos seguía estando totalmente en manos de Microsoft. Es cierto que podíamos reportar bugs y sugerir cambios, pero en ningún momento participar de forma activa en los proyectos, ni ver lo que iba ocurriendo entre una RTM y otra.

Y de pronto, hoy nos levantamos con la noticia de que ASP.NET MVC, Web API y Web Pages (Razor) van a ser distribuidos como open source (licencia Apache 2.0). Pero si ya podíamos acceder al código fuente, ¿cuál es la novedad?

Pues, en primer lugar, que ahora los proyectos se han abierto totalmente de cara a la comunidad. No se trata de dejar que se vean las tripas de estos productos una vez terminados, sino de adoptar un modelo de desarrollo abierto y transparente al cien por cien.

Para empezar, ya no tendremos que esperar a las versiones RTM para acceder al código fuente, como ocurría hasta ahora; tendremos total transparencia durante el proceso de desarrollo, puesto que el fuente está ya disponible en un repositorio público accesible con Git. Podemos descargarlo, clonarlo, hacer forks, sugerir ideas, ver en cualquier momento el estado de los trabajos, las características pendientes de desarrollo, los bugs subsanados y las modificaciones que se están realizando por parte de los contribuyentes en tiempo real.

ProgramadoresY recalco aquí la palabra “contribuyentes”, que es el segundo aspecto importante de la noticia a la que hacía referencia. No hablamos ya exclusivamente de los equipos de Microsoft que hasta ahora han llevado las riendas: a partir de ahora, los desarrolladores de la comunidad podemos contribuir con parches e implementando funcionalidades de estos tres productos.

De hecho, el primero en estrenarse ha sido Miguel de Icaza, que había sido avisado previamente del movimiento, y ha contribuido añadiendo modificaciones a las plantillas de edición para que tengan en cuenta el DataType (Email, Url, DateTime, Date, Time, Number, etc.) a la hora de generar los controles en la página, de forma que éstos ya aparezcan con el atributo type establecidos de forma correcta para aprovechar las ventajas de HTML5.

Pero ojo, que esto no significa que MVC, Web API o Razor vayan a ser abandonados a su suerte. Microsoft va a seguir incluyéndolos con Visual Studio y trabajando en ellos como hasta ahora, desarrollándolos con la misma gente, dándoles soporte oficial, y asegurando su calidad. Cada contribución será minuciosamente revisada y comprobada por el equipo de ASP.NET y sólo serán aprobadas aquellas que cumplan estrictos criterios de calidad y adhesión al Roadmap de cada producto.

En palabras de Hanselman, nada cambia salvo una cosa: Es ASP.NET, pero ahora puedes involucrarte.

Y las ventajas están claras: estas tecnologías se verán enriquecidas e impulsadas por la comunidad de desarrolladores, y, si no se ponen trabas, esto seguro que se traduce en un crecimiento mucho mayor que el que hemos vivido hasta ahora, una respuesta más rápida ante problemas, una mejor y más eficiente adaptación a los cambios tecnológicos del entorno, y sobre todo, una mayor cercanía de los productos a sus usuarios y a la realidad de nuestro trabajo.

En fin, se trata de un importante movimiento por parte de Microsoft cuyas consecuencias y resultado iremos viendo con el tiempo, pero si todas las premisas se cumplen sin duda es una gran noticia para los desarrolladores ASP.NET.

Enlaces:

Publicado en: Variable not found.

SignalR (III): Más Conexiones Persistentes

Qué divertidoLo divertido de escribir sobre productos que están todavía en fase de desarrollo es que cambian… y a veces, ¡de qué forma! Pues esto ha ocurrido con SignalR: recientemente se publicó la revisión 0.4 y bastantes cosas de las tratadas en el post anterior de la serie ha quedado en agua de borrajas. En fin, estaba avisado, así que mucho no puedo quejarme 😉

Por tanto, esta tercera entrega de la serie vamos a dedicarla (otra vez ;-)) a las conexiones persistentes, veremos qué cosas han cambiado con la llegada de la revisión 0.4, y desarrollaremos un nuevo ejemplo que ilustre las novedades que podemos encontrar a la hora de trabajar a bajo nivel con SignalR.

1. Resumen rápido: persistent connections y la versión 0.4 de SignalR

Para los que ya dominabais SignalR, os resumo las novedades principales que he encontrado hasta el momento en esta nueva revisión:

  • han desaparecido todos los métodos síncronos (OnConnected, OnDisconnect, OnReceived…) que implementábamos en el ejemplo de la segunda entrega de la serie para tomar el control ante determinados eventos de la conexión.
  • por tanto, ahora es obligatorio utilizar las versiones asíncronas (OnConnectedAsync, OnDisconnectAsync, OnReceivedAsync…), que retornan un objeto Task para implementar la lógica de gestión de los eventos.
  • SignalR ha sido desacoplado de ASP.NET, lo que permite utilizarlo en otros entornos. Como consecuencia, la firma de algunos métodos ha sido modificada. Así, cuando somos notificados de una nueva conexión, ya no recibimos el contexto HTTP tradicional, sino abstracciones propias de SignalR y desvinculadas de ASP.NET, lo cual supone alguna pequeña limitación aunque fácilmente salvable.
  • El proceso de negociación utiliza el transporte más apropiado para cada navegador, teniendo en cuenta de los componentes disponibles en servidor. En Internet Explorer se utiliza el transporte “Forever frame”, mientras que Firefox y Chrome usan “Long Polling” cuando el servidor no corre sobre .NET 4.5 (en caso contrario usaría websockets de HTML5).
  • Curiosamente, lo que antes denominábamos “clientId”, que es ese GUID que identifica de forma única a cada cliente conectado, ha pasado a llamarse “connectionId”. No afecta a nada, sigue significando lo mismo, pero es un cambio conceptual destinado a que tengamos claro qué indica ese dato.
  • Ahora, los broadcasts enviados justo durante la conexión de un nuevo cliente incluyen al propio cliente conectado, lo que evita tener que hacer el hack al que nos veíamos obligados en el post anterior (el “ping”).

A continuación desarrollaremos un ejemplo completo para que podemos ver de nuevo en funcionamiento esta maravilla. Como siempre, al final del artículo encontraréis un enlace para descargar el proyecto de demostración, en el que también he modificado el ejemplo que vimos en el post anterior para adaptarlo a los cambios de la revisión de SignalR.

2. Creación de un chat simple con SignalR

En este artículo vamos a complicar ligeramente el ejemplo anterior para construir un pequeño chat al estilo del que encontramos en Facebook, y cuyo aspecto podéis ver en la captura de pantalla siguiente:

Captura de pantalla
De forma muy similar al ejemplo que vimos en el post anterior, necesitaremos:

  • Crear nuestro servicio, como ya sabemos, implementando una clase que herede de PersistentConnection. Llamaremos a esta clase SimpleChatService.
  • Tomar el control cuando un cliente envía un mensaje al servidor, con objeto de hacer un broadcast al resto de usuarios. Esto lo conseguiremos sobrescribiendo el método OnReceivedAsync.
  • También tendremos que registrar la ruta hacia el endpoint, de forma que las peticiones puedan llegar a él.
  • Y, por último, implementaremos el lado cliente, que consistirá en el UI y los scripts que se conectan al servidor, envían los mensajes cuando son tecleados por el usuario, y muestran en pantalla a su vez los textos enviados por otros usuarios.

Vamos con ello.

3. El lado servidor. Asincronía al 100%.

Un sistema basado en SignalR no podría funcionar correctamente si no utilizara las capacidades de asincronía del framework, debido a la gran cantidad de mensajes que pueden llegar a intercambiarse entre cliente y servidor. Por ello, el equipo del producto ha puesto especial énfasis en utilizar métodos asíncronos siempre que sea posible, evitando así bloqueos innecesarios y aumentando la capacidad de respuesta del servicio.

En el post anterior veíamos que podemos tomar el control del sistema cuando los usuarios se conectan, desconectan, o envían información al servidor sobrescribiendo los métodos OnConnected(), OnDisconnect(), y OnReceived() respectivamente. Sin embargo, el uso de esos métodos síncronos podían hacer ver que este aspecto no era importante.

La versión 0.4 ha dado la vuelta a esto, y obliga a utilizar sus equivalentes asíncronos OnConnectedAsync(), OnDisconnectAsync() y OnReceivedAsync(); en estos casos siempre se retornarán objetos de tipo Task, lo que abre la puerta a la paralelización y mejor aprovechamiento de la infraestructura sobre la que estamos corriendo.

Por tanto, comenzaremos la implementación de nuevo servicio heredando de PersistentConnection y sobrescribiendo, aunque de momento con un cuerpo vacío, el método que necesitamos para nuestro chat:

public class SimpleChatService : PersistentConnection
{

    protected override Task OnReceivedAsync(string connectionId, string data)
    {
        // ...
    }
}

Observad el retorno del objeto Task, que representa la tarea asíncrona que iniciamos desde el método. Si el trabajo a realizar en su interior es asíncrono podemos utilizar cualquiera de las vías disponibles para crear un Task, lo retornamos, y listo, por ejemplo así:

    protected override Task OnReceivedAsync(string connectionId, string data)
    {
        return Task.Factory.StartNew(() =>
            // Async task here...
        );
    }

Pero, ¿qué ocurre si la lógica que queremos implementar en ellos es puramente síncrona? Pues no pasa nada, la ejecutaremos y retornaremos una llamada a la implementación por defecto del método, que ya se encarga de devolvernos un Task vacío 🙂

    protected override Task OnReceivedAsync(string connectionId, string data)
    {
        // Do some sync work and 
        // then return an empty Task
        return base.OnReceivedAsync(connectionId, data);
    }

Bueno, continuemos con la implementación de nuestro chat. Cuando un cliente nos envíe un texto, lo recibiremos en el método OnReceivedAsync, y lo que queremos hacer es enviarlo al resto de usuarios conectados, ¿no? Pues simplemente usaremos la propiedad Connection disponible en la clase base y llamaremos a su método BroadCast(). Y dado que éste retorna un Task, directamente podremos retornarlo como resultado del método. La implementación completa de la clase queda como sigue:

public class SimpleChatService : PersistentConnection
{
    protected override Task OnReceivedAsync(string connectionId, string data)
    {
        string clientDescription = getClientDescription();
        return Connection.Broadcast(new { user = clientDescription, message = data });
    }
 
    private static string getClientDescription()
    {
        var context = HttpContext.Current;
        var name = context.Request.IsAuthenticated
                        ? context.User.Identity.Name
                        : context.Request.UserHostAddress;
        return name;
    }
}

Chat en funcionamientoMediante la invocación a BroadCast() estamos enviando a todos los clientes conectados un objeto anónimo serializado en JSON. En este caso, dicho objeto contará únicamente con dos propiedades, user y message, que recuperaremos desde el lado cliente para mostrar el mensaje en pantalla junto con el nombre del usuario emisor.

El otro método que aparece en el código, getClientDescription(), es simplemente una ayuda para obtener el nombre del usuario que se mostrará en pantalla, que será el User.Identity.Name si está autenticado, o la IP en caso contrario.

No sé si os estáis dando cuenta, pero, ¡estamos implementando el lado servidor de un mini-chat en menos de diez líneas de código!

4. Rutado de peticiones hacia el endpoint

Como también vimos en el artículo anterior de la serie, para que las peticiones lleguen al endpoint es necesario registrarlo en el sistema de routing. El lugar para hacerlo, como es habitual, será el global.asax:

    protected void Application_Start()
    {
        AreaRegistration.RegisterAllAreas();
        RegisterGlobalFilters(GlobalFilters.Filters);
        RegisterSignalrConnections(RouteTable.Routes);
        RegisterRoutes(RouteTable.Routes);
    }
 
    public static void RegisterSignalrConnections(RouteCollection routes)
    {
        routes.MapConnection<SimpleChatService>("SimpleChat", "SimpleChatService/{*operation}");
    }

Esto es todo lo que necesitamos para dejar configuradas las rutas. El parámetro genérico de la llamada a MapConnection() indica la clase que implementa el servicio; el primer parámetro del método es simplemente el nombre de la ruta (no tiene mayor importancia), y el segundo especifica la URL a través de la cual será posible acceder al endpoint.

Y así, podemos pasar ahora a implementar el cliente.

5. El lado cliente

En el proyecto de demostración que podéis descargar al final del post está un poco más trabajado a nivel de interfaz de usuario, aquí vamos a comentar únicamente los principales aspectos a tener en cuenta.

En lo relativo a la presentación, nuestro chat sólo necesita la inclusión del siguiente código en la página:

<div id="chat">
    <div id="chat-messages">
        <!-- Placeholder for messages -->
    </div>
    <form method="POST" action="#" id="chat-form">
        <input type="text" id="chat-message" />
        <input type="submit" value="Send" />
    </form>
</div>

Como podéis intuir, el bloque con identificador “chat-messages” será utilizado, obviamente, para introducir los mensajes que vayamos recibiendo desde el servidor, es decir, los escritos por todos los usuarios que estén participando en el chat. El formulario, por otra parte, simplemente es el mecanismo mediante el cual los usuarios podrán enviar sus mensajes.

Además de esto necesitaremos algunos scripts que le den vidilla al asunto:

    $(function () {
        $("#chat-form").submit(function () {
            var message = $.trim($("#chat-message").val());
            if (message != "") {
                conn.send(message);
            }
            $("#chat-message").focus().val("");
            return false;
        });
 
        var conn = $.connection("SimpleChatService");
        conn.received(function (data) {
            var user = data.user;
            var message = data.message;
            $('#chat-messages')
                .append("<div><strong>" + user + "</strong>: " +
                         message + "</div>"
                );
          
            var sh = $('#chat-messages')[0].scrollHeight;
            $("#chat-messages").animate({ scrollTop: sh }, 3000);
        });
        conn.start();
    });

A grandes rasgos, lo que estamos haciendo es lo siguiente:

  • En primer lugar, creamos y asignamos la función de tratamiento del evento submit del formulario. En su interior, lo único que hacemos es enviar el contenido de la caja de texto “chat-message” al servidor, limpiar dicho control y volver a posicionar sobre él el foco de edición. El texto enviado desde este punto será el que recibimos en el método OnReceivedAsync() que hemos visto anteriormente implementado en el servidor.
  • A continuación, se obtiene una conexión al servicio, almacenándola en la variable conn.
  • Implementamos el callback received sobre el objeto conn, que será invocado cuando se reciban datos enviados por el servidor. El parámetro data será la instancia del objeto anónimo que enviamos desde el servidor mediante el broadcast, por eso podemos usar directamente sus propiedades name y message para añadirlo a la ventana del chat. Finalmente, desplazamos la ventana hasta el final del scroll para que se puedan ver directamente los últimos mensajes recibidos.
  • Por último, iniciamos la conexión llamando a método start() del objeto correspondiente.

Y con esto hemos acabado la implementación de nuestro simplísimo chat. Como os comentaba, en el proyecto de demostración está un poco más trabajado a nivel de interfaz, pero básicamente es lo que hemos visto aquí.

Simplechat SimpleChat

6. Punto extra: agrupación de clientes en SignalR

Hasta ahora, hemos visto que al hacer un broadcast el mensaje es enviado a todos y cada uno de los clientes que se encuentran en ese momento conectados al servicio. Esto es válido en muchos escenarios, pero hay otros en los que necesitamos algún mecanismo para segmentar o agrupar los clientes según determinados criterios.

Un ejemplo clásico serían los chats reales, donde es habitual encontrar “salas”. Los mensajes enviados por un usuario concreto son vistos únicamente por los compañeros de la sala en la que se encuentra, por lo que podríamos considerar que se trata simplemente de un mecanismo de agrupación de usuarios.

SignalR incluye de serie mecanismos para añadir o eliminar usuarios (clientes) a grupos y enviar mensajes a éstos de forma bastante sencilla, mediante los siguientes métodos de la clase PersistentConnection (que, por cierto, también retornan un objeto de tipo Task):

  • AddToGroup(string connectionId, string groupName): agrega el cliente con el identificador clientId al grupo denominado groupName.
  • RemoveFromGroup(string connectionId, string groupName): elimina el cliente clientId del grupo identificado como groupName.
  • SendToGroup(string groupName, object value): envía el objeto value, convenientemente serializado en JSON, a todos los clientes conectados al grupo groupName.

Fijaos que, de momento, el API es bastante simple en lo relativo a los grupos. Por ejemplo, no hay forma de vaciar un grupo, o de consultar los clientes pertenecientes a cada uno de ellos, aunque parece que está previsto ampliar las funcionalidades disponibles en futuras versiones del producto.

De nuevo os animo a que descarguéis el proyecto de demostración y veáis SignalR en ejecución. Un framework que, sin duda, vale la pena conocer por la cantidad de escenarios en los que podemos utilizarlo y aportar ese factor “¡uau!” que comentaba al principio de esta serie.

Publicado en: Variable not found.

Experiencia Summit

Space NeedlePues sí, toda una experiencia, no podría describir de otra forma los días que acabo de dejar atrás. Aún con las avenidas de Bellevue grabadas en la retina, el regustillo amargo de la cerveza Mac&Jac en el paladar, y el aroma a la Kobe Burger del Cheesecake Factory, es un buen momento para hacer una breve retrospectiva de lo que ha sido este viaje.
Primero empezaré contando lo malo, y si hay algo realmente malo de ir al Summit es precisamente el “ir”. Bueno, y el volver ;-).

Para alguien poco curtido en materia viajera como un servidor, saltar a la otra parte del mundo no es tarea sencilla. Es un viaje tan largo que da para todo: turbulencias que te ponían el estómago al revés, retenciones en el control de entrada a Estados Unidos, escalas, y muchas, pero muchas, horas de vuelo tanto transoceánicos (Barcelona-Nueva York) como internos (Nueva York-Seattle). Tanto la ida como la vuelta duraron más de veinte horas, interminables en algunos momentos, aunque bastante bien llevadas gracias a la inestimable compañía de los amigos Marc y Lluis.

Tenía la esperanza de encontrarme con Ballmer por un pasillo y decirle que a ver si la próxima vez podían hacerlo más cerquita, en Cádiz o algo así, pero no ha podido ser 😉 De todas formas, también es cierto que si lo organizaran en otra parte no sería lo mismo.

Otro tema curioso es el cambio horario. Las nueve horas de diferencia entre España y Seattle hacen que se duerma bastante poco; durante los primeros días me despertaba entre las cuatro y las cinco de la mañana, aunque ya después fue mejorando la cosa; eso sí, sólo conseguí dormir ocho horas seguidas el día previo al regreso. Pero a pesar del sueñecillo con el que hay que convivir durante unas jornadas, la verdad es que es un tema que no me ha resultado especialmente duro, ni a la ida ni a la vuelta.

Durante el viaje y estancia el acento americano me ha resultado prácticamente indescifrable. No es la primera vez que salgo al extranjero, ni que tengo que interactuar en inglés, pero siempre había sido en su “versión” británica y más o menos había sido capaz de defenderme. En este caso, cada vez que alguien me hablaba es como si lo hiciese en chino mandarín.

Por ejemplo, una dependienta de un McDonalds de Seattle, una vez acabó de atenderme, me despidió con un amable “Thank you for visiting us”. Entendí algo así como “¿Qué vais a visitar hoy?”. Me pareció raro, pero pensé que la simpática muchacha había notado que era extranjero y quería ser cortés, por lo que le respondí contándole los planes turísticos que teníamos para la tarde: íbamos a visitar el Space Needle, el monorail, Pike Place Market… En fin, podéis imaginar la cara que se le quedó a la muchacha, y las risas que eché cuando me di cuenta de lo que había pasado ;-DDDD

Afortunadamente, en las sesiones técnicas esto cambiaba y las palabras que cazaba eran suficientes para entender el mensaje apoyándome en las proyecciones, normalmente Powerpoints o Visual Studio. Además, sabéis que el lenguaje técnico es bastante más simple y previsible que lo te encuentras por la calle, las expresiones son siempre las mismas, y no cuesta mucho seguir el hilo a una presentación.

Vaya dos pájarosSin tener en cuenta los días que hemos ocupado por completo viajando, han sido en total cinco días de estancia en Bellevue, lo que nos ha dejado tiempo para todo: turismo, sesiones técnicas, networking, compras, divertirnos en buena compañía, y saludar a personas a las que no había tenido oportunidad de conocer hasta este momento.

Y desde luego, no nos podemos quejar de la meteorología. Aunque llevaba en la maleta paraguas y chubasquero por aquello de la “ciudad de la lluvia”, no me ha hecho falta sacarlos en ningún momento. Sé que en algunos momentos llovió, e incluso cayó algo de aguanieve, pero siempre cuando estábamos en el interior de edificios o de noche, y sólo en una ocasión nos dimos un leve remojón de camino al hotel. Eso sí, lo que es el sol la verdad es que se ve poco por allí, el cielo siempre está encapotado y amenazante.

Nos alojábamos en un sitio bastante céntrico de Bellevue, en el hotel Courtyard by Marriot. Las habitaciones eran muy amplias y bien equipadas, y, afortunadamente, con acceso a internet wifi gratuito de calidad bastante razonable –salvo cuando intentabas descargar Windows 8 ó VS11 beta-, lo cual permitía seguir con facilidad lo que iba ocurriendo a este lado del Atlántico, mantener contacto con familia, amigos, clientes, colaboradores, y continuar las labores de tutoría de los cursos de CampusMVP. Además, se encontraba a pocos minutos a pie de los hoteles donde se concentraban gran número de charlas y actividades del evento, el Hyatt Regency y el Westing Bellevue, lo que nos ha venido bastante bien a la hora de planificarnos y movernos por los alrededores.

Rascacielos en Seattle
Visitamos Seattle en dos ocasiones. Estaba bastante cerca de Bellevue, a unos veinte minutos en autobús, y pasamos una tarde de turismo en la ciudad, en la que pudimos pasear por sus avenidas, subir al famoso monorail y al Space Needle, y las obligadas visitas al Pike Place Market y al cercano “Starbucks original”, lo que fue el primer local de la famosa cadena.

Todo un gustazo darse una buena caminata para saborear el ambiente a pie de calle, pero, para los que estamos acostumbrados a ciudades relativamente planas como Sevilla, quizás lo más impresionante es ver por primera los enormes edificios y rascacielos de la ciudad.

La segunda vez que fuimos a Seattle, ya el último día del evento, fue para asistir a la fiesta de clausura del evento, que tuvo lugar en el Century Link Field, el estadio del Seahawks de Seattle, un equipo local de fútbol americano, y del Sounders FC. Un fiestorro por todo lo alto en el que no faltaron animadoras (no puedo poner fotos sin comprometer a algunos amigos, es NDA ;-)), mascotas del equipo, jugadores firmando autógrafos, actividades de ocio, comida, bebida, videojuegos… en fin, una pasada.

También, a otros veinte minutos en autobús estaba el campus de Microsoft en Redmond, que visitamos en varias ocasiones, y que es una de las cosas que más me han impresionado del viaje.
Primero, porque cuando hace tiempo veía fotos de las instalaciones o leía sobre cómo era aquello, en ningún momento se me podía pasar por la cabeza que en algún momento pudiera visitarlo y, segundo, porque es sencillamente impresionante.
Campus de Microsoft, zona "The Commons"
Ya desde que llegas a Bellevue empiezas a ver Microsoft en un gran número de edificios, y empiezas a hacerte una idea del gigante que hay detrás de ese logo que vemos todas las mañanas al arrancar el ordenador. Y sí, es cierto que todos sabemos que es una gran multinacional, que es enorme, descomunal… pero la dimensión real de la compañía la sientes cuando estás allí.

Como ya comentaba el amigo José Manuel Alarcón hace cosa de un año, independientemente de la simpatía o no que se tenga por la casa, sus productos, y sus políticas, las instalaciones de Microsoft en Redmond son dignas de admiración. Avenidas, árboles y jardines perfectamente cuidados, restaurantes, bares, cafeterías, bancos, tiendas, áreas deportivas (al menos un campo de fútbol y una cancha de baloncesto), servicios de transporte interno para desplazarse gratuitamente a través de la inmensa superficie que ocupa, y muchas cosas más que seguro no vi durante las visitas.

Bueno, y seguro que a estas alturas os preguntaréis, “vale, todo eso está muy bien, pero, ¿y los contenidos de las sesiones técnicas? ¿Porque ibas para eso, no?”. Pues la verdad es que poco os puedo contar al respecto 🙁

La NDA (Non-Disclosure Agreement, “Acuerdo de Confidencialidad”) es un documento que firmamos al ser nombrados MVP en el que nos comprometemos a no revelar información que Microsoft considera restringida y a la que en algunas ocasiones tenemos acceso. En el Summit todas las sesiones se encontraban protegidas por este acuerdo.

También ha habido oportunidad de conocer en persona a componentes de los distintos equipos de producto y celebrities de este mundillo. Resulta curioso reconocer por los pasillos a gente a la que sigues desde hace mucho tiempo; por allí andaban Phil Haack, Scott Hanselman, Erik Porter, Damian Edwards, David Fowler, Jon Galloway, Daniel Roth… Me quedé con las ganas de ver en directo a Scott Guthrie y Anders Hejlsberg, pero la próxima vez será 😉

Y por lo demás, muchos almuerzos, cenas y sus correspondientes copas posteriores con los componentes de la representación española en el Summit, en las que, al menos un servidor, ha disfrutado del enorme nivel humano, técnico y el gran sentido del humor que se gasta en el grupo. Vamos, que me lo he pasado de fábula 🙂

Publicado en: Variable not found.

Llamemos a las cosas por su nombre

Errores y usuariosSeguro que todos sabéis lo que es tratar con los usuarios de vuestro software: son lentos, patosos, inconscientes, atrevidos, inseguros, ignorantes, y no consiguen entender la belleza de la herramienta que con tanto esmero hemos creado. Y lo que más rabia da es que muchas veces tienen razón en los problemas que reportan de nuestras aplicaciones.

Pero afortunadamente otras veces no es así, y los problemas no son nuestros sino suyos, momento en el que pone a disposición de la dulce venganza la genial creatividad característica de nuestro gremio.

Lo que vamos a ver a continuación son siete de las formas existentes, conocidas y documentadas de cargar las culpas sobre el usuario, o simplemente llamarlo torpe, sin que éste se dé cuenta. Algo muy socorrido si, por ejemplo, debemos contarle a un compañero lo que ocurre en presencia del implicado.

Por supuesto, a partir de aquí todo es secreto, no dejéis que caiga en manos de algún desalmado 😉

1. Error ID-10-T

El «ID-10-T» (pronunciado ID-ten-T) es un código de error que se utiliza para indicar que el usuario es simplemente idiota. De hecho, si unimos las letras (ID10T) podremos ver fácilmente que el término es la traducción a leet speak de IDIOT, lo cual no podía dejar más clara su intencionalidad y los momentos en que debemos utilizarlo.

Se rumorea que en los años noventa los equipos de soporte técnico telefónico daban instrucciones a los usuarios y clientes para que incluyeran en su archivo config.sys la línea id=10t (o combinaciones parecidas) como aviso para futuras intervenciones cuando se encontraban con un espécimen a tener en cuenta.

ID10T

También podemos encontrarlo, aunque en menor medida, escrito como ID107, otra posible traducción del término al lenguaje 1337.

Curiosamente, el término se utiliza en otros ámbitos como el militar, aunque la pronunciación puede variar ligeramente: “One delta ten tango” o “Eye Dee Ten Tango”.

¡ASP.NET MVC 4 Beta disponible!

ASP.NET MVC 4 betaBueno, supongo que ya os habréis enterado, pero por si acaso os lo comento: varios meses después de aparecer la última revisión pública, hoy mismo se ha publicado la beta de ASP.NET MVC 4 (para Visual Studio o Visual Web Developer 2010) que ya podemos ir descargando y probando para ir haciéndonos a la idea de lo que se nos viene encima.

Eso sí, si vais a hacerlo desinstalad la developer preview antes de nada, tal y como se indica en el documento de notas de la revisión. Si tenéis instalado ASP.NET MVC 3 o anteriores, tranquilos, que seguirán funcionando con normalidad tras instalar esta beta (¡o eso aseguran! ;-))

El producto aún está recién salido del horno, así que seguro que durante los próximos días seguimos descubriendo novedades interesantes en esta beta, pero os voy a ir comentando algunas de las que me he encontrado en un primer contacto.

1. Nuevas plantillas

En primer lugar, al crear un proyecto ASP.NET MVC 4 Beta nos encontramos con las siguientes plantillas:

  • Empty, Internet Application y Intranet Application, ya conocidas de versiones anteriores del framework.
  • Mobile application, que ya comentamos en el post en el que desmenuzábamos la developer preview de MVC 4, allá por el mes de septiembre.
  • ASP.NET Web API, una de las novedades de esta entrega, un interesante framework que facilita creación de aplicaciones que publican servicios y datos utilizando HTTP.
  • Single Page Application, otra novedad de la beta, que incluye un conjunto de herramientas y componentes específicos para implementar aplicaciones en una única página web, mediante el uso intensivo de Ajax.

Plantilla de proyectos MVC 4 beta

2. ASP.NET Web API

ASP.NET Web API es un nuevo framework que ofrece una fórmula muy sencilla para exponer determinadas funciones o datos de nuestras aplicaciones mediante el uso de toda la potencia que nos ofrece el protocolo HTTP, y muy apropiada para interfaces de tipo REST.

Esto, aunque ya podíamos conseguirlo con las versiones anteriores del framework, con ASP.NET Web API se ha simplificado bastante, permitiéndonos ir más al grano en nuestros desarrollos y olvidarnos de detalles de menor nivel. Las principales características de Web API son las siguientes:

  • Integración con el sistema de routing, de forma que tenemos control total sobre las URL de acceso a los servicios, y la posibilidad de usar mecanismos con los constraints.
  • Negociación de contenidos, de forma que cliente y servidor pueden acordar formatos de intercambio de información. De fábrica vienen con soporte XML, Json y otros, aunque es posible añadir nuevos formatos o incluso modificar la forma en que la negociación se produce.
  • Soporte de model binding y validación, mecanismos ya conocidos por los desarrolladores de MVC, que sirven para convertir la información disponible en las peticiones en objetos del CLR.
  • Soporte de filtros de acción, permitiendo la introducción de comportamientos trasversales cuya ejecución se realizará previa y posteriormente a la ejecución de acciones.
  • Composición de consultas, que ofrece la posibilidad de realizar peticiones directas según las convenciones OData de una forma realmente simple, retornando objetos IQueryable<T>.
  • Otras características, como la facilidad para la realización de pruebas unitarias, la resolución de dependencias, o la posibilidad de usar self-hosts para la publicación de servicios.

Puedes aprender más sobre ASP.NET Web API en la web oficial del producto.

En la práctica, lo que vamos a notar es que al crear un proyecto ASP.NET MVC 4 encontraremos una plantilla llamada “Web API”. Con ella podemos hacernos rápidamente una idea de las posibilidades que nos ofrece esta nueva característica.

La idea consiste en crear controladores “especiales” que no heredan del tradicional Controller, sino de la nueva clase ApiController. Sus acciones, a diferencia de las habituales cuando programamos con el framework, es que se nombran en función del verbo HTTP al que atenderán; es decir, existirá una acción GetXXX() para manejar las peticiones HTTP GET, otra acción PostXXX() para el verbo POST, etc. Para la ejecución de estas acciones se utilizan los mecanismos habituales de binding para poblar los parámetros, lo que implica, por ejemplo, que podemos utilizar parámetros complejos en las mismas, o data annotations para especificar restricciones.

Curiosamente, las “XXX” pueden ser cualquier cosa; el framework simplemente buscará que el nombre del método comience por Get, Post, o el verbo que sea, a la hora de determinar la acción a ejecutar.

El siguiente ejemplo muestra la implementación por defecto para los verbos habituales; observad que el retorno en las acciones de consulta son directamente los objetos a enviar al cliente serializados como JSON.

public class PruebaController : ApiController
{
    // GET /api/prueba
    public IEnumerable<string> Get()
    {
        return new string[] { "value1", "value2" };
    }
 
    // GET /api/prueba/5
    public string Get(int id)
    {
        return "value";
    }
 
    // POST /api/prueba
    public void Post(string value)
    {
    }
 
    // PUT /api/prueba/5
    public void Put(int id, string value)
    {
    }
 
    // DELETE /api/prueba/5
    public void Delete(int id)
    {
    }
}

Para facilitar la creación de estas clases, se ha incorporado en el diálogo de creación de controladores dos nuevas plantillas, “Empty API Controller” y “API Controller with empty read/write actions”. El resultado de utilizar la segunda de ellas es el código que hemos visto anteriormente.

Nuevas plantillas de controlador

Otro aspecto que hace posible el acceso exterior a las API definidas según esta vía es que en el global.asax encontramos registrada la siguiente ruta:

    routes.MapHttpRoute(
        name: "DefaultApi",
        routeTemplate: "api/{controller}/{id}",
        defaults: new { id = RouteParameter.Optional }
    );

De esta forma, una petición como POST /api/prueba sería dirigida hacia la acción Post() del controlador PruebaController que hemos visto anteriormente, o GET /api/prueba/5 invocaría la acción Get() de PruebaController, suministrándole el valor 5 en el parámetro id.

¿Y era necesario crear una plantilla de proyecto nada más que para eso? Pues no. De hecho, este mecanismo viene también incluido de serie en la plantilla “Internet Application”, donde podemos encontrar ya la ruta registrada. Es decir, en una aplicación normal simplemente hemos de crear nuestros controladores ApiController y las tendremos nuestros API publicados de forma inmediata.

Por último, comentar que ASP.NET Web API no sólo incluye componentes para implementar el lado servidor, también se ofrecen nuevos componentes que facilitan el acceso a servicios desde clientes .NET.

3. Single Page Applications (SPA)

Las aplicaciones de página única son aquellas que realizan todas sus funcionalidades sin apenas navegación entre páginas del sitio web, y usan de forma intensiva scripting y Ajax. Muchos servicios actuales, como Twitter y Facebook, utilizan este enfoque, logrando una gran sensación de dinamismo y modernidad en los interfaces de usuario.

Hasta ahora no disponían de un soporte especialmente potente en MVC 3 y anteriores… por no decir que no tenían soporte alguno. Había que gestionarlo todo de forma manual, como nos mostraba el amigo Eduard Tomás en este magnífico post: envío de datos al servidor, actualizaciones parciales de página, recepción de datos, control del botón “atrás” del navegador… en fin, no es una tarea sencilla.

MVC 4 Beta ha incluido, palabras textuales, “soporte experimental” para la creación de aplicaciones de este tipo, incluyendo los siguientes componentes:

  • Bibliotecas de script que permite interactuar con datos cacheados localmente,
  • Componentes adicionales de Web API con soporte para Unit of Work y DAL.
  • Una nueva plantilla de proyecto (“Single page applications”) con herramientas de para generar rápidamente andamiaje.

Puedes aprender más sobre Single Page Applications en ASP.NET en la web oficial del producto.

La nueva plantilla de proyectos, llamada “Single page applications”, que en verdad tiene bastante poca chicha, simplemente incluye un buen puñado de scripts en el proyecto y da algunas indicaciones que nos permiten poner en marcha el esqueleto de una interfaz CRUD usando SPA en unos cuantos clics.

Como podéis ver, esas instrucciones se encuentran en comentarios en una clase del Modelo:

// To quickly get started building a Single Page Application based on the following model
// class, build your solution (Build -> Build Solution), then right-click on the "Controllers" folder, 
// choose Add -> Controller, and set the following options:
//
//  * Controller name:    TasksController
//  * Template:           Single Page Application with read/write actions and views, using Entity Framework
//  * Model class:        TodoItem (MvcSPAApplication.Models)
//  * Data Context class: Choose <New data context...>, then click OK
//
// Afterwards, launch your application (Debug -> Start Debugging), then browse to the URL /Tasks
// For more information, see http://go.microsoft.com/fwlink/?LinkId=238343
 
public class TodoItem
{
    public int TodoItemId { get; set; }
    [Required]
    public string Title { get; set; }
    public bool IsDone { get; set; }
}

Si seguimos las instrucciones, lo que veremos en ejecución es una pantalla de introducción de datos de elementos TodoItem , funcionando en modo SPA, como la que podéis observar en la siguiente captura:

SPA en ejecución

Vamos a seguir paso a paso las instrucciones y comentamos lo que va ocurriendo en el proyecto.

Primero, creamos nuestro controlador TasksController utilizando la nueva plantilla “Single Page Application with read/write actions and views using Entity Framework”. Creo que esta extensa descripción no deja mucho lugar a dudas de lo que pretendemos generar, no?

Generación del controlador

Observad también que hemos indicado que deseamos generar una clase de contexto de datos, a la que vamos a llamar, en un alarde de originalidad, DataContext. El resultado de esta acción es la inclusión de un buen número de elementos en el proyecto.

En primer lugar, se añade a nuestra carpeta /models el contexto de datos, una clase similar a la que podemos generar desde MVC 3 en escenarios similares de creación de controladores: heredando de DbContext, y con una propiedad pública DbSet<TodoItem> para representar al conjunto de elementos en el almacén. Como viene siendo costumbre, se utiliza EF Code First para el acceso a datos.

Como era de esperar, en la carpeta /controllers encontraremos un controlador específico, TaskController, para cargar la página principal, desde donde realizaremos el mantenimiento de entidades TodoItem, que lo único que hace es retornar la vista. Será ésta (es decir, en cliente) donde se implementará la mayor parte de la lógica. El código de este controlador es el siguiente:

    public class TasksController : Controller
    {
        public ActionResult Index()
        {
            return View();
        }
    }

Dicha vista incluye toda la lógica de control, y realiza peticiones al servidor para actualizar o consultar los datos (entidades TodoItem) que necesita en cada caso. Estas peticiones, iniciadas mediante scripting desde el lado cliente, atacan a un controlador especial llamado DataController, que es generado automáticamente en la carpeta /controllers, y que se encuentra definido en los siguientes archivos:

  • DataController.cs, es una clase parcial donde podemos incluir el código genérico del controlador, aquél que sea común a todas las peticiones de datos que podamos recibir. En este caso hereda de DbDataController<DataContext>.
  • DataController.TodoItem.cs, es una porción de la clase parcial anterior que implementa métodos CRUD (GetTodoItem(), InsertTodoItem(), UpdateTodoItem(), y DeleteTodoItem()) sobre el elemento que hemos indicado anteriormente al generar el controlador. Habrá una clase similar a esta para cada entidad de las que generemos el interfaz de mantenimiento utilizando esta vía; es decir, que igual que tenemos DataController.TodoItem.cs para implementar los servicios relativos a la entidad TodoItem, tendremos DataController.Friend.cs para la gestión de entidades Friend, DataController.Product.cs para Product, etc. En definitiva, que dentro del controlador DataController tendremos métodos para manipular los datos de las entidades que necesitemos desde nuestro interfaz SPA.

En cuanto a la vista, es obvio que en /Views/Tasks encontraremos la vista principal de la funcionalidad de gestión. Si abrimos el archivo, lo primero que llama la atención es que en lugar de encontrarnos una maraña de código terrible para implementar la lógica de control y presentación del sistema, lo que podemos observar es bastante limpio gracias al uso de Knockout. Esta biblioteca open source es un framework de scripting que utiliza el patrón MVVM (Model-View-ViewModel) para crear interfaces dinámicos y bien estructurados, poniendo un poco de orden en la normalmente caótica capa cliente.

Archivos de vista añadidosTambién en la carpeta de vistas encontramos los siguientes archivos:

  • _Editor.cshtml, una vista parcial que define cómo será el interfaz de edición de la entidad, es decir, el formulario que aparecerá cuando vayamos a crear o editar un objeto.
  • _Grid.cshtml, la maquetación de la rejilla de datos.
  • _Paging.cshtml, la composición del mecanismo de paginación del grid.
  • _SpaLayout.cshtml, en la carpeta /Views/Shared, que es un layout específicos para este tipo de páginas (SPA). Lo único destacable del mismo es que incluye bastantes bibliotecas de script, necesarias para que funcione todo el invento.

La verdad es que el funcionamiento hay que estudiarlo a fondo para hacerse con el control de lo que ocurre por detrás. A priori, da la sensación de excesivo automatismo, y no queda claro los puntos de extensibilidad o personalización más allá del retoque de las vistas parciales generadas, puesto que se utilizan scripts generados automáticamente (como la clase ViewModel en creada en javascript a partir de la entidad del Modelo que estamos gestionando, en este caso TodoItem), upshot.js, knockout.js, nav.js., history.js, adaptadores upshot-knockout… en fin, demasiados scripts como para poder entender rápidamente el funcionamiento.

A ver si aumenta la información disponible en el sitio web, le echamos un poquito más de tiempo, y podemos llegar a verle sentido al esfuerzo que han realizado en esta dirección…

4. Cosas que más o menos siguen igual

No dudo que habrán mejorado internamente, pero sí que hay bastantes cosas que al primer vistazo siguen aproximadamente como en la developer preview:

  • El look mejorado de la plantilla de proyecto por defecto, bastante más bonita que la clásica que lleva con nosotros desde el principio de los tiempos del framework MVC, además de incluir de serie determinadas funcionalidades basadas en Ajax.
  • El DisplayMode, que sabéis que es esa característica permite crear una única aplicación y renderizar una u otra vista en función del dispositivo o las condiciones que deseemos. Siguen siendo válidos los mecanismos de switching de vistas, o el browser overriding que ya describimos cuando apareció la developer preview.
  • Seguimos teniendo la plantilla para creación de sitios específicos para móviles usando jQuery Mobile.
  • El componente de bundling (compactación y minimización) de archivos de script y de estilos, del que ya estuve hablando por aquí hace algún tiempo, sigue estando presente, y de hecho en la plantilla de proyectos se incluye por defecto la configuración del mismo (en el global.asax) y las referencias a los recursos compactados en los layouts.
  • Se siguen utilizando por defecto los proveedores universales de membresía, roles, profiles y sesiones.
  • Se mantiene compatibilidad con el SDK 1.5 de Azure de septiembre de 2011.
  • Eso sí, sigo sin ver ni rastro de las famosas recetas (recipes), que tanto se destacaron en el roadmap del producto. Ciertamente hay un SDK de recipes en Nuget, pero pocas recetas ya implementadas para echarnos a la boca.

En definitiva, se trata de un pasito más hacia la versión definitiva de ASP.NET MVC 4. ¡A ver qué sorpresas nos encontramos por el camino! 😉

Enlaces:

Publicado en: Variable not found.

Validación manual con Data Annotations

Microsoft .NET Habitualmente asociamos la validación de entidades basadas en anotaciones de datos, o data annotations, a tecnologías como dynamic data o ASP.NET MVC, y estamos acostumbrados a que la validación se realice de forma automática, pero nada más lejos de la realidad. Podemos utilizar data annotations desde cualquier tipo de aplicación .NET (Webforms, Winforms, WPF, Consola, o cualquier otra en la que tengamos disponible System.ComponentModel.DataAnnotations), puesto que existe la posibilidad de invocar manualmente los procedimientos de validación.

En este post vamos a ver cómo realizar validaciones basadas en anotaciones de forma manual, lo cual puede tener su utilidad en gran número de escenarios.

Resumidamente, esta técnica consiste en decorar cada una de las propiedades con una serie de atributos llamados anotaciones (definidos en System.ComponentModel.DataAnnotations) que indican las comprobaciones que se aplicarán a la entidad para determinar su validez. La siguiente porción de código muestra una entidad en la que se están indicando estas restricciones en cada una de sus propiedades:

public class Friend
{
    [Required, StringLength(50)]
    public string Name { get; set; }
 
    [Range(0, 120)]
    public int Age { get; set; }
}

En el citado espacio de nombres encontramos atributos que cubren la mayoría de casos frecuentes: Required (propiedad obligatoria), RegularExpression (validar contra una expresión regular), StringLength (longitud máxima y mínima de un texto), Range (rangos de valores permitidos), y CustomValidation (validaciones personalizadas). Además, este conjunto de anotaciones puede ser extendido muy fácilmente creando atributos que hereden de ValidationAttribute, disponible también en System.ComponentModel.DataAnnotations.

Validación manual de objetos

De lo más sencillo: la clase estática Validator, disponible también en el namespace System.ComponentModel.DataAnnotations, ofrece métodos que permiten realizar las comprobaciones de forma directa sobre objetos o propiedades concretas.

En este caso, dado que lo que nos interesa es validar las entidades completas, utilizaremos el método Validator.TryValidateObject(), al que suministraremos:

  • el objeto a validar,
  • un contexto de validación (que debemos crear previamente),
  • una colección de ValidationResult en la que almacenaremos los errores,
  • y, por último, si deseamos validar todas las propiedades (indicando true), o por el contrario preferimos parar el proceso en cuanto se detecte el primer error (false).

La implementación de la validación podría ser como la que la sigue:

    private IEnumerable<ValidationResult> getValidationErrors(object obj)
    {
        var validationResults = new List<ValidationResult>();
        var context = new ValidationContext(obj, null, null);
        Validator.TryValidateObject(obj, context, validationResults, true);
        return validationResults;
    }

El método retornará una lista de errores vacía cuando el objeto haya superado las restricciones impuestas, o llena con los objetos ValidationResult que describen los problemas encontrados.

Y podríamos utilizarlo desde una aplicación de consola de la siguiente forma:

    var friend = new Friend { Age = -1, Name = "" };
    var errors = getValidationErrors(friend);
    foreach (var error in errors)
    {
        Console.WriteLine(error.ErrorMessage);
    }
The Name field is required.

The field Age must be between 0 and 120.

Los mensajes de validación que aparecen pueden ser definidos en la misma anotación, por ejemplo así:

    [Required(ErrorMessage="Please, enter the name")]
    public string Name { get; set; }

¿Y si los metadatos están en otra clase?

Hay escenarios en los que no tenemos acceso a la clase en la que deseamos introducir las anotaciones. Un ejemplo claro lo encontramos cuando nos interesa especificar las restricciones en una clase generada por un proceso automático, como el diseñador de EDM de Entity framework; cualquier cambio realizado sobre el código generado será sobrescrito sin piedad al modificar el modelo.

En estos casos, es una práctica frecuente definir los metadatos en clases “buddy”, que son copias exactas de la entidad a anotar, pero que serán utilizadas únicamente como contenedores de anotaciones. Las clases buddy se vinculan con la entidad original utilizando el atributo MetadataType de la siguiente forma:

    // This class has been generated by a tool
    public partial class Friend
    {
        public string Name { get; set; }
        public int Age { get; set; }
    }
 
    // Let's associate the buddy class FriendMetadata
    [MetadataType(typeof(FriendMetadata))]
    public partial class Friend
    {
        
    }
 
    // Buddy class
    public class FriendMetadata
    {
        [Required]
        public string Name { get; set; }
 
        [Range(0, 120)]
        public int Age { get; set; }
    }

Observad que para poder utilizar esta técnica es necesario que la entidad a la que queremos añadir anotaciones sea creada como parcial. En caso contrario no podríamos indicarle con MetadataType dónde se encuentran definidos sus atributos de validación.

Pues bien, resulta que algunos marcos de trabajo (como ASP.NET MVC) están preparados para detectar este escenario y obtener de forma automática los metadatos desde la clase buddy, pero si estamos realizando la validación de forma manual el atributo [MetadataType] no será tenido en cuenta.

Por tanto, debemos ser nosotros los que indiquemos expresamente dónde se encuentran los metadatos, para lo que, afortunadamente, contamos con la ayuda de TypeDescriptor (definida en System.ComponentModel), desde donde podemos indicar el origen de los metadatos de clases simplemente registrando el proveedor desde el cual pueden ser obtenidos.

El procedimiento para conseguirlo es bastante simple: creamos un proveedor de descripciones basado en metadatos utilizando la clase AssociatedMetadataTypeTypeDescriptionProvider (uuf con el nombrecito ;-)) en el que vinculamos la clase “original” con la que contiene los metadatos (la clase buddy), y a continuación añadimos dicho proveedor a la primera.

Por ejemplo, para hacer que las anotaciones de la clase Friend se obtengan desde el tipo FriendMetadata podríamos incluir el siguiente código de inicialización:

var descriptionProvider = new AssociatedMetadataTypeTypeDescriptionProvider(
     typeof(Friend), 
     typeof(FriendMetadata)
);
TypeDescriptor.AddProviderTransparent(descriptionProvider, typeof(Friend));

Otra posibilidad más genérica sería implementarlo como se muestra a continuación, donde buscamos en todo el ensamblado actual clases decoradas con el atributo MetadataType, registrando el proveedor de metadatos indicado en dicho atributo de forma automática:

private static void registerBuddyClasses()
{
    var buddyAssociations = 
        from t in Assembly.GetExecutingAssembly().GetTypes()
        let md = t.GetCustomAttributes(typeof(MetadataTypeAttribute), false)
                .FirstOrDefault() as MetadataTypeAttribute
        where md != null
        select new { Type = t, Buddy = md.MetadataClassType };
 
    foreach (var association in buddyAssociations)
    {
        var descriptionProvider = 
            new AssociatedMetadataTypeTypeDescriptionProvider(
                association.Type, association.Buddy
            );
        TypeDescriptor.AddProviderTransparent(descriptionProvider, association.Type);
    }
}

De esta forma, bastará con invocar el método registerBuddyClasses() durante la inicialización de la aplicación para que las clases buddy sean registradas de forma automática.

Pero más interesante es, sin duda, que podríamos implementar nuevas fórmulas para indicar dónde se encuentran los metadatos de una clase. Por ejemplo, sería realmente sencillo modificar el método anterior para sustituir el atributo MetadataType por una convención de nombrado del tipo “las clases llamadas FooMetadata contendrán los metadatos de las clases de llamadas Foo”:

private static void registerBuddyClassesUsingConventions()
{
    var allAssemblyTypes = Assembly.GetExecutingAssembly().GetTypes().ToList();
    var buddyAssociations =
        from t in allAssemblyTypes
        let buddy = allAssemblyTypes
                    .FirstOrDefault(other => other.Name == t.Name + "Metadata")
        where buddy != null
        select new { Type = t, Buddy = buddy };
 
    foreach (var association in buddyAssociations)
    {
        var descriptionProvider =
            new AssociatedMetadataTypeTypeDescriptionProvider(
                association.Type, association.Buddy
            );
        TypeDescriptor.AddProviderTransparent(descriptionProvider, association.Type);
    }
}

¿Y si quiero usar IValidatableObject?

El interfaz IValidatableObject (definido System.ComponentModel.DataAnnotations) obliga a implementar un único método, llamado Validate(), que retornará una lista de objetos ValidationResult con los resultados de las comprobaciones.

A continuación se muestra un ejemplo de implementación de este interfaz sobre una entidad:

public class Friend : IValidatableObject
{
    public string Name { get; set; }
    public int Age { get; set; }
 
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        if (Name.Equals("albert", StringComparison.CurrentCultureIgnoreCase))
        {
            yield return new ValidationResult("I don't like Alberts!");
        }
    }
}

El método Validate() impuesto por el interfaz será invocado automáticamente por el framework desde el mismo TryValidateObject() siempre que no encuentre errores al comprobar las restricciones especificadas mediante anotaciones. O sea, que sólo se invocará a Validate() cuando no se hayan detectado errores previos de validación.

En cualquier caso, si nos interesa validar de forma manual también estos objetos, siempre podemos hacerlo como sigue:

    private static IEnumerable<ValidationResult> getIValidatableErrors(object obj)
    {
        var validationResults = new List<ValidationResult>();
        var context = new ValidationContext(obj, null, null);
        var validatable = obj as IValidatableObject;
        if(validatable!=null) 
            validationResults.AddRange(validatable.Validate(context));
 
        return validationResults;
    }

De esta forma, podríamos comprobar la ejecución así:

    var friend = new Friend { Age = -1, Name = "albert" };
    var errors = getValidationErrors(friend);
    foreach (var error in errors)
    {
        Console.WriteLine(error.ErrorMessage);
    }
The field Age must be between 0 and 120.

I don’t like Alberts!

En resumen, en este post hemos visto cómo utilizar las herramientas que ofrece el framework .NET para trabajar con validaciones basadas en data annotations de forma manual, lo que abre su ámbito de utilización a prácticamente cualquier tipo de aplicación para este marco de trabajo. Por el camino hemos repasado los mecanismos de anotaciones, y diversos escenarios como la externalización de atributos en clases buddy o el uso de la interfaz IValidatableObject.

Descargar un proyecto VS2010 con el código y pruebas desde Skydrive.

Publicado en Variable not found.

SignalR (II): Conexiones persistentes

Hace poco estuvimos viendo por aquí conceptos básicos sobre SignalR, el componente que nos permite crear espectaculares aplicaciones en las que múltiples usuarios pueden estar colaborando de forma simultánea, asíncrona, y en tiempo real.

Entre otras cosas, comentábamos que SignalR crea una capa de abstracciones sobre una conexión virtual permanente entre cliente y servidor, sobre la que podemos trabajar de diferentes formas:

  • mediante conexiones persistentes, la opción de menor nivel, que proporciona mecanismos de notificación de conexión y desconexión de clientes, así como para recibir y enviar mensajes asíncronos a clientes conectados, tanto de forma individual como colectiva.
  • mediante el uso de “hubs”, que ofrece una interfaz de desarrollo mucho más sencilla, con una integración entre cliente y servidor que parece pura magia, y que seguro será la opción más utilizada por su potencia y facilidad de uso.
En este post estudiaremos la primera opción, conexiones persistentes. Los hubs los veremos en un artículo posterior de la serie, aunque si sois impacientes ya podéis ir leyendo el fantástico post del amigo Marc Rubiño sobre el tema, “Push con SignalR”.

Demo de conexiones persistentesBueno, pues vamos al tema: emplearemos esta vía para implementar una funcionalidad bastante simple, pero nada trivial utilizando las herramientas habituales de ASP.NET: mostrar en una página, en tiempo real, información sobre los usuarios que están llegando a ella, los que la abandonan y el número de usuarios que hay conectados justo en ese momento, en tiempo real.

Para ello haremos lo siguiente:

  1. En el lado servidor, implementaremos un servicio (endpoint) SignalR, que es el que procesará las conexiones y desconexiones de clientes, y enviará información actualizada por las conexiones abiertas.
  2. Registraremos este endpoint durante la inicialización de la aplicación, asociándole una URL de acceso a las funcionalidades del servicio.
  3. En el lado cliente implementaremos la conexión con el servicio, capturaremos la información que nos vaya enviando y la mostraremos en la página en forma de log.
El resultado lucirá tal y como se muestra en la captura de pantalla adjunta. Aunque si lo preferís, podéis verlo en vivo y en directo descargando y ejecutando el proyecto de demostración que encontraréis al final de este artículo.

Ya en el post anterior de la serie vimos cómo descargar e instalar SignalR en un proyecto, así que vamos a suponer que ese paso ya lo hemos realizado previamente.

1. Implementación del endpoint

El endpoint, o servicio SignalR, que vamos a implementar utilizando el enfoque de conexión persistente es simplemente una clase que hereda de SignalR.PersistentConnection, en la que podemos sobrescribir los métodos que necesitemos para implementar nuestras funcionalidades. En ella encontramos métodos como OnConnected(), OnDisconnect(), OnReceived(), y bastantes más, que nos permiten tomar el control cuando se producen determinados eventos de interés en la conexión:
    public class VisitorsService : PersistentConnection
    {
        protected override void  OnConnected(HttpContextBase context, string clientId) { ... }
        protected override void  OnDisconnect(string clientId) { ... }
        protected override void  OnReceived(string clientId, string data) { ... }
        // [...]
    }
Observad que el interfaz es bastante similar a la que encontramos al trabajar directamente con sockets: podemos introducir lógica cuando un nuevo cliente se conecte sobrescribiendo el método OnConnected(), cuando se desconecte, haciendo lo propio con OnDisconnect(), o cuando el cliente envíe algún tipo de mensaje al servidor, que ejecutará la funcionalidad implementada en OnReceived().

De la misma forma, la clase base PersistentConnection ofrece mecanismos para enviar mensajes directos a un cliente, a grupos de ellos, o a todos los clientes conectados.

Volviendo al sistema que estamos desarrollando, básicamente para alcanzar nuestros objetivos necesitamos:

  • tomar el control en el momento en que se produce una nueva conexión (método OnConnected), momento en que enviaremos al resto de clientes un mensaje con información sobre el cliente conectado y el total de conexiones activas.
  • tomar el control en el momento en que se produce la desconexión de un cliente (método OnDisconnect()), para notificar al resto y actualizarles el número de clientes conectados.

1.1. Notificando a los clientes las nuevas conexiones

Cuando se realiza una nueva conexión al servicio, es decir, la llegada de un nuevo cliente, SignalR invocará al método OnConnected() del endpoint suministrándole el contexto de la petición HTTP actual, y un “ClientId”. El primero nos puede ser muy interesante para acceder a información de la petición (como el navegador, IP, cookies, información de autenticación, etc.), y el segundo es un identificador único generado por SignalR para realizar el seguimiento de la conexión.

Implementamos nuestro método y lo comentamos justo a continuación:

    protected override void OnConnected(HttpContextBase context, string clientId)
    {
        var clientDescription = getClientDescription(context);
        _clients.TryAdd(clientId, clientDescription);
 
        string text = clientDescription + " arrived.";
        var msg = new NotificationMessage(text, _clients.Count);
        Connection.Broadcast(msg);
    }
Lo primero que hacemos en la implementación del método es obtener una descripción textual del cliente (que puede ser el nombre del usuario autenticado, o su IP), utilizando el método getClientDescription(), que veremos más adelante. Esta descripción, asociada al ClientId, es almacenada en el diccionario estático _clients, lo que nos permitirá conocer en todo momento los clientes conectados.

Justo después componemos el mensaje y realizamos el envío a todos los usuarios conectados invocando el método Broadcast() de la propiedad de instancia Connection, que nos da acceso al canal virtual abierto entre clientes y servidor. El parámetro que recibe este método es de tipo object, y viajará serializado en formato JSON hasta cada uno de los clientes conectados; en este caso, hemos creado una clase llamada NotificationMessage que contiene toda la información que necesitamos suministrarles:

public class NotificationMessage
{
    public NotificationMessage(string message, int onlineUsers)
    {
        OnlineUsers = onlineUsers;
        Message = message;
    }
 
    public string Date
    {
        get { return System.DateTime.Now.ToLongTimeString(); }
    }
    public string Message { get; set; }
    public int OnlineUsers { get; set; }
}
Es conveniente tener en cuenta, sin embargo, que es posible enviar cualquier tipo de objeto: tipos propios (como en el ejemplo anterior), objetos anónimos, primitivos, o lo que se nos ocurra. Simplemente será serializado como JSON y llegará al cliente de forma directa (más adelante veremos cómo).

Los miembros auxiliares utilizados en el código anterior son los siguientes:

    private static ConcurrentDictionary<string, string> _clients =
        new ConcurrentDictionary<string, string>();
 
    private static string getClientDescription(HttpContextBase context)
    {
        var browser = context.Request.Browser.Browser + " " +
                        context.Request.Browser.Version;
        var name = context.Request.IsAuthenticated ?
                    "User " + context.User.Identity.Name :
                    "IP " + context.Request.UserHostAddress;
        return name + " (" + browser + ")";
    }
Observad que el diccionario donde almacenamos la información sobre las conexiones ha sido definido como ConcurrentDictionary para evitar problemas de concurrencia durante las actualizaciones, y es estático para que su información sea compartida entre todas las instancias del servicio.

1.2. Notificando a los clientes las desconexiones

Cuando SignalR detecta que un cliente se ha desconectado, invocará al método virtual OnDisconnect() del endpoint, lo cual nos permite introducir lógica de gestión del evento. En nuestro caso, simplemente necesitamos eliminar al cliente del diccionario donde los estamos almacenando,

De la misma forma, debemos controlar las desconexiones para notificar este hecho a los clientes aún conectados, para lo que sobrescribimos el método OnDisconnect():

    protected override void OnDisconnect(string clientId)
    {
        string text, clientDescription;
 
        if (_clients.TryRemove(clientId, out clientDescription))
            text = clientDescription + " is leaving.";
        else
            text = "Unknown user leaving.";
 
        var msg = new NotificationMessage(text, _clients.Count);
        Connection.Broadcast(msg);
    }
En este método recibimos el ClientId que SignalR asignó al cliente en el momento de iniciar la conexión; lo único que hacemos es buscarlo en el diccionario de clientes donde los estamos almacenando, eliminarlo, y enviar un mensaje broadcast al resto de usuarios indicando la desconexión que se ha producido.

Cuando implementéis funcionalidades en la desconexión, tened en cuenta que SignalR tarda unos segundos en darse cuenta de las desconexiones (recordad que con el transporte utilizado por defecto se trata de una conexión persistente virtual) por lo que puede aparecer un leve retraso en las notificaciones. Estos tiempos, en cualquier caso, pueden ser configurados (en el proyecto de demostración podéis ver cómo hacerlo).

[Actualización]: como bien indica Arturo en un comentario del post, para que las desconexiones sean notificadas correctamente es necesario utilizar IIS o IIS Express. Con Cassini (el servidor web integrado en VS) no funcionará bien este mecanismo.

1.3. Algunas observaciones adicionales

Al principio de comenzar a jugar con conexiones persistentes de SignalR, una de las cosas que pueden llamar la atención es que si en la implementación del método OnConnected() enviamos un broadcast a todos los usuarios conectados, el usuario actual (el que ha provocado la llamada a OnConnected) no recibirá el mensaje; o en otras palabras, el broadcast llegará a todos los clientes excepto al que acaba de realizar la conexión.

Desconozco si se trata de un comportamiento por diseño, si es algo que se modificará en posteriores revisiones de SignalR (recordemos que en estos momentos es todavía una versión preliminar), o si simplemente se trata de un nombre para el método poco afortunado, pues en mi opinión da a entender que la conexión ya ha sido realizada y, por tanto, el broadcast debería llegarle también.

Pero en cualquier caso, en la implementación del proyecto de pruebas que podéis descargar al final de este post veréis cómo lo he solucionado incluyendo una llamada explícita (“ping”) desde el cliente al servidor para forzar el envío de un mensaje de actualización justo después de completarse la conexión. Conceptualmente, lo que se hace es:

  • desde el cliente, una vez se ha realizado la conexión, realizar un envío de datos al servidor, algo similar a un “ping”,
  • en el método OnReceived() del servidor, capturar el mensaje enviado desde el cliente y responderle de forma directa con la información que nos interese hacerle llegar, que podría ser un mensaje de bienvenida y, como en otras ocasiones, el número de usuarios conectados:
        protected override void OnReceived(string clientId, string data)
        {
            var msg = new NotificationMessage("Hi!", _clients.Count);
            Send(clientId, msg);
        }
Más adelante, cuando tratemos la parte cliente del servicio, veremos cómo está implementado el envío desde el cliente de este “ping”.

2. Registro de ruta

Una vez tenemos el servicio implementado, debemos registrar en el sistema de routing de ASP.NET una URL a través de la cual será posible acceder al mismo. El lugar idóneo para hacerlo, como siempre que se trata de cargar la tabla de rutas, es en el global.asax, para que se ejecute durante la inicialización de la aplicación.

Por ejemplo, en una aplicación ASP.NET MVC podría ser algo así:

    public static void RegisterSignalrConnections(RouteCollection routes)
    {
        routes.MapConnection<VisitorsService>("Visitors", "VisitorsService/{*operation}");
    }
 
    protected void Application_Start()
    {
        RegisterSignalrConnections(RouteTable.Routes);
        [...]
    }
Observad que lo único que estamos haciendo es añadir a la tabla de rutas una entrada en la que asociamos el servicio, en este caso nuestra clase VisitorsService, a la dirección “VisitorsService/{*operation}”, que será la URL de acceso al mismo.

El primer parámetro que enviamos al método MapConnection() es simplemente el nombre de la entrada en la tabla de rutas, no tiene demasiada importancia.

3. Implementación del cliente web

La implementación de clientes web para las conexiones persistentes desarrolladas con SignalR es bastante simple, y comienza incluyendo en la página o vista una referencia hacia la biblioteca cliente de este componente:
<script src="@Url.Content("~/Scripts/jquery.signalR.js")" type="text/javascript"></script>
Como siempre, esta inclusión puede realizarse a nivel de página, o bien en la Master o Layout si queremos aplicarlo a todas las vistas del sistema.

Nota: si queremos dar soporte a clientes antiguos que no soportan deserialización JSON de forma nativa (por ejemplo, IE7), será necesario descargar desde Nuget la biblioteca de scripts json2.js y referenciarla en la página antes de la carga de SignalR.js. En caso contrario, se lanzará una excepción con el error:

“SignalR: No JSON parser found. Please ensure json2.js is referenced before the SignalR.js file if you need to support clients without native JSON parsing support, e.g. IE<8”

Centrándonos en nuestra aplicación, el marcado HTML será tan simple como el que se muestra a continuación, lo único que hacemos es dejar un “hueco” en el que introduciremos los mensajes que se vayan recibiendo del servidor:
<h2>Log</h2>
<div id="log"></div>
A continuación, necesitamos implementar el código de script que realice las siguientes tareas:
  • iniciar la conexión con el endpoint,
  • tras ello, enviar un “ping” para recibir el mensaje de bienvenida (recordad lo que os comentaba previamente de que el broadcast no se recibe por el cliente que inicia la conexión),
  • mostrar en el log la información recibida del servidor.
Y el código de script tampoco puede ser más sencillo:
<script type="text/javascript">
    $(function () {
        var conn = $.connection("VisitorsService");
        conn.received(function (data) {
            var text = data.Date + " - " + data.Message + " " +
                data.OnlineUsers + " users online.";
            
            $('#log').prepend("<div>" + text + "</div>");
        });
 
        conn.start(function () {
            conn.send("ping");
        });
    });
</script>
Lo comentamos muy rápidamente:
  • en la variable conn obtenemos una referencia hacia el endpoint, identificado por el nombre de la conexión persistente, en este caso, VisitorsService.
  • sobre ella, definimos la función received(), que será invocada cuando el servidor envíe información. El parámetro que recibe la función anónima es la información enviada desde el servidor, que, recordaréis, en este caso se trataba en objetos de tipo NotificationMessage. Dado que la serialización y deserialización se realizan de forma automática, podemos acceder directamente a sus miembros, como podéis ver en el código para montar el mensaje e introducirlo en el log.
  • por último, iniciamos la conexión invocando al método start() de la conexión. Observad que este método admite un callback que será llamado cuando la conexión se haya establecido, momento que aprovechamos para enviar el “ping” al servidor que nos permitirá recibir el mensaje de bienvenida.
Y ¡esto es todo!

Si tenéis un ratillo, no dejéis de descargar el proyecto de prueba y jugar un rato con él. Y sobre todo, observad las pocas líneas de código que hemos tenido que emplear para resolver esta funcionalidad y comparadlo con lo que supondría implementarla de forma artesana, con las técnicas tradicionales.

Introducción a SignalR (I): Conceptos básicos

 

Uau!!Una aplicación que mezcla internet, asincronía, y múltiples usuarios colaborando e interactuando al mismo tiempo siempre es merecedora de un “¡uau!”. Seguro que, al igual que un servidor, en algún momento os habéis quedado maravillados con la interactividad que presentan algunos sistemas web modernos, como Facebook, Google Docs, o muchos otros, en las que estamos recibiendo actualizaciones, prácticamente en tiempo real, sin necesidad de recargar la página.

Por ejemplo, en Google Docs, si estamos editando un documento online y otro usuario accede al mismo, podemos ver sobre la marcha que ha entrado, e incluso las modificaciones que va realizando sobre el documento. O algo más cotidiano, en un simple chat vía web van apareciendo los mensajes tecleados por nuestros compañeros de sala como por arte de magia. Ambos sistemas utilizan el mismo tipo de solución: el envío asíncrono de datos entre servidor y clientes en tiempo real.

En esta serie de artículos veremos cómo podemos implementar sorprendentes funcionalidades de este tipo utilizando SignalR, un framework open source desarrollado por gente del equipo de ASP.NET, que nos facilitará bastante la tarea.

¿Cómo puede el servidor enviar eventos al cliente de forma asíncrona?

Crear este tipo de sistemas usando herramientas convencionales nos puede causar algunos dolores de cabeza, principalmente porque los protocolos que sustentan la web están basados en un modelo cliente-servidor síncrono: uno o varios clientes realizan una conexión hacia el servidor y le transmiten una acción a realizar, éste la procesa y les retorna la respuesta, cerrándose la conexión de forma inmediata.

PollingA priori, no hay forma de que el servidor sea el que notifique a los clientes los cambios de estado (por ejemplo, la llegada en un chat de un mensaje procedente de otro usuario), salvo que éstos utilicen un mecanismo de polling, es decir, que estén continuamente estableciendo conexiones con el servidor para ver si hay algún nuevo evento a tener en cuenta.

Aunque válido en determinados escenarios, hay otros en los que se trata de una solución demasiado costosa, sobre todo cuando hay que gestionar un alto número de clientes conectados.

El ideal sería utilizar una conexión persistente, siempre abierta, entre cliente y servidor, que permitiría el envío y recepción de mensajes y eventos de forma bidireccional entre ambos. De esta forma, si el servidor tiene algo que enviar a sus clientes, simplemente tendría que transmitirlo por el canal que mantendría abierto con cada uno de ellos.

Conexión persistenteSin embargo, hasta ahora esto sólo se podía conseguir usando sockets, lo cual, en entorno web, requería la existencia de algún tipo de elemento activo sobre la página (Silverlight, Flash, o applets Java, por ejemplo) capaz de establecer este tipo de comunicaciones.

Afortunadamente, la W3C parece dispuesta a cambiar esta situación al introducir de forma nativa los famosos WebSockets, cuya definición se encuentra todavía en borrador. Esta nueva API permitirá abrir conexiones directas desde el navegador usando Javascript, por lo que podría ayudarnos bastante una vez su implementación sea universal en los agentes de usuario. De momento no es así, aunque ya está disponible en algunos de ellos (podéis ver una demo simple aquí con Chrome).
Logo de HTML5
También existe otra iniciativa de la W3C que podría ayudar a enviar mensajes o eventos desde el servidor a los clientes suscritos, llamada Server-Sent Events. Como en el caso anterior, se encuentra en borrador, aunque ya algunos navegadores lo implementan (podéis ver una demo aquí con Chrome), por lo que todavía no podemos utilizarla de forma segura.

Por esta razón, existen hoy en día múltiples soluciones que permiten solventar las limitaciones del protocolo, como las englobadas bajo la denominación Server push o Comet, aprovechando los recursos existentes en los protocolos utilizados para crear, o al menos simular, este canal abierto continuo entre cliente y servidor utilizando polling, long polling, HTTP streaming, y otros artificios.

Long pollingPor ejemplo, el mecanismo long polling utiliza peticiones HTTP para crear una conexión “pseudopersistente”. El servidor, en lugar de procesar la petición y retornar la respuesta de forma inmediata, espera hasta que haya disponible algún evento o mensaje a enviar al cliente; en este momento, lo retorna como respuesta a la petición original y cierra la conexión. El cliente, por su parte, procesa esta respuesta y realiza inmediatamente después una nueva petición al servidor, que volverá a quedar abierta a la espera de mensajes, y así sucesivamente.

En definitiva, se trata de un mecanismo más limpio y eficiente que el polling, puesto que evita gran cantidad de peticiones absurdas que se producen cuando en el servidor no hay eventos pendientes de notificar. Además, dado que utiliza HTTP estándar, es válida para todo tipo de agentes de usuario, y bastante amigable para proxies, filtros, firewalls y otros inconvenientes que puede haber por el camino entre los dos extremos.

Y en este punto es donde entra en escena SignalR, un conjunto de componentes desarrollados por Damian Edwards y David Fowler, miembros del equipo de ASP.NET en Microsoft, que nos abstrae de los detalles subyacentes y nos ofrece la visión y ventajas de un entorno conectado en el que podemos comunicar cliente y servidor bidireccionalmente, de forma asíncrona, y con una sencillez pasmosa. SignalR nos hace ver como si cliente y servidor estuvieran conectados de forma continua y facilita el envío de mensajes asíncronos bidireccionales entre ambos extremos.

Por último, es importante decir que SignalR no es específico para ASP.NET MVC, ni para WebForms: podemos utilizarlo con cualquier tipo de proyecto web. De hecho, incluso se puede utilizar en otro tipo de proyectos usando un servidor self-hosted 🙂

SignalR, conceptualmente

SignalR ofrece una visión a muy alto nivel de la comunicación entre el servidor y los múltiples clientes que se encuentren a él conectados. Y cuando digo “alto nivel”, creedme que estoy hablando de muchos metros de altura 😉

Como desarrolladores, trabajaremos sobre una conexión virtualmente siempre abierta: en servidor podremos detectar cuándo se ha conectado un nuevo cliente, cuándo se ha desconectado, recibir mensajes de éstos, enviar mensajes a los clientes conectados…, en definitiva, todo lo que podemos necesitar para crear aplicaciones asíncronas multiusuario.

Sin embargo, en realidad estas conexiones persistentes no existen, o no tienen por qué existir. Se trata de una abstracción creada por SignalR, que el que se encargará del trabajo sucio que hay por debajo, manteniendo la conexión de los clientes con el servidor mediante distintos mecanismos denominados “transportes”, que son el conjunto de tecnologías utilizadas para mantener crear la conexión continua, o al menos la ilusión de su existencia.

Lo interesante de los protocolos de transporte es que pueden ser sustituidos de forma transparente sin afectar a nuestras aplicaciones, que trabajarán aisladas de estos detalles. Nuestros sistemas funcionarán exactamente igual sea cual sea el transporte utilizado, lo que permite que éste sea elegido en cada escenario en función de la disponibilidad de las tecnologías en ambos extremos.

Por ejemplo, el transporte Websockets es capaz de crear una conexión con el servidor y mantenerla abierta de forma continua, aunque requiere que esta tecnología esté disponible tanto en el cliente (en el caso de clientes web, es necesario que el navegador implemente Websockets) como en el servidor.

Long polling, el transporte utilizado por defecto en SignalRDebido a ello, y para asegurar la máxima compatibilidad con los clientes, actualmente se utiliza por defecto el transporte denominado Long polling, que ya hemos comentado anteriormente.

Observad que, a pesar de la relativa complejidad que supondría implementar algo así a mano, nosotros no tendremos que hacer nada: SignalR se encarga de llevar a cabo todas estas tareas para ofrecernos la sensación de estar siempre conectados.

Su componente cliente será el encargado de realizar las conexiones, mantenerse a la espera de noticias del servidor, reconectar cuando se reciban eventos o cuando por cualquier otra causa se haya perdido la conectividad, etc., ofreciéndonos una superficie de desarrollo muy simplificada.

El lado servidor de SignalR, por otra parte, será el encargado de recibir la conexión y mantenerla en espera, almacenar los mensajes recibidos, realizar el seguimiento de clientes conectados, enviar mensajes a través de un bus interno, etc., y de la misma forma, ofreciéndonos un API bastante simple para implementar nuestros servicios.

Implementación de servicios con SignalR

SignalR nos ofrece dos fórmulas para trabajar sobre las conexiones que crea con el servidor:

  • usando “conexiones persistentes”, es la de más bajo nivel y proporciona mecanismos simples para registrar conexiones y desconexiones de clientes y comunicarse de forma bidireccional con ellos. De hecho, esta forma de crear servicios es bastante similar a como hacemos utilizando sockets.
  • usando “hubs”, que ofrece una abstracción aún mayor, permitiendo la comunicación entre cliente y servidor de forma casi mágica. Esta es la opción que convendrá utilizar en la mayoría de ocasiones, por la potencia que aporta y su gran comodidad de uso.

En cualquiera de los dos casos, y ya centrándonos en el entorno web más habitual, donde el servidor es una aplicación ASP.NET y los clientes van a ser las páginas o vistas en las que tendremos un motor de scripting, la implementación de servicios consistirá en:

  • en el servidor, crear el servicio (también llamado endpoint) con las funcionalidades que nos interese, utilizando las clases disponibles en el ensamblado SignalR.
  • en cliente, crear el consumidor del servicio utilizando las clases disponibles en la biblioteca de scripts jQuery.SignalR.js (o su correspondiente versión minimizada).

Cada una de las dos fórmulas citadas tiene sus particularidades, por lo que las estudiaremos mediante el desarrollo de ejemplos independientes en futuros posts de la serie.

Pero primero, veamos rápidamente cómo podemos incluir este componente en nuestros proyectos, aunque desde luego más sencillo no puede ser… 😉

Instalación de SignalR

El sitio web oficial del producto (signalr.net), a día de hoy, es una simple redirección hacia Github, donde se encuentra la documentación y el código fuente del proyecto. Aunque podríamos descargarlo desde ahí, la opción más sencilla, como siempre, es utilizar Nuget:

PM> Install-Package signalr
Attempting to resolve dependency 'SignalR.Server (≥ 0.3.5)'.
Attempting to resolve dependency 'Microsoft.Web.Infrastructure (≥ 1.0.0.0)'.
Attempting to resolve dependency 'SignalR.Js (≥ 0.3.5)'.
Attempting to resolve dependency 'jQuery (≥ 1.6)'.
Successfully installed 'Microsoft.Web.Infrastructure 1.0.0.0'.
Successfully installed 'SignalR.Server 0.3.5'.
Successfully installed 'SignalR.Js 0.3.5'.
Successfully installed 'SignalR 0.3.5'.
Successfully added 'Microsoft.Web.Infrastructure 1.0.0.0' to SignalRDemo.
Successfully added 'SignalR.Server 0.3.5' to SignalRDemo.
Successfully added 'SignalR.Js 0.3.5' to SignalRDemo.
Successfully added 'SignalR 0.3.5' to SignalRDemo.

Esta instalación incluye, además de algún elemento infraestructural, dos componentes de SignalR:

  • SignalR.Server, que es la biblioteca de servidor principal para integrar en aplicaciones ASP.NET.
  • SignalR.Js, la biblioteca Javascript necesaria para conectar desde cliente (páginas web) con el servidor.

Existen también otros clientes específicos para .NET, como SignalR.Client (cliente genérico), SignalR.Client.Silverlight (específico para SL), o SignalR.Client.WP7 (específico para Windows Phone 7), que podemos instalar de forma independiente.

Además, tanto en Nuget como en el sitio web del producto podéis encontrar otros paquetes interesantes a los que vale la pena echar un vistazo, como SignalR.Sample, un ejemplo completo de uso de este componente, SignalR.SelfHost, que permite activar el servidor sin usar ASP.NET, o SignalR.Websockets, un adaptador (o transporte, en argot SignalR) para usar Websockets para el mantenimiento de la conexión entre cliente y servidor.

Observaréis que en todos los casos se trata de versiones muy preliminares pero que podemos ir probando y disfrutando desde ya, porque funcionan bastante bien. Podéis comprobarlo accediendo a http://jabbr.net/, un chat implementado sobre SignalR donde podréis encontrar charlando hasta a los mismísimos padres de la criatura. 🙂

En el próximo post veremos cómo implementar clientes y servicios SignalR utilizando conexiones persistentes, el enfoque de menor nivel ofrecido por este fantástico marco de trabajo.

Publicado en: Variable not found.