ASP.NET MVC – ¿Por qué usar los helpers para formularios?

Hace nada mi compi Javier Torrecilla (Little Tower para los amigos) ha escrito un post sobre los helpers de ASP.NET MVC.

En este post quiero centrarme en por qué debes usar los helpers para formularios de ASP.NET MVC. La respuesta “por qué están ahí” no vale. Hay muchas cosas que están por ahí y no deberían usarse salvo casos muy concretos (incluso cosas del .NET Framework).

Iremos como los New Kids on the Block, es decir paso a paso. Por supuesto si ya sabes todo lo que te aportan los helpers entonces este post no te aportará mucho, pero eres bienvenido a seguir leyendo por supuesto (y a comentar, claro) 🙂

Pregunta: Como recibo en un controlador los datos mandados en un <form>?

Esa es una pregunta típica que se hace cualquiera que empieza con ASP.NET MVC. Si vienes de ASP antiguo (el clásico, el de Interdev) o de otra tecnología como PHP, pues igual empiezas a buscar alguna propiedad llamada Form en la Request o algo así. ¡No busques! No lo hagas porque la encontrarás y entonces la usarás y te perderás uno de los elementos más poderosos de ASP.NET MVC: el model binder.

Para responder a esta pregunta vamos a usar esta vista:

Da de alta una cerveza:

 

<form method="POST">

    Nombre:

    <input type="text" name="Name" />

    <br />

    Categoría:

    <input type="text" name="Category" />

    <br />

    <input type="submit" value="Enviar!"/>

</form>

Todo HTML, que de moment no usamos Helpers 🙂

Veamos como no acceder a los valores de un formulario. Si vienes de PHP o Interdev probablemente habrás llegado a teclear algo como esto:

public ActionResult Index()

{

    if (Request.Form["Name"] != null && Request.Form["Category"] != null)

    {

        // Procesar alta

        return null;

    }

    else

    {

        return View();

    }

}

Parece lógico ¿no? Buscamos si los datos de formulario existen, y si exiten es que venimos del POST y los procesamos. En caso contrario es que nos han hecho un GET por lo que devolvemos la vista.

Vale… olvida este código. Así no se hacen las cosas en ASP.NET MVC. Primero la lógica del GET (mostrar una vista) y del POST (procesar una alta) están mezclados y eso no es buena señal (¿conoces el SRP)? Por suerte ASP.NET MVC nos permite separar la lógica del GET de la del POST definiendo dos métodos y decorando el que gestiona el POST con el atributo [HttpPost].

Tener la lógica separada sería un poco mejor y de hecho hay por Internet código parecido al siguiente:

public ActionResult Index()

{

    return View();

}

 

[HttpPost]

public ActionResult Index(FormCollection form)

{

    if (form["Name"] != null && form["Category"] != null)

    {

        // Procesar alta

    }

    return null;

}

La clase FormCollection que aparece es una clase propia de ASP.NET MVC que tiene la misma información que Request.Form. Este código funciona y de hecho verás código así por Internet (se ve de todo en este mundo) pero la realidad es que hay muy pocas razones para usar FormCollection hoy en día. ASP.NET MVC tiene un concepto mucho más poderoso, un amigo al que debes conocer y entender: el model binder.

Ya he hablado en este blog sobre el model binder, así que ahora solo introduciré la regla más básica: Si tu acción tiene un parámetro cuyo nombre es idéntico al de un atributo name de un campo de un formulario, el model binder enlazará el valor del campo al parámetro automáticamente.

Es decir, el código anterior nos puede quedar como:

[HttpPost]

public ActionResult Index(string name, string category)

{

    // procesar alta

    return null;

}

Esa es la regla básica del model binder. Estoy seguro que esto te parece precioso pero a la vez es posible que una inquietud atormente tu nueva felicidad: si tengo un formulario con 20 campos… se deben declarar 20 parámetros en la acción?

Por suerte el equipo de ASP.NET MVC se dio cuenta de ello y así surge la segunda (y última que veremos en este post) regla del model binder. Ojo, que ahí va: Si tu accíón recibe como parámetro, un objeto de una clase que tiene una propiedad cuyo nombre es igual al de un atributo name de un campo del formulario, el model binder enlazará automáticamente el valor de este campo a la propiedad correspondiente del objeto recibido como parámetro. Quizá te parezca un poco rebuscado, pero lo que significa es que puedo tener una clase como la que sigue:

public class BeerModel

{

    public string Name { get; set; }

    public string Category { get; set; }

}

Y ahora en mi controlador recibir los datos simplemente usando un parámetro de tipo BeerModel:

[HttpPost]

public ActionResult Index(BeerModel beer)

{

    // procesar alta

    return null;

}

El model binder se encarga de crear el objeto beer y de rellenarlo con los parámetros del formulario. ¿Genial, no?

Pregunta: Vale, pero esto que tiene que ver con los helpers?

De momento nada, pero ahora veamos que ocurre si no los ponemos. Imagina que alguien me da de alta una cerveza pero hace submit del formulario sin entrar el nombre que es obligatorio. Evidentemente yo tengo que mostrarle de nuevo la vista de dar de alta una cerveza. Una forma no muy correcta de hacerlo es la siguiente:

[HttpPost]

public ActionResult Index(BeerModel beer)

{

    if (string.IsNullOrEmpty(beer.Name))

    {

        return View(beer);

    }

    // procesar alta

    return null;

}

