Glimpse: cómo crear plugins, paso a paso

GlimpseYa estuvimos viendo la herramienta Glimpse, un interesantísimo complemento que nos puede ayudar bastante a depurar nuestras aplicaciones, ofreciéndonos una visión muy completa de lo que ocurre en el servidor desde que recibe una petición hasta que la responde.

Pero además de ser una utilidad imprescindible, una de sus características más interesantes es que puede ser extendido con suma facilidad. En este post vamos a ver cómo crear un plugin sencillo que nos permita mostrar en Glimpse información que nos interese sobre nuestra aplicación.

Y como tiene que ser, lo vamos a ver desarrollando un ejemplo paso a paso.

0. Objetivo

Para no distraernos de la intención de este post, que es ver cómo se crean plugins para Glimpse, vamos a desarrollar una extensión muy sencilla, un visor que nos permita observar información sobre el usuario actualmente conectado.

El plugin, al que llamaremos “Authentication” se instalará en el panel de Glimpse y mostrará información como la siguiente:

  • Nombre de la aplicación
  • Nombre del usuario conectado
  • Email
  • Último login
  • Roles del usuario en el sistema

Plugin Authentication para Glimpse
Para obtener esta información de forma rápida utilizaremos los sistemas integrados de membresía y funciones (membership y roles) de ASP.NET. Obviamente, podéis utilizar los mismos conceptos que vamos a ver para implementar visualizadores más específicos de vuestra aplicación, como objetos de sesión, caché, e incluso información obtenida desde bases de datos 🙂

1. Instalación de Glimpse

Quiero imaginar que todos estáis ya utilizando Nuget a tope (¿verdad?), por lo que no me detendré a explicar cómo instalar esta indispensable herramienta, y comenzaremos directamente instalando Glimpse.

Como ya describí en el otro post, la instalación de Glimpse es trivial utilizando la consola de Nuget:

Each package is licensed to you by its owner. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. 
Some packages may include dependencies which are governed by additional licenses. Follow the package source (feed) URL to determine any dependencies. Package Manager Console Host Version 1.3.20419.9005 Type 'get-help NuGet' to see all available NuGet commands. PM> install-package glimpse [...] Successfully installed 'log4net 1.2.10'. Successfully installed 'NLog 1.0.0.505'. Successfully installed 'Castle.Core 1.2.0'. Successfully installed 'Castle.DynamicProxy 2.2.0'. Successfully installed 'Glimpse 0.80'. Successfully added 'log4net 1.2.10' to DemoGlimpse. Successfully added 'NLog 1.0.0.505' to DemoGlimpse. Successfully added 'Castle.Core 1.2.0' to DemoGlimpse. Successfully added 'Castle.DynamicProxy 2.2.0' to DemoGlimpse. Successfully added 'Glimpse 0.80' to DemoGlimpse. PM>

2. Añadir una referencia al proyecto

Para poder desarrollar nuestro plugin para Glimpse necesitamos añadir al proyecto una referencia al ensamblado System.ComponentModel.Composition. Esto es así en la versión actual (v0.80 beta), no sé si más adelante se suprimirá esta molestia que, en cualquier caso, es bastante leve.

3. Implementar el plugin

Vamos a pasar directamente a crear el plugin, que veréis que es bastante simple. En primer lugar, añadimos al proyecto una clase que implemente el interfaz IGlimpsePlugin, e implementamos los siguientes miembros definidos en el mismo:

  • void SetupInit(HttpApplication application), que contiene código de inicialización que será invocado al cargarse el plugin (una vez por petición), si así se configura expresamente. Por defecto este método ni siquiera será llamado.
  • string name {get; }, que debe retornar el nombre de la pestaña que aparecerá integrada en el interfaz de Glimpse.
  • public object GetData(HttpApplication application), que retornará un objeto cualquiera con la información que debe ser mostrada por Glimpse en nuestra pestaña. Este objeto será serializado como JSON, y el valor de sus propiedades, tenga la estructura que tenga, mostrada en el panel de la herramienta.

Por último, debemos indicar a Glimpse que la clase que hemos creado es un plugin, para lo que la decoramos con el atributo GlimpsePlugin.

La implementación completa del plugin sería la mostrada a continuación.

using System;
using System.Collections.Generic;
using System.Web;
using System.Linq;
using Glimpse.Net.Extensibility;
using System.Web.Security;
 
namespace Prueba.Utils
{
    [GlimpsePlugin()]
    public class AuthenticationPlugin: IGlimpsePlugin
    {
        public object GetData(HttpApplication application)
        {
            if (!application.Context.User.Identity.IsAuthenticated)
                return new { Usuario = "No autenticado" };
 
            MembershipUser usr = Membership.GetUser();
            string[] roles = Roles.GetRolesForUser(usr.UserName);
 
            return new
            {
                ApplicationName = Membership.ApplicationName,
                Username = usr.UserName,
                Email = usr.Email,
                LastLoginDate = usr.LastLoginDate,
                Roles =  roles.Any()? roles: new[] { "Ninguno" }
            };
        }
 
        public string Name
        {
            get { return "Authentication"; }
        }
 
        public void SetupInit(HttpApplication application)
        {
        }
    }
}

Observad que el método principal GetData(), puede retornar cualquier tipo de objeto (incluidos los de tipo anónimo, como es el caso). Además, fijaos que el tipo de retorno es distinto cuando estamos autenticados y cuando no lo estamos; Glimpse simplemente mostrará en el panel correspondiente el objeto que le suministremos, sea del tipo que sea, recorriendo sus propiedades y visualizándolas de la forma apropiada.

4. ¡Lo tenemos!

Voilá, ya tenemos nuestro plugin para Glimpse funcionando y ofreciéndonos la información que necesitamos:

Con usuario no autenticado

Con usuario autenticado

Obviamente el ejemplo es muy simple, pero seguro que sois capaces de imaginar escenarios en los que os puede resultar de gran utilidad: ver el contenido de objetos específicos de vuestra aplicación, mostrar trazas personalizadas, consumo de recursos, observar peticiones Ajax, y un larguísimo etcétera.

Siguiendo el ejemplo anterior e implementando a vuestra conveniencia el método de obtención de datos lo tendréis listo sin apenas esfuerzo.

Publicado en: Variable not found.

Glimpse: simplemente imprescindible

GlimpseTenía ya ganas de echar un vistazo en profundidad a esta herramienta de depuración para ASP.NET MVC y WebForms cuya difusión y número de buenas críticas en las últimas semanas ha sido brutal. Hanselman, Haack, Scott Guthrie, o Brad Wilson son sólo algunos de los que han quedado impresionados con Glimpse y no dudan en calificarla como una utilidad indispensable para los desarrolladores.

Como la definen sus autores, Glimpse es al servidor lo que Firebug al cliente. En la práctica, se trata de un componente que nos permite obtener desde el navegador una visión en profundidad de lo que está ocurriendo en el servidor durante la ejecución de las peticiones.

Brevemente, lo que vamos a conseguir ver con Glimpse es:

  • Información sobre configuración de la aplicación
  • Información sobre el entorno
  • Métodos ejecutados
  • Metadatos detallados de las clases del modelo
  • Información enviada en la petición
  • Tabla de rutas completa
  • Valores retornados en las variables de servidor
  • Información almacenada en variables de sesión
  • Vistas completas y parciales cargadas en la respuesta
  • Peticiones Ajax realizadas desde el cliente

Icono de GlimpseUna vez instalado en nuestra aplicación y activado (más adelante veremos cómo), aparecerá en la esquina inferior de todas nuestras las páginas un icono representando un ojo cuya pulsación abrirá el interfaz de Glimpse, que como se aprecia en la siguiente captura, es bastante similar a Firebug o las developer tools de otros navegadores:

Glimpse en funcionamiento
Observad que Glimpse ocupa toda la zona inferior, y aunque podría parecer lo contrario, es puro HTML y no un complemento del navegador como ocurre con las herramientas anteriormente citadas. Todo lo que vemos es creado por el componente cliente de Glimpse e introducido en la página actual 🙂

Arquitectura

Glimpse está compuesto por los siguientes elementos:

  • Glimpse Server Module, el componente encargado de recolectar información de depuración en servidor.
  • Glimpse Client Side Viewer, encargado de obtener la información de depuración desde el módulo servidor y mostrar el interfaz de usuario que permite su estudio.
  • Glimse Protocol, el protocolo que permite el intercambio de información entre servidor y cliente.

En la actualidad, el componente de servidor exige ASP.NET 4, aunque se está trabajando en una versión compatible con ASP.NET 3.5. Además, el servidor donde se ejecute debe disponer de la versión 3 del framework MVC en el caché de ensamblados para que funcione, independientemente de si queremos utilizarlo para depurar aplicaciones MVC o Webforms, debido a una serie de dependencias que serán eliminadas en próximas versiones de la herramienta.

Sin embargo, el objetivo final de Glimpse es bastante más ambicioso: aunque la implementación actual está muy ligada a la tecnología .NET, su arquitectura modular hará posible la aparición de componentes de servidor para cualquier tecnología, como PHP o RoR. Éstos se comunicarán con el lado cliente mediante Glimpse Protocol, que es ajeno a la tecnología utilizada en servidor.

El componente de cliente, por su parte, es capaz de obtener datos de depuración generados en servidor y representados en Glimpse Protocol, y mostrarlo integrado en la página. Es importante saber que este cliente está construido sobre jQuery, por lo que la página donde vayamos a utilizarlo debe cargar esta biblioteca de scripts previamente para que todo funcione correctamente.

El diseño del visor se ha conceptualizado, además, para que sea extensible: será posible añadir plugins que den soporte a características avanzadas, como pestañas para mostrar información específica de plataformas o CMS como Sharepoint u Orchad, autenticación para acceder a la información de depuración, etc.

Instalación y puesta en marcha de Glimpse

Este apartado, que describe la instalación de Glimpse en nuestro proyecto ASP.NET MVC o Webforms, podría ser realmente extenso de no ser por Nuget. Glimpse depende de otros paquetes como Castle.Core, Castle.DynamicProxy o Log4net y la descarga e instalación manual de estos componentes podría acabar con la paciencia de cualquiera. Afortunadamente esta joya nos va a poner la tarea bastante fácil 🙂

Desde la consola Nuget, accesible a través del menú Herramientas > Library Package Manager > Package Manager Console, podemos dejarlo listo en segundos simplemente introduciendo el comando install-package como sigue:

Each package is licensed to you by its owner. Microsoft is not responsible for, nor does it grant any licenses to, third-party packages. Some packages may include dependencies which are governed by additional licenses. Follow the package source (feed) URL to determine any dependencies.
Package Manager Console Host Version 1.3.20419.9005
Type 'get-help NuGet' to see all available NuGet commands.

PM> install-package glimpse
[...]
Successfully installed 'log4net 1.2.10'.
Successfully installed 'NLog 1.0.0.505'.
Successfully installed 'Castle.Core 1.2.0'.
Successfully installed 'Castle.DynamicProxy 2.2.0'.
Successfully installed 'Glimpse 0.80'.
Successfully added 'log4net 1.2.10' to DemoGlimpse.
Successfully added 'NLog 1.0.0.505' to DemoGlimpse.
Successfully added 'Castle.Core 1.2.0' to DemoGlimpse.
Successfully added 'Castle.DynamicProxy 2.2.0' to DemoGlimpse.
Successfully added 'Glimpse 0.80' to DemoGlimpse.

PM> 

Tras introducir la orden de instalación, Nuget descargará e instalará el paquete Glimpse, junto con todas sus dependencias, en nuestro proyecto.

El siguiente paso es acudir a la pantalla de configuración de Glimpse con objeto de activarlo para nuestro sitio web. Para ello, simplemente tenemos que lanzar nuestro proyecto y acudir a la dirección /glimpse/config, donde encontraremos el botón de activación “Turn Glimpse On”:

Activación de Glimpse

imageA partir de ese momento, Glimpse estará activo en nuestro proyecto, y podremos comenzar a depurar nuestro sitio Web. Como comentaba al principio, pulsando el icono que aparecerá en la esquina inferior izquierda de todas las páginas, accederemos al visor de información de depuración.

Como en el caso de Firebug, Developer Tools de IE y otros, esta información aparece dividida en pestañas, como se puede apreciar en la siguiente captura de pantalla:

Pestañas de Glimpse

A continuación se describe lo que podemos encontrar en cada una de estas pestañas, exceptuando “Binding”, que aún no está implementada, y “Glimpse warnings”, que muestra avisos internos de la propia herramienta.

Pestaña “Config”

En esta pestaña tendremos acceso a la configuración de la aplicación, o en otras palabras, obtendremos una vista estructurada de determinados parámetros indicados en el web.config. Así, tendremos acceso detallado a parámetros definidos en la sección appSettings, cadenas de conexión, configuración del elemento customErrors y al modo de autenticación utilizado en el sitio.

Pestaña "Config"

Pestaña “Environment”

Aquí podemos consultar diversos datos sobre el entorno en el que se está ejecutando nuestra aplicación, como el nombre de la máquina, sistema operativo, versión del framework, servidor web, ensamblados cargados, etc.

Pestaña "Entorno"

Pestaña “Execution”

Esta interesante pestaña nos muestra información sobre los métodos que han sido ejecutados para procesar la petición, tanto en el controlador como en los filtros aplicables a la acción. Curiosamente, también encontraremos en esta pestaña los métodos no ejecutados en éstos.

image
Como se puede observar en la captura, también ofrece información sobre el orden de ejecución y el tiempo de proceso consumido por de cada uno de estos métodos, lo que nos puede ayudar a detectar cuellos de botella.

Pestaña “Metadata”

Facilita el análisis de los metadatos asociados a la información suministrada desde el controlador a la vista a través de la propiedad Model. En ellos veremos, con todo lujo de detalles y por cada propiedad las descripciones textuales, reglas básicas de validación y formato, el tipo de datos, etc.

Pestaña "Metadata"

Pestaña “Remote”

Aunque no parece funcionar de forma correcta, o al menos no de la forma en que uno podría esperar, esta pestaña permite acceder a las peticiones realizadas por clientes del sitio web, y lanzarlas de nuevo para observar su información de depuración a través del resto de pestañas.

Pestaña "Remote"

Pestaña “Request”

Permite observar los datos enviados en la petición actual en cookies, como campos de formulario, o en la querystring.

Pestaña "Request"

Pestaña “Routes”

Para mi gusto, una de las pestañas más interesantes que ofrece Glimpse. Nos permite consultar la tabla de rutas completa, conocer cuál de las rutas ha sido elegida para procesar la petición, y los valores para cada uno de sus parámetros.

Pestaña "Routing"
Si ya habéis estado trabajando con la última versión de RouteDebugger de Phil Haack, esta herramienta os sonará bastante, pues se parece bastante. Si todavía no lo habéis hecho, no dejéis de probarla, pues os ayudará mucho cuando os encontréis con peticiones que acaban siendo rutadas aparentemente de forma incomprensible.

Pestaña “Server”

En esta sección tendremos acceso a las variables HTTP enviadas en la respuesta del servidor.

Pestaña "Server"

Pestaña “Session”

Aquí se nos ofrece información muy interesante: los datos almacenados en variables de sesión para el usuario actual. Podremos ver la clave de búsqueda en la colección Session, el valor almacenado y el tipo de éste.

Pestaña "Server"

Pestaña “Trace”

En esta pestaña tendremos acceso a la información de depuración generada por la propia aplicación utilizando los métodos de trazas disponibles en System.Diagnostics. Es decir, podemos llamar desde desde el código al método Trace.WriteLine() para escribir mensajes de depuración que después consultaremos desde este punto.
Pestaña "Trace"

Pestaña “Views”

Esta pestaña nos permite, en primer lugar, consultar información sobre el proceso de localización de las vistas, tanto completas como parciales, por parte de los distintos view engines registrados en el sistema. Como se puede observar en la siguiente captura de pantalla, las dos líneas destacadas con las vistas utilizadas para componer la página actual, el resto son los intentos de localización que se han realizado de forma infructuosa.

Pestaña "Views"

Además, en cada una de las vistas procesadas tendremos acceso a la información que le está siendo suministrada desde el controlador a través de los diccionarios ViewData y TempData, y de la propiedad Model:

Pestaña "Views"

Pestaña “XHRequests”

En ella podremos consultar en tiempo real las peticiones Ajax que están siendo realizadas. Pulsando el enlace “launch” asociado a cada una de ellas, podemos acceder a la información de depuración del proceso de dicha petición accediendo a la pestaña que nos interese.

Pestaña "XHRequests"

Una curiosidad: si en esta pestaña lo que aparecen son las peticiones Ajax, ¿por qué la han llamado “XHRequest”, y no “Ajax”? La respuesta la encontramos en un twit de uno de sus creadores: en la versión actual del cliente las pestañas salen ordenadas alfabéticamente, y no quería que ésta fuera la primera 😀

Glimpse plugins

Aunque aún es pronto (recordad que Glimpse es todavía una beta), la arquitectura pluggable del producto hará posible la aparición de extensiones del producto que nos permitirán obtener más datos de depuración, y adaptarlo a plataformas específicas.

Un ejemplo lo podemos encontrar ya en “Glimpse Dependencies Plugin”, un componente que podemos instalar directamente desde Nuget (“install-package glimpse-dependencies”). Se trata de una extensión que añade una pestaña adicional en el cliente, que ofrece información sobre el proceso de resolución de dependencias que utilizan el sistema incorporado en MVC 3:

Plugins para glimpse

Desinstalación de Glimpse

Una vez hemos acabado con Glimpse, podemos desinstalarlo con total sencillez, de la misma forma que lo instalamos. Utilizaremos esta vez el comando uninstall-package, e indicaremos que deseamos eliminar también las dependencias que instalamos con él en su momento:

PM> Uninstall-Package -RemoveDependencies glimpse
Successfully removed 'Glimpse 0.80' from WebSCCB.
Successfully removed 'Castle.DynamicProxy 2.2.0' from WebSCCB.
Successfully removed 'Castle.Core 1.2.0' from WebSCCB.
Successfully removed 'NLog 1.0.0.505' from WebSCCB.
Successfully removed 'log4net 1.2.10' from WebSCCB.
Successfully uninstalled 'Glimpse 0.80'.
Successfully uninstalled 'Castle.DynamicProxy 2.2.0'.
Successfully uninstalled 'Castle.Core 1.2.0'.
Successfully uninstalled 'NLog 1.0.0.505'.
Successfully uninstalled 'log4net 1.2.10'.

PM>

Tras esta operación, nuestro proyecto volverá a estar igual que como lo teníamos. No me canso de decirlo: Nuget es una maravilla 🙂

Conclusión

Sin duda, Glimpse es una herramienta realmente interesante para los que trabajamos con ASP.NET MVC y, aunque actualmente en menor medida, con Webforms, que nos ofrecerá una perspectiva de lo que ocurre en el servidor hasta ahora imposible de obtener de forma tan sencilla, y que nos puede ayudar bastante depurar nuestros sistemas.

La facilidad y limpieza con la que la instalamos y desinstalamos en nuestros proyectos gracias a Nuget hace que sea realmente cómodo contar con ella cuando la necesitemos y volver al estado original cuando ya no sea necesaria.

Por poner alguna pega, aún se trata de un producto en beta, y que aún no implementa todas las funcionalidades. También se echa en falta algo de documentación que ayude a averiguar el significado de determinadas opciones, pero entiendo que esto llegará conforme vaya madurando.

En fin: descargadlo, probadlo, y veréis como os gusta.

Publicado en: Variable not found.

ASP.NET MVC: EditorFor para propiedades anulables

ASP.NET MVCHace unos días me llegaba, vía formulario de contacto del blog, una consulta cuya respuesta pienso que puede resultar interesante para alguien más, así que ahí va.

El problema con el que se encuentra el amigo F. H. es algo con lo que ya me topé hace tiempo al intentar utilizar el helper EditorFor() con propiedades anulables.

Para los que todavía no lo habéis utilizado, comentar que el helper EditorFor() de ASP.NET MVC es una ayuda imprescindible para crear de forma muy rápida potentes interfaces de introducción de datos. Por ejemplo, hace tiempo vimos cómo podíamos utilizarlo para crear un editor de propiedades de tipo fecha usando jQuery datepicker, y que quedaba la mar de bien 😉

Plantillas de edición en ASP.NET MVCSu uso es muy sencillo. Si tenemos una propiedad del modelo, por ejemplo Edad, de tipo int, podemos desde la vista generar un editor apropiado para ella utilizando la expresión:

    @Html.EditorFor(model => model.Edad)

En tiempo de ejecución, el framework buscará en la carpeta /Views/Shared/EditorTemplates si existe una vista parcial con el nombre del tipo (int32 en este caso, que es el nombre real del tipo en la plataforma .NET), y la mostrará para que el usuario pueda editar el valor de esta propiedad.

El siguiente código podría ser una plantilla de edición válida para el ejemplo anterior, el contenido que podríamos encontrar dentro del archivo int32.cshtml:

@model int
@Html.TextBox("", 
            Model, 
            new { maxlength=8, style="width: 100px; text-align: right;"  }
)

Como se puede observar, simplemente estamos creando un cuadro de texto, con un estilo determinado y una capacidad máxima de 8 caracteres.

Pues bien, y volviendo al tema del post, el problema que comentaba este amigo de Variable not found es que cuando, en lugar de tratarse de un tipo valor puro (como int32, DateTime, double, etc.) se trata de un tipo anulable (int?, DateTime?, double?…) todo va bien mientras el contenido que queremos editar no sea un nulo, momento en que aparecerá el siguiente error:

El modelo pasado al diccionario es NULL, pero este diccionario requiere un elemento de modelo distinto de NULL de tipo ‘System.Int32’.

El modelo pasado al diccionario es NULL, pero este diccionario requiere un elemento de modelo distinto de NULL de tipo ‘System.Int32’.

Básicamente, lo que nos indica el error es que en la plantilla de edición estamos esperando un int, como hemos indicado con la directiva @model de Razor, y estamos recibiendo un valor nulo, lo cual es absolutamente incompatible.

Aunque a priori parece que el problema se podría solucionar creando en la carpeta EditorTemplates otra plantilla exclusiva para el tipo anulable int32? , rápidamente nos damos cuenta de que esto no sería una tarea sencilla. El nombre interno de los tipos anulables, como de cualquier otro tipo genérico, está bastante lejos de ser manejable e intuitivo… no, la solución es mucho más simple.

ASP.NET MVC utiliza la misma plantilla de edición para los tipos anulables y sus tipos valor correspondiente. Así, la vista int32.cshtml será utilizada tanto para editar las propiedades de tipo int como las int?. Esto hace que la solución al problema sea, simplemente, modificar ésta para que acepte el tipo correcto de datos, y gestionar los nulos de la forma que más nos interese, por ejemplo mostrando un cero como valor por defecto en el cuadro de edición, como en el siguiente código:

@model int?
@Html.TextBox("", 
            Model ?? 0, 
            new { maxlength=8, style="width: 100px; text-align: right;"  }
)

¡Y eso es todo! Espero que la duda quede resuelta, y que pueda ser de utilidad a alguien más 🙂

Publicado originalmente en http://www.variablenotfound.com/2011/05/aspnet-mvc-editorfor-para-propiedades.html

ASP.NET MVC 3 y la coma en los decimales

ASP.NET MVCSi a día de hoy hay algo fatalmente poco resuelto en ASP.NET MVC, es sin duda la localización. Aunque con un poco de paciencia se pueden poner en marcha sistemas completamente adaptados a distintos idiomas y culturas, la verdad es que se echa en falta una mayor consideración, entre otros, con los que osamos utilizar la coma para separar la parte entera de la decimal en un número.

Hace tiempo traté el tema por aquí, y aporté una solución para la versión 2 de ASP.NET MVC, que aún utilizaba las bibliotecas de scripting de Microsoft Ajax. Sin embargo, la versión 3 ha sustituido “de serie” esos componentes por jQuery Validate y el magnífico sistema de validaciones no intrusivas, por lo que todo lo dicho en aquella ocasión no vale ya para nada 🙁

