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.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *