Recibiendo un parámetro de tipo Array en un Controller de ASP.NET MVC

Implementando una acción en un controlador (Controller) de ASP.NET MVC he necesitado que uno de los parámetros fuera una lista o array de enteros, en concreto los Id a considerar para un pequeño informe. Por ponerlo negro sobre blanco, o mejor, pixel sobre pantalla:

public class InformeController : Controller
{
    //
    // GET: /Informe/Ventas?desde=6/1/2011&hasta=6/30/2011&centros=1,2,3

    public ActionResult Ventas(DateTime desde, DateTime hasta, int[] centros)
    {
        return Content("Centros: " + string.Join(" / ", centros));
    }
}

 

Para mi informe necesito un rango de fechas definido por desde y hasta, y una lista de centros definidos por sus Id. El paso de parámetros en la URL, mediante HTTP GET, tiene para mí muchas ventajas, entre ellas la facilidad para realizar pruebas.

A modo de test devuelvo una cadena con los centros separados por barras, para que se vea que no hay truco. En el comentario del propio código puede verse cómo sería una llamada, el interés está en el parámetro centros que recibe una lista separada por comas de los Id (dejando de lado que las fechas deben ir en inglés, pero esa es otra cuestión). La elección de la coma como separador es mía, aunque sigue una convención bastante extendida.

La cuestión es que esta acción tan sencilla e intuitiva no funciona puesto que en el argumento centros no se recibe el valor deseado en todos los casos.

  • En el ejemplo del comentario, con centros=1,2,3 en la QueryString, recibiremos null en centros. Mal.
  • En cambio, si pasamos centros=1 recibiremos un array con un entero de valor 1. Bien.

¿A qué se debe esto? Bien, tenemos que saber que estamos confiando en el ModelBinder de ASP.NET MVC para convertir los valores de la QueryString (nuestra URL) en los parámetros de nuestras acciones. Este ModelBinder tiene una funcionalidad muy completa con los tipos sencillos, pero poco más. Si necesitamos algo extra, como es nuestro caso, no nos ayuda.

Pero ASP.NET MVC tiene múltiples puntos de extensión, entre ellos la posibilidad de definir nuestros propios ModelBinder para cubrir los tipos que necesitemos. Así que vamos a definir uno para permitir la recepción de un array de enteros en un formato separado por comas. Sólo tenemos que extender de IModelBinder:

public class ArrayOfIntModelBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext,

                            ModelBindingContext bindingContext)
    {
        return controllerContext.HttpContext.Request[bindingContext.ModelName]

            .Split(‘,’)

            .Select(int.Parse)

            .ToArray();
    }
}

 

Esta interfaz sólo nos exige implementar el método BindModel que tiene la responsabilidad de transformar el trozo de la QueryString donde va nuestro parámetro (y que es un string, no lo olvidemos) en el valor destino, en nuestro caso un array de enteros. Recibe dos argumentos:

  • El contexto del controlador, que incluye la información de la petición (Request). De aquí obtenemos el valor del parámetro en la QueryString.
  • El contexto del modelo, en nuestro caso el argumento destino de la conversión. De aquí obtenemos el nombre del argumento buscado, que en nuestro ejemplo anterior sería “centros” pero que si usamos bindingContext.ModelName tenemos un Binder genérico que nos sirve para cualquier otro uso de array de enteros en otra acción (o incluso como otro parámetro en la misma).

El resto del método es sencillo: dividimos la lista de enteros usando la coma como separador, los convertimos a enteros con int.Parse y convertimos el IEnumerable resultante en un array. Pido perdón por mi concisión en el código, parte de la culpa la tiene Linq, otra buena parte Resharper, y otra yo mismo, qué pasa, si tenemos un lenguaje potente y hermoso como C# es para aprovecharlo, ¿no?

Bueno, nos queda la parte más importante, porque como ya imagináis esta clase por sí misma no hace nada si no le decimos a ASP.NET MVC que la utilice al procesar las peticiones. Para esto, basta con incluir en el Global.asax.cs una línea como:

 

ModelBinders.Binders[typeof(int[])] = new ArrayOfIntModelBinder();

 

Podemos ponerla en Application_Start(), y su mandato es sencillo de definir con palabras: “cuando encuentres un parámetro de tipo array de entero, lo procesas con esta instancia y no con el ModelBinder genérico.” Dicho y hecho, si todavía tenéis la URL anterior, volved a ejecutar el proyecto y refrescar esa página, podéis comprobar como ahora el parámetro centros devuelve todos los enteros que hayamos tenido a bien suministrarle en la URL.

Un placer.

Análisis de una aplicación web tipo Gmail

Durante la lectura de la hoja de ruta de MVC4 (en inglés), leo lo siguiente que pica mi curiosidad:

implementing a full-fledged single-page application (e.g. Gmail)

En primer lugar el hecho de que un documento de Microsoft cite a Gmail como ejemplo no es gratuito. Es bien merecido, ya que Gmail supuso una revolución en el concepto de página web y en su transición al concepto de aplicación web, en el sentido de sustituto de las aplicaciones de escritorio tradicionales (ya sabemos que antes ya había aplicaciones web, pero ¿cómo eran? E incluso ¿cómo son muchas ahora?). Pese a su antigüedad, podemos incluso atrevernos a hablar de Gmail como la primera aplicación HTML5, o de una precursora de estas (en realidad es una precursora en el uso de AJAX, pero ¿de dónde sale HTML5 si no?) Dicho esto con todas las precauciones tratándose de un desarrollo que comenzó hace ya varios años (hace sólo 2 años que dejó de ser beta, pero el proyecto se inició en 2004). También podemos señalar otro logro en el haber de Gmail, su contribución a que muchos usuarios asimilaran el concepto de la nube incluso antes de haberlo oído nombrar por primera vez.