Validación decimal incorrectaEl problema radica en que el plugin jQuery Validate utiliza únicamente el punto como separador de decimales, por lo que la validación en cliente de valores de tipo decimal, float o double que utilicen la coma finalizará siempre en un error e impedirá el envío del formulario, como puede observarse en la captura de pantalla de la derecha.

Por cierto, antes de que se me olvide, hace unos meses reportaron este asunto como bug en Microsoft Connect. Si el tema os preocupa, podéis ir y votarlo a ver si conseguimos que este asunto se tenga en cuenta en próximas revisiones.

Sin embargo, estrictamente hablando, no se trata de un bug de ASP.NET MVC, puesto que la validación en cliente ha sido delegada por completo al plugin de jQuery, y éste es el que no tiene en cuenta los aspectos de internacionalización. Desde este punto de vista, quizás tendría más sentido, por tanto, esta issue reportada en Github sobre jQuery Validate, que propone su integración de forma nativa con jQuery Global.

Por tanto, me temo que se trata de un asunto de responsabilidad compartida (y dispersa, por tanto) entre los equipos de MVC, de jQuery Validate, y no sé si de alguno más. Esperemos que entre todos puedan solucionar de forma razonable el problema.

En cualquier caso, los que ya estamos creando aplicaciones con MVC 3 no podemos esperar las soluciones oficiales, que seguro llegarán más tarde o más temprano, y nos vemos obligados a buscar alternativas que nos permitan convivir con este problema de la forma más cómoda posible.

Y esto es lo que veremos en este post: varias posibilidades que tenemos para que la validación en cliente de valores decimales no nos compliquen demasiado la vida. Seguro que hay más, seguro que las hay mejores, pero ahí van unas cuantas opciones que nos pueden ayudar en escenarios como el descrito anteriormente.

1. Desactivar la validación en cliente

Está claro que el problema es en cliente, por lo que si desactivamos estas validaciones y dejamos que sea el servidor el que se encargue de comprobar que los valores de los distintos campos cumplen las restricciones impuestas por su tipo y las anotaciones de datos, ya no nos afectará más la absoluta indiferencia de jQuery Validate hacia las particularidades culturales.

Esto podemos conseguirlo de varias formas:

  • desactivar la validación en cliente de forma global, estableciendo a false la propiedad clientValidationEnabled en el web.config, lo cual dejará a toda la aplicación sin validaciones en cliente. Como solución es algo drástica, pero poderse se puede.
  • desactivar la validación en cliente de forma local, sólo en aquellos formularios en los que existan propiedades de tipo decimal, introduciendo el siguiente código Razor (o su correspondiente en ASPX) antes de la llamada a BeginForm():
    @{    
       Html.EnableClientValidation(false);
    }
    
  • desactivar la validación en cliente sólo en el campo que nos interese, que podemos conseguir introduciendo el siguiente script, imaginando que el campo decimal en el que queremos anular la validación en cliente tiene como identificador “Altura”:
    <script type="text/javascript">
       $("#Altura").removeAttr("data-val");
    </script>

Aunque podemos utilizar cualquiera de estas tres opciones,sin duda la menos violenta es la última, pues permite disfrutar de las validaciones en cliente y que sólo sean ignorados los campos conflictivos.

2. Modificar jQuery Validate

Esta es una solución algo bestia que he encontrado por ahí, pero soluciona el problema de un plumazo: modificar el código de jQuery Validate para que acepte comas en lugar de puntos para separar los dígitos decimales de los enteros tanto en la validación numérica como en los rangos.

En el blog de Lenard Gunda podéis encontrar de forma muy detallada los cambios a realizar al archivo jquery.validate.js (o a su versión minimizada). Hay, sin embargo, un par de detalles que debemos tener en cuenta si optamos por esta solución:

  • primero, que nos estamos separando de la distribución oficial del plugin. Si actualizamos la biblioteca jquery.validate, por ejemplo utilizando Nuget, volveremos a tenerlo todo como al principio, y tendremos que volver a introducir los cambios oportunos.
  • segundo, que esto no nos ayudará en aplicaciones adaptadas a varios idiomas; si modificamos el plugin para que acepte comas como separador, ya no volverá a aceptar el punto. Una solución rápida que se me ocurre para esto es tener dos versiones de la biblioteca (la original y la modificada), y referenciar desde la página la apropiada para la cultura actual.

3. Modificar la forma en que jQuery Validate parsea los decimales

Afortunadamente, el plugin de validación para jQuery es muy flexible, y permite introducir código personalizado para la validación de formato numérico y comprobación de rangos, lo que nos brinda la posibilidad de solucionar nuestro problema de forma muy limpia.

El siguiente código sería una primera aproximación a la solución del problema. Como podéis observar, simplemente introducimos en $.validator.methods.number y $.validator.methods.range las funciones que queremos utilizar para validar respectivamente los números y los rangos, reemplazando la coma por el punto antes de realizar la conversión con parseFloat():

<script type="text/javascript">
    $.validator.methods.number = function (value, element) {
        value = floatValue(value);
        return this.optional(element) || !isNaN(value);
    }
    $.validator.methods.range = function (value, element, param) {
        value = floatValue(value);
        return this.optional(element) || (value >= param[0] && value <= param[1]);
    }
 
    function floatValue(value) {
        return parseFloat(value.replace(",", "."));
    }  
</script>

Si incluimos este script en la página cuando la cultura activa sea la nuestra (o cualquier otra que también utilice la coma para separar decimales), tendremos el problema solucionado.

Una fórmula más elegante y universal sería modificar la función floatValue(), y en lugar de reemplazar de forma manual los caracteres, utilizar el plugin Global para realizar la conversión a flotante según la cultura actual. Los detalles de esto, sin embargo, los dejo para otro post.

En fin, que como habréis comprobado existen mil y un enfoques posibles para enfrentarnos al problema. Espero que las ideas que hemos ido comentando os sean de utilidad para implementar vuestras propias soluciones hasta que tengamos una vía “oficial” para conseguirlo.

Publicado en: Variable not found.

ASP.NET MVC: Nombres de acciones y controladores más amigables