Si el nombre de la cerveza está vacío devuelvo la vista como resultado, pero le paso la propia cerveza. ¿Para qué? Pues porque ahora tengo que meter código para recuperar el estado de los controles. Es decir, debo rellenar el atributo value de los dos <input> con los valores que me vienen de beer, ya que si no el usuario recibiría… ¡una vista completamente vacía!. Si vienes de Webforms este es un buen punto para empezar a odiar ASP.NET MVC, pero si lo sobrepasas luego, estoy seguro, lo empezarás a amar irremediablemente.

En resumen nuestra vista ahora queda algo como así:

@model MvcApplication1.Models.BeerModel

@{

    ViewBag.Title = "Index";

    var nameValue = Model != null ? Model.Name : null;

    var categoryValue = Model != null ? Model.Category : null;

}

Da de alta una cerveza:

 

<form method="POST">

    Nombre:

    <input type="text" name="Name" value="@nameValue"/>

    <br />

    Categoría:

    <input type="text" name="Category" value="@categoryValue"/>

    <br />

    <input type="submit" value="Enviar!"/>

</form>

Aquí me estoy aprovechando de una característica de Razor que es que si igualamos un atributo de un elemento HTML a null, entonces Razor ni nos renderiza el atributo. Es decir si nameValue o categoryValue vale null, el atributo value ni aparece en el HTML generado.

Vale… ahora imagínate un formulario con 20 campos y ya puedes empezar a llorar.

Y esta es la primera razón por la que usar helpers. Si modificamos la vista para usar los helpers:

@model MvcApplication1.Models.BeerModel

@{

    ViewBag.Title = "Index";

}

Da de alta una cerveza:

 

<form method="POST">

    Nombre:

    @Html.TextBoxFor(m=>m.Name)

    <br />

    Categoría:

    @Html.TextBoxFor(m=>m.Category)

    <br />

    <input type="submit" value="Enviar!"/>

</form>

Automáticamente los controles del formulario recuperan su estado automáticamente. ¡Hey! El que venía de Webforms… sigues ahí? 😉

Pregunta: Y como muestro que campo es el que ha producido el error?

Jejejeee… muy buena pregunta. Hemos redirigido de nuevo al usuario porque se ha dejado de introducir el nombre de la cerveza, pero estaría bien que le informáramos de ello. Antes he dicho que el código del controlador no era muy correcto. Y no lo es porque no usa un objeto de ASP.NET MVC llamado ModelState. El ModelState guarda entre otras cosas todos los errores que se producen al validar los datos por parte del controlador.

Así que lo suyo es introducir el error en el ModelState cuando lo detectamos:

[HttpPost]

public ActionResult Index(BeerModel beer)

{

    if (string.IsNullOrEmpty(beer.Name))

    {

        ModelState.AddModelError("Name", "¿Donde has visto una cerveza sin nombre?");

        return View(beer);

    }

    // procesar alta

    return null;

}

El método AddModelError añade un error a la propiedad indicada (el segundo parámetro es el mensaje de error). Ahora si ejecutamos el proyecto y envías el formulario dejando el campo nombre vacío, el código HTML generado de vuelta será algo como:

<form method="POST">

    Nombre:

    <input class="input-validation-error" id="Name" name="Name" type="text" value="" />

    <br />

    Categoría:

    <input id="Category" name="Category" type="text" value="sdsd" />

    <br />

    <input type="submit" value="Enviar!"/>

</form>

Fíjate que el <input> generado para tener el nombre de la cerveza ahora incluye una clase llamada “input-validation-error”. Esta clase la añade el helper cuando ve que hay un error asociado en el ModelState.

Luego ya por CSS es cosa tuya personalizar esta clase para que haga algo útil (p. ej. mostrar el campo en rojo). Lo importante es que el helper la añade automáticamente sin que tu tengas que hacer nada.

Ahora imagínate sin los helpers… para un formulario con 20 campos, tener que consultar para cada uno si existe un error en el ModelState… buf, sería para morirse.

Y es por esto, básicamente, que debemos utilizar los helpers!

Un saludo!

PD: Ojo, hay mejores maneras de validar la entrada de datos en ASP.NET MVC. Basta decir que el código de validación no debería estar en el controlador y que ASP.NET MVC ofrece dos mecanismos built-in que son DataAnnotations y la interfaz IValidatableObject. No hemos visto nada de esto porque no es el objetivo de este post.

2 comentarios sobre “ASP.NET MVC – ¿Por qué usar los helpers para formularios?”

  1. Hola Eduard,

    Como siempre, magnifico post!!

    Desde que puedo depurar ASP.NET MVC me he vuelto un cotilla para ver como funcionan muchas cosas que antes eran casi imposibles de averiguar 🙂

    Respecto al tema de tu post y como «mágicamente» recuerdan el estado los helpers, al final todo se reduce a ¿Has bindeado en el controlador? Si lo has hecho, tu helper se aprovechará de ello. Sino tu helper no sabrá que mostrar. La única diferencia que aprecio es que los helpers no tipados (los que no llevan For), si no encuentran nada en ModelState también lo intentan finalmente en ViewBag. Por contra, los tipados (con For) sólo buscan en ModelState.

    Todo esto pasa en el método privado InputHelper de la clase InputExtensions http://aspnetwebstack.codeplex.com/SourceControl/latest#src/System.Web.Mvc/Html/InputExtensions.cs

    Lo dicho, doy infinitas gracias a que me funcionará de una maldita vez el tema de depurar ASP.NET MVC, ahora me lo paso pipa!! xD

    Un saludo.

Deja un comentario

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