En cualquier caso, su referencia en la hoja de ruta se refiere principalmente al modelo de aplicación: completa y en una única página. Es decir, lo tenemos todo disponible sin cambiar de página, con algunas características muy destacables:

  • Navegación: Es como un navegador dentro de un navegador: sólo cambia el contenido, los menús se conservan. De hecho incluso muestra su propio mensaje Cargando… sustituyendo al del navegador.
  • Enlaces: Aún simulando el comportamiento del navegador, se integra perfectamente con él: responde al botón atrás (y toda la gestión del historial), permite abrir un correo en una nueva página o ventana…
  • Caché local: Mantiene los correos descargados en la memoria del navegador, lo que nos permite una navegación muy rápida entre ellos.
  • Carga inicial: Todo esto tiene un coste: la carga inicial no es rápida, de ahí la barra de progreso. Pero aun así tampoco es lenta, podemos decir que es muy soportable.

Un día tengo que profundizar bien en cómo hace Gmail todo esto y lo compartiré con vosotros (aunque si alguien quiere ahorrarme el trabajo, soy todo oídos). Lo bueno de las aplicaciones web es que es fácil aprender de ellas puesto que tenemos su parte de cliente en nuestro navegador, y Gmail es evidentemente una aplicación de cliente: Javascript en estado puro. Lo cual no quiere decir que sea fácil de bichear.

En cualquier caso, y volviendo a MVC (perdón que abrevie, pero me cuesta citar un tercio de alfabeto cada vez, o sea ASP.NET MVC, que son 9 de las 27 letras del abecedario) es evidente que el tipo de aplicaciones cubierto hasta ahora por MVC no es el tipo Gmail, sino todo lo contrario: la típica aplicación web con páginas bien diferenciadas. Evidentemente se puede mejorar, sobre todo con jQuery, cada vez mejor integrado, y con la facilidad que ofrecen los controllers para implementar servicios web, pero sigue sin existir un soporte nativo para este tipo de aplicaciones (léase plantilla de proyecto, helpers, integración con librerías de javascript para tal fin…) ¿Es eso lo que plantean en la hoja de ruta? Evidentemente, los artefactos que planteo pueden desarrollarse sobre MVC3, de hecho muchos los habrán desarrollado en mayor o menor medida. Pero su eventual inclusión en MVC4 contribuiría a su estandarización y a la difusión de su uso.

En resumen, dentro de la gran bola de nieve que es HTML5, el tipo de aplicación en página única es uno de los objetivos que se persiguen con la especificación. Y no sólo depende de características en el navegador (como bien ha demostrado el propio Gmail hace tiempo, aunque sí puedan ayudar a simplificar y mucho su desarrollo); sino que requieren de una fuerte integración entre cliente y servidor. MVC nos ofrece una moderna y flexible plataforma en el servidor, ¿en su nueva versión extenderá sus ramas aún más hacia el cliente con una integración más estrecha con librerías de javascript para simplificar el desarrollo de este tipo de aplicaciones? Tengo que pensar que las extensiones de jQuery desarrolladas por Microsoft (templates, data binding) ya marcaban este camino. ¿Tan lejano está el día en que yo no necesite mirar cómo hace Gmail su magia porque tendremos una plataforma que nos lo proporcione a un alto nivel de abstracción?

Por supuesto, esto no son más que divagaciones mías centradas en tan sólo uno de los numerosos puntos que se marca la hoja de ruta de ASP.NET MVC 4, es una lectura muy recomendable y amena.

Hola Geeks.ms

Hoy estreno mi nuevo blog en geeks.ms, algo que no podía ni imaginar hace sólo unos meses. Pero un grupo fantástico de gente me ha puesto las pilas y me ha convencido de que con muy poquitos conocimientos pero muchas ganas de compartir se puede aportar mucho a la comunidad, y si nos estamos nutriendo siempre de las aportaciones de los demás en nuestro día a día, es de justicia que aportemos también algo, por poco que sea. Así que muchas gracias a Rodrigo por ofrecerme este espacio y a tantos amigos de Twitter que me mantienen despierto todo el día y parte de la noche con sus aportaciones de enorme calidad.

Me siento muy feliz de formar parte de esta gran comunidad y espero poder aportar todo lo que mi tiempo libre me permita. Bueno, aunque libre ya no es, porque ya está también ocupado 😉

Y ahora un poco sobre mí: Aunque me inicié con Visual Basic 6 y ADO, mi mayor experiencia se centra en C#, WPF y Silverlight, con inmersiones más o menos profundas en WinForms, EF, ASP.NET MVC, SQL Server, TFS… y tantos productos relacionados. De ahí que la temática del blog pueda ir cambiando según el proyecto en el que esté trabajando.

Por cierto, no quiero decir con el título que geeks.ms sea el mundo, pero sí es cierto que agrupa una parte importante de nuestro mundo profesional y por eso estoy muy ilusionado por formar parte activa de él.

Nos leemos.