La ruta por defecto de ASP.NET MVC es válida para la gran mayoría de escenarios simples, permitiéndonos acceder a las acciones a través de URLs del tipo

http://{servidor:puerto}/{controlador}/{accion}

Así, dada una clase controlador con acciones como las siguientes:

public class InformacionCorporativaController : Controller
{
    public ActionResult QuienesSomos()
    {
        return View();
    }
    public ActionResult MisionVisionYValores()
    {
        return View();
    }
    public ActionResult UneteANuestroEquipo()
    {
        return View();
    }
}

… podríamos acceder a ellas utilizando direcciones tan naturales como /InformacionCorporativa/QuienesSomos, o /InformacionCorporativa/MisionVisionYValores. Sin duda, un gran avance vistas a ofrecer a nuestros usuarios un esquema de rutas limpio, intuitivo, y de paso, mejorar nuestro posicionamiento.

Sin embargo, ¿no os parece que sería mejor aún poder acceder a acciones y controladores utilizando guiones para separar los distintos términos? Me refiero a poder utilizar, por ejemplo, la URL /Informacion-corporativa/Quienes-Somos, o /Informacion-corporativa/Unete-a-nuestro-equipo.

Obviamente no podemos modificar el nombre de nuestros controladores y acciones, puesto que nuestros lenguajes de programación no permiten el uso de guiones en el nombre de los identificadores.

Ante este escenario, una posibilidad sería asignar mediante el atributo [ActionName] un nombre alternativo a cada una de estas acciones. Sin embargo, además del trabajo que esto implica, no habría una forma sencilla de conseguirlo también para los nombres de las clases controlador. Otra opción sería registrar en la tabla de rutas una entrada específica para cada una de las acciones. Aunque no tiene contraindicaciones y cubriría perfectamente nuestras necesidades, uuffff… sin duda es demasiado trabajo a estas alturas.

Afortunadamente, existen en el framework bastantes puntos de extensión donde podemos “enganchar” nuestra propia lógica al proceso de las peticiones, y emplearlos para conseguir solucionar de forma global problemas como este. Y uno de ellos es el sistema de routing.

1. ¡Convenciones al poder!

A continuación vamos a ver cómo conseguirlo de forma global utilizando el sistema de routing, y basándonos en una convención muy simple que vamos a establecer: los identificadores de controladores y acciones que contengan un guión bajo (“_”) serán convertidos a nivel de rutas en guiones.

Dado que el guión bajo es perfectamente válido en los nombre de clases y métodos, sólo tendremos que introducir en ellos este carácter en el lugar donde queremos que aparezcan los guiones, y dejar que el sistema de rutado se encargue de realizar la transformación de forma automática. Y, por supuesto, realizaremos esta transformación en sentido bidireccional, es decir, tanto cuando se analiza la URL para determinar el controlador y acción a ejecutar, como cuando se genera la dirección a partir de los parámetros de ruta.

2. La clase FriendlyRoute

A continuación, vamos a crear una nueva clase, a la que llamaremos FriendlyRoute, y que dotaremos de la lógica de transformación de guiones medios en guiones bajos, y viceversa. Nada que no podamos resolver en una escasa veintena de líneas de código:

public class FriendlyRoute : Route
{
    public FriendlyRoute(string url, object defaults) :
        base(url, new RouteValueDictionary(defaults), new MvcRouteHandler()) { }
 
    public override RouteData GetRouteData(HttpContextBase httpContext)
    {
        var routeData = base.GetRouteData(httpContext);
        RouteValueDictionary values = routeData.Values;
        values["controller"] = (values["controller"] as string).Replace("-", "_");
        values["action"] = (values["action"] as string).Replace("-", "_");
        return routeData;
    }
 
    public override VirtualPathData GetVirtualPath(RequestContext ctx, RouteValueDictionary values)
    {
        values["controller"] = (values["controller"] as string).Replace("_", "-").ToLower();
        values["action"] = (values["action"] as string).Replace("_", "-").ToLower();
        return base.GetVirtualPath(ctx, values);
    }
}

Del código anterior, destacar algunos aspectos:

  • sólo he creado un constructor, he de decirlo, por pura pereza. En realidad, si quisiéramos cubrir más casuística, como la introducción de restricciones o de espacios de nombre que sí permiten las distintas sobrecargas de la clase Route, deberíamos crear todas ellas para nuestra clase. Pero vaya, es trivial en cualquier caso.
  • el método GetRouteData() es utilizado por el framework para obtener los datos de ruta de una petición entrante. Como puede observarse en el código anterior, simplemente ejecutamos la lógica de la clase base, y aplicamos las oportunas transformaciones (de guiones medios a bajos).
  • el método GetVirtualPath() es utilizado para la transformación inversa, es decir, para generar una URL partiendo de valores de los parámetros de ruta. En este caso, primero retocamos ligeramente los valores del nombre de controlador y acción, transformando los guiones bajos en medios y pasándolos a minúsculas, y posteriormente invocamos a la lógica de la clase base para que genere la URL por nosotros.

A partir de este momento ya podríamos añadir en la tabla de rutas objetos de esta clase con un código como el siguiente:

routes.Add("default", 
    new FriendlyRoute(
        "{controller}/{action}/{id}", // URL con parámetros
        new { controller = "inicio", action = "portada_principal", id = UrlParameter.Optional }
    )
);

Observad que una de las ventajas de utilizar esta técnica es que a nivel de código utilizaremos siempre el nombre real de los controladores y acciones (con el guión bajo), es el routing el que realizará las transformaciones. Esto, entre otros beneficios, nos permite seguir utilizando el imprescindible T4MVC para evitar las “magic strings” en nuestro código.

3. Pero es queeee… yo estoy acostumbrado a usar routes.MapRoute()… O:-)

No pasa nada, también podemos ponértelo así de fácil. Los métodos MapRoute() que solemos usar en el método RegisterRoutes del global.asax no son más que extensores de la clase RouteCollection, por lo que podemos basarnos en esta misma idea y crear extensiones personalizadas que nos pongan más a mano el mapeo de nuestras amigables rutas:

public static class RouteCollectionExtensions
{
    public static Route MapFriendlyRoute(this RouteCollection routes, string name, string url, object defaults)
    {
        var route = new FriendlyRoute(url, defaults);
        routes.Add(name, route);
        return route;
    }
}

