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.