De esta forma, ahora bastaría con referenciar desde el global.asax el espacio de nombres donde hemos definido la clase anterior y ya podemos llenar la tabla de rutas utilizando sentencias como la siguiente:

routes.MapFriendlyRoute(
    "Default",                    // Nombre de ruta
    "{controller}/{action}/{id}", // URL con parámetros
    new { controller = "inicio", action = "portada_principal", id = UrlParameter.Optional } 
);

Así, una vez introducida la ruta anterior en lugar de la que viene por defecto en proyectos ASP.NET MVC, ya nuestro sistema podrá recibir una petición como GET /Nombre-Controlador/Nombre-Accion y mapearla hacia a la acción Nombre_Accion de la clase controlador Nombre_ControladorController.

Por si os interesa, he dejado código y demo en SkyDrive.

Friendly Route Demo

Publicado originalmente en: http://www.variablenotfound.com/2011/04/aspnet-mvc-nombres-de-acciones-y.html

Retornar vistas dependiendo de la cultura actual en ASP.NET MVC

ASP.NET MVCASP.NET MVC utiliza los mismos mecanismos de ASP.NET para la implementación de sitios web localizados, por lo que podemos utilizar los clásicos recursos definidos en la carpeta App_GlobalResources para ir componiendo los interfaces.

De esta forma, los literales de texto de las vistas son sustituidos por expresiones que, ya en tiempo de ejecución, son tomadas del archivo de recursos correspondiente al idioma actual:

image
Sin embargo, recientemente me he encontrado con un escenario en el que, para facilitar el mantenimiento de los contenidos de las vistas, me resultaba mucho más cómodo disponer de versiones distintas de las mismas en sus correspondientes archivos Razor (.cshtml), y seleccionar en tiempo de ejecución cuál de ellas debía ser enviada en función de la cultura del usuario. Eso sí, en caso de no existir una vista específica para la cultura actual, era necesario retornarla en el idioma por defecto.

imageLa idea, como se muestra en la captura de pantalla de la derecha, es modificar ligeramente la convención de nombrado de archivos de vistas del framework para incluir el código del idioma en que está escrito su contenido.

Así, en este caso, asumiremos que las vistas localizadas se llamarán siempre de la forma {vista}_{idioma}. Por ejemplo, la traducción al inglés de la vista “about” estará definida en el archivo “about_en.cshtml” (usando Razor), al francés en “about_fr.cshtml”, y así sucesivamente. Por simplificar, utilizaremos sólo los dos primeros caracteres del idioma, aunque podrían ser referencias culturales completas, como “about_es-es.cshtml”.

Una vez definida la nueva convención, sólo nos falta implementar en el controlador el mecanismo de selección de vistas en función de la cultura actual. En un primer acercamiento, podríamos implementarla directamente sobre las acciones, como en el siguiente ejemplo:

public ActionResult About()
{
    string currentLang = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
    var viewResult = ViewEngines.Engines.FindView
                        (ControllerContext, "About_" + currentLang, null);

    if (viewResult.View == null)
        return View("About_es");
 
    return View("About_" + currentLang);
}

Aunque funcionalmente es correcto, es fácil ver que la introducción de esta lógica contaminaría bastante el código del controlador, además de atentar contra el principio DRY en cuanto tuviéramos varias acciones en las que introducirlo. Obviamente, debemos encontrar otra solución más apropiada y elegante a esta cuestión.

Y como siempre, en ASP.NET MVC existen multitud de fórmulas para resolver problemas de este tipo. En este post vamos a ver dos de ellas: extendiendo el conjunto de ActionResults, y mediante filtros personalizados.

Usando ActionResults

Empezando por el final, el resultado que vamos a lograr es el que podemos ver en el siguiente código de controlador:

public ActionResult About()
{
return LocalizedView();
}

LocalizedView() no es sino un método rápido para instanciar un LocalizedViewResult, cuya implementación veremos a continuación. La lógica que incluye utilizará los motores de vistas para buscar una vista compuesta por el nombre indicado como parámetro (por defecto el nombre de la acción actual) y la cultura establecida en el hilo de ejecución; de no encontrarse, se mostrará la vista correspondiente a la cultura por defecto.

El código de este ActionResult es el siguiente:

public class LocalizedViewResult : ViewResult
{
private string _defaultCulture;

public LocalizedViewResult(string defaultCulture)
{
_defaultCulture = defaultCulture;
}

public override void ExecuteResult(ControllerContext context)
{
string currentCulture = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;

if (string.IsNullOrWhiteSpace(ViewName))
{
ViewName = context.RouteData.GetRequiredString(«action»);
}

var viewResult = ViewEngines.Engines.FindView(context, this.ViewName + «_» + currentCulture, null);

if (viewResult.View == null)
ViewName += «_» + _defaultCulture;
else
ViewName += «_» + currentCulture;

base.ExecuteResult(context);
}
}

Como se puede observar, el método ExecuteResult() obtiene el idioma actual y comprueba si existe una vista que siga la convención de nombrado que hemos definido previamente (vista_idioma.cshtml). Si se encuentra, se anexa al nombre original de la vista el sufijo del idioma; en caso contrario se actúa de la misma forma, pero utilizando el nombre por defecto.

Por último, para hacer más rápido su uso desde las acciones, necesitamos implementar el método LocalizedView() en una clase base de la que deberíamos heredar nuestros controladores:

public class ControladorBase: Controller
{
public LocalizedViewResult LocalizedView(string viewName = null, object model = null,
string master = null, string defaultLang = «es»)
{
if (model != null)
{
ViewData.Model = model;
}

return new LocalizedViewResult(defaultLang)
{
ViewName = viewName,
MasterName = master,
ViewData = ViewData,
TempData = TempData,
};
}
}

Obviamente, el método LocalizedView() sólo podría ser utilizado desde controladores descendientes de ControladorBase. Si preferís no heredar de un controlador base, siempre podríais crearlo como método extensor de Controller, aunque entonces deberíais invocarlo con el this por delante, y queda algo menos elegante.

Las siguientes acciones muestran posibles usos de esta implementación. Como se puede observar, son bastante explícitas y dejan clara su intencionalidad:

public ActionResult Prueba()
{
return LocalizedView(«About»);
}
public ActionResult About()
{
return LocalizedView();
}

Usando filtros

Los filtros presentan una solución bastante menos intrusiva al problema y, para mi gusto bastante mejor, puesto que no obligaría a modificar la implementación tradicional de las acciones. Para hacernos una idea, lo que pretendemos conseguir es lo siguiente:

[LocalizedView]
public ActionResult About()
{
    return View();
}

El simple hecho de decorar una acción con el atributo LocalizedView provocará que el retorno de la acción sea analizado, y se ejecute la lógica de selección de vistas ya descrita anteriormente. Además, el hecho de ser un filtro hace posible su aplicación a controladores completos, o incluso su aplicación global registrándolo en la inicialización de la aplicación.

El código del filtro personalizado es el siguiente:

public class LocalizedViewAttribute : ActionFilterAttribute
{
    private string _defaultLang;
 
    public LocalizedViewAttribute(string defaultLang = "es")
    {
        _defaultLang = defaultLang;
    }
 
    public override void OnResultExecuting(ResultExecutingContext filterContext)
    {
        var viewResult = filterContext.Result as ViewResultBase;
        if (viewResult != null)
        {
            if (string.IsNullOrWhiteSpace(viewResult.ViewName))
            {
                viewResult.ViewName = filterContext.RouteData.GetRequiredString("action");
            }
 
            string currentLang = Thread.CurrentThread.CurrentCulture.TwoLetterISOLanguageName;
 
            var v = ViewEngines.Engines.FindView(
                filterContext.Controller.ControllerContext,
                viewResult.ViewName + "_" + currentLang, null
            );
 
            if (v.View == null)
                viewResult.ViewName += "_" + _defaultLang;
            else
                viewResult.ViewName += "_" + currentLang;
        }
        base.OnResultExecuting(filterContext);
    }
}

En el código se puede observar que en primer lugar se comprueba que el resultado de la acción sea de tipo ViewResultBase; en caso contrario, es decir, cuando la acción no retorne una vista, simplemente se ejecutará la lógica por defecto.

A continuación, ya asumiendo que la acción retorna una vista, puede observarse la aplicación de la convención de nombrado cuando el nombre de la misma sea nulo, tomándolo de la acción actual. Finalmente, tras comprobar la existencia o no de la vista localizada, se modifica el valor de la propiedad ViewName del resultado, que identifica la vista que finalmente será enviada al cliente.

Desde el punto de vista de la implementación de las acciones queda prácticamente igual de claro que usando la técnica del ActionResult, pero en cambio, ésta requiere menos andamiaje. A continuación, dos posibles ejemplos de uso de este atributo:

[LocalizedView]
public ActionResult About()
{
    return View();
}
 
[LocalizedView("en")]
public ActionResult Prueba()
{
    return View("About");
}

SkydriveY finalmente, por si os interesara ver todo esto en funcionamiento, he dejado un proyecto de demostración en Skydrive (requiere VS2010+MVC 3).

Publicación original: http://www.variablenotfound.com/2011/03/retornar-vistas-dependiendo-de-la.html

Compilación de vistas ASP.NET MVC y el error “No se pudo cargar el tipo EntityDesignerBuildProvider”

ASP.NET MVCHacía tiempo que no activaba la compilación de vistas en ASP.NET MVC. La verdad es que si no tienes una máquina potente ralentiza bastante la construcción de las soluciones, pero ahora que la tecnología está de nuevo de mi lado, pensé que sería buena idea activarla en un proyecto MVC 3 y resulta que me he topado con el siguiente error de compilación:

No se pudo cargar el tipo ‘System.Data.Entity.Design.AspNet.EntityDesignerBuildProvider’

Pero vamos a empezar por el principio, para los que se han incorporado recientemente a ASP.NET MVC. Recordemos que esta característica permite comprobar la corrección de las vistas (Razor o Webforms) en tiempo de compilación, por lo que es bastante útil para la detección temprana de errores que de otra forma sólo podríamos detectar accediendo a la vista en tiempo de ejecución.

Para activarla, basta con abrir el archivo del proyecto MVC (el .csproj o .vbproj) con un editor y cambiar un detalle en la configuración. Esto podemos hacerlo desde cualquier editor tipo bloc de notas, o desde el mismo Visual Studio abriendo el menú contextual sobre el proyecto y seleccionando la opción “Descargar el proyecto”, como vemos en la captura de pantalla:

Menú contextual > Descargar el proyecto

Justo a continuación, de nuevo sobre el proyecto, volvemos a abrir el menú contextual, seleccionando en este caso la opción “Editar {nombreProyecto}”.

Menú contextual > Editar proyecto

Ya con el archivo abierto, veremos que se trata de un XML, en el que simplemente hay que localizar el tag <MvcBuildViews>false</MvcBuildViews> e introducir el valor true en su interior.
Archivo de configuración
Salvando el archivo, acudimos de nuevo al menú contextual sobre el proyecto, y seleccionamos “Volver a cargar el proyecto”, con lo que ya tendremos activa esta característica. A partir de ese momento, el proceso de construcción del proyecto incluirá la compilación de los archivos de vistas, por lo que podremos ver y corregir los errores detectados en ellas.

El único problema, al que hacía referencia al principio del post, es el extraño error “No se pudo cargar el tipo ‘System.Data.Entity.Design.AspNet.EntityDesignerBuildProvider’“ que aparecerá al finalizar la compilación cuando estéis utilizando Entity Framework en el proyecto. Por alguna extraña razón, el hecho de tener activa la compilación de vistas hace que se produzca este error (!).

Bueno, pues si os aparece simplemente debéis añadir la siguiente línea al web.config:

<assemblies>
  ...
  <add assembly="System.Data.Entity.Design, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" />
</assemblies>

De todas formas, ya os adelanto que aunque tengáis un último modelo de máquina, cuando el proyecto tiene cierto volumen váis a notar el tiempo extra a la hora de compilar y, si sois algo impacientes, lo tendréis la mayor parte del tiempo desactivado.

Publicado en: Variable not found.