Helpers para formularios

  • Por
  • ASP, .NET
Los helpers nos van a permitir crear campos para crear un formulario de una manera muy cómoda.
En el artículo anterior vimos el uso de POST en ASP.NET MVC junto con las bases del Model Binding. En concreto la norma más importante que debemos recordad es que el atributo name del campo debe llamarse igual que la propiedad que queremos enlazar. Por supuesto que el Model Binder admite enlazar parámetros complejos (una clase que tenga una propiedad que sea una lista de objetos que a su vez tengan más propiedades simples o complejas) y en este caso basta que los atributos name de los campos del formulario sigan unas determinadas reglas. No vamos a entrar más en detalle porque, por suerte el framework nos ofrece una ayuda para no tener que recordar exactamente todas esas reglas: Los helpers para formularios.

Los helpers nos van a permitir crear campos para crear un formulario de una manera muy cómoda y son sin duda alguna la manera preferida de hacerlo.

Crear campos HTML

Lo primero que vamos a ver es como crear un campo HTML para editar (o mostrar) una propiedad específica del Viewmodel que recibe la vista. Supongamos que tenemos esa clase:

public class Trabajador
{
public string Nombre { get; set; }
public string Apellido { get; set; }
public DateTime FechaNacimiento { get; set; }
public double Sueldo { get; set; }
public bool EsFijo { get; set; }
}

Ahora vamos a crear una vista que muestre cuatro campos de texto (para nombre, apellido, fecha de nacimiento y sueldo) y una casilla de verificación para indicar si es fijo o no. Pero en lugar de crear los controles a mano (<input type="xxx">) vamos a usar los helpers:

@using MvchelpersForms.Models
@model Trabajador

<form method="post">

Nombre: @Html.TextBoxFor(x=>x.Nombre) <br />
Apellido: @Html.TextBoxFor(x=>x.Apellido) <br />
Fecha Nacimiento: @Html.TextBoxFor(x=>x.FechaNacimiento) <br />
Sueldo: @Html.TextBoxFor(x => x.Sueldo) <br />
Es Fijo: @Html.CheckBoxFor(x=>x.EsFijo) <br />

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

</form>

Estamos usando los helpers:

  • Html.TextBoxFor que muestra un campo de texto para la propiedad indicada
  • Html.CheckBoxFor que muestra una casilla de verificación para la propiedad indicada
Fijaos en como se indica a cada helper para que propiedad debe renderizar el control:

@Html.TextBoxFor(x=>x.Nombre)

El parámetro en forma x=>x.Nombre es una expresión lambda. No es objetivo de este artículo entrar en las expresiones lambda, así que nos vamos a limitar a indicar que en este caso se usan para indicar la propiedad. La ventaja de usar una expresión lambda antes que una cadena (algo como TextBoxFor("Nombre")) es que las expresiones lambda son evaluadas en tiempo de compilación y no de ejecución (si nos equivocamos en el nombre de la propiedad el código no compila). Esto requiere que la vista sea tipada con el tipo de ViewModel (fijaos en el uso de la directiva @model).

Bien, ahora veamos que código fuente nos ha generado esta vista:

<form method="post">

Nombre: <input id="Nombre" name="Nombre" type="text" value="" /> <br />
Apellido: <input id="Apellido" name="Apellido" type="text" value="" /> <br />
Fecha Nacimiento: <input id="FechaNacimiento" name="FechaNacimiento" type="text" value="" /> <br />
Sueldo: <input id="Sueldo" name="Sueldo" type="text" value="" /> <br />
Es Fijo: <input id="EsFijo" name="EsFijo" type="checkbox" value="true" /><input name="EsFijo" type="hidden" value="false" /> <br />

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

</form>

La verdad es que no se diferencia mucho del código que nosotros pondríamos a mano (que vimos en el artículo anterior). Lo que sí os puede llamar la curiosidad es el campo hidden cuyo name es "EsFijo". Este campo existe para… bueno, para lidiar con el hecho de que una checkbox no marcada, no se envía. Es decir si tenemos el campo:

<input id="EsFijo" name="EsFijo" type="checkbox" value="true" />

Eso significa que si la checkbox está marcada se enviará el valor indicado en value (true), mientras que si la checkbox no está marcada el navegador no enviará nada. Una checkbox no marcada es como si no existiese. Pero el Model Binder de ASP.NET MVC esperará que haya un campo EsFijo para poder enlazarlo a la propiedad, de ahí que se añada este campo hidden con el mismo nombre y valor false. De este modo si la checkbox no está marcada el campo hidden se envía y tiene el valor de false. ¿Habrías pensado tú en eso? Esas son las ventajas de usar los helpers :)

Ventajas de usar los helpers

Pero todavía hay más, si mostráis la vista en el navegador y sin introducir datos le dais a Enviar:

¡Exacto! Nos aparecen errores. En concreto el sistema nos muestra un error si FechaNacimiento o Sueldo están vacíos. ¿Y por qué esos dos campos y no los otros? Muy sencillo: porque esos dos campos están declarados como DateTime y double que son tipos por valor y por lo tanto nunca pueden valer null.

Ahora… probad de meter una cadena en el Sueldo:

¡Sigue dando error! Esto es porque la cadena “dsddsd” no puede ser convertida en double, lo que genera un error.

Ahora bien, quiero dejar una cosa bien clara: El sistema siempre realiza esas validaciones con independencia de que usemos los helpers o no. Pero estos últimos están preparados para mostrar el error (usar una clase CSS específica) si ese existe.

Ahora igual os surge la siguiente duda: ¿Y si no uso los helpers como puedo saber si hay errores en un campo concreto? Pues haciendo lo mismo que realmente hacen ellos: Acceder a la propiedad ModelState del ViewData que entre otras cosas contiene los errores vinculados a una propiedad. Mirad como sería el código si queremos generar el campo Sueldo para que se comporte igual que usando el helper:

<input type="text" name="Sueldo" value="@(Model != null ? Model.Sueldo.ToString() : string.Empty)"
class="@(ViewData.ModelState.ContainsKey("Sueldo") && ViewData.ModelState["Sueldo"].Errors.Any() ? "input-validation-error" : string.Empty )"
/>

Tenemos que realizar manualmente las dos tareas que el helper hace por nosotros:

  • Establecer la propiedad value al valor del ViewModel recibido si lo hubiese
  • Establecer la clase a input-validation-error en caso de haber un error vinculado en el campo que estamos mostrando.
Así pues, los helpers no son imprescindibles. Pero sí que son muy cómodos y la manera recomendable de construir formularios en ASP.NET MVC.

Hemos visto TextBoxFor y CheckBoxFor pero hay un helper por cada control HTML que habitualmente usamos en formularios, así tenemos Html.TextAreaFor, Html.LabelFor, Html.DropDownListFor, etc…

Os muestro el uso de Html.LabelFor porque es otro que se usa muchísimo. Ese genera un campo

@Html.LabelFor(x=>x.Nombre) @Html.TextBoxFor(x=>x.Nombre) <br />

Nos genera el código HTML:

<label for="Nombre">Nombre</label> <input id="Nombre" name="Nombre" type="text" value="" /> <br />

La etiqueta <label> se renderiza como texto plano. La ventaja de usarla es que al pulsar sobre la label el navegador da el foco al control indicado en el atributo for (en ese caso el textbox de al lado).

Personalización de los helpers

¿Si a un helper sólo le puedo pasar una expresión lambda que indica la propiedad para la cual debe renderizar un determinado control HTML, como puedo personalizarlos? Por personalizarlos me refiero a poder añadir atributos HTML adicionales como style o cualquier otro.

Pues bien, muchos de los helpers tienen una sobrecarga donde admiten un objeto anónimo cuyas propiedades serán mapeadas a atributos HTML al generar el código. Así pues el siguiente código en la vista:

@Html.LabelFor(x => x.Nombre) @Html.TextBoxFor(x => x.Nombre, new { style = "border-color: blue", size = "30" })

Genera el siguiente código HTML:

<input id="Nombre" name="Nombre" size="30" style="border-color: blue" type="text" value="" />

Helpers "inteligentes"

Con los helpers que hemos visto hasta ahora es nuestra responsabilidad la de decidir qué control HTML mapeamos a cada propiedad (un textbox, una checkbox, etc). Pero ASP.NET MVC incorpora dos helpers, llamémosles "inteligentes" que son capaces de determinar cuál es el mejor control para visualizar o editar una propiedad concreta.

Esos dos helpers son:

  • HtmlDisplayFor: Renderiza el HTML necesario para visualizar una propiedad (sin poder ser editada)
  • HtmlEditorFor: Renderiza el HTML necesario para poder editar una propiedad.
Esos helpers son extraordinariamente potentes (tanto que van a tener un artículo para ellos solos) así que por ahora vamos a ver solo su uso:

@using MvcHelpersForms.Models
@model Trabajador
<form method="post">
@Html.LabelFor(x => x.Nombre) @Html.EditorFor(x => x.Nombre)
<br />
Apellido: @Html.EditorFor(x => x.Apellido)
<br />
Fecha Nacimiento: @Html.EditorFor(x => x.FechaNacimiento)
<br />
Sueldo: @Html.EditorFor(x => x.Sueldo) <br />
Es Fijo: @Html.EditorFor(x => x.EsFijo)
<br />
<input type="submit" value="Enviar" />
</form>

Fijaos que ahora usamos solamente Html.EditorFor. El código HTML generado ahora es:

<form method="post">
<label for="Nombre">Nombre</label> <input class="text-box single-line" id="Nombre" name="Nombre" type="text" value="" />
<br />
Apellido: <input class="text-box single-line" id="Apellido" name="Apellido" type="text" value="" />
<br />
Fecha Nacimiento: <input class="text-box single-line" id="FechaNacimiento" name="FechaNacimiento" type="text" value="" />
<br />
Sueldo: <input class="text-box single-line" id="Sueldo" name="Sueldo" type="text" value="" /> <br />
Es Fijo: <input class="check-box" id="EsFijo" name="EsFijo" type="checkbox" value="true" /><input name="EsFijo" type="hidden" value="false" />
<br />
<input type="submit" value="Enviar" />
</form>

Este código es casi idéntico al que teníamos antes. Html.EditorFor renderiza un cuadro de texto para cada propiedad a excepción de la booleana para la cual renderiza una casilla de verificación.

La pregunta evidente es: ¿Si existe EditorFor para que usar el resto de helpers? Pues bien, cuando queráis vosotros decidir cuál es el control HTML se usa para visualizar o mostrar una propiedad podéis usar los helpers que vimos antes. Cuando queráis que sea el sistema usad EditorFor (o DisplayFor si estáis realizando una vista que no permita editar). Como ya os he avanzado antes EditorFor y DisplayFor son extraordinariamente potentes y con ellos se pueden hacer auténticas maravillas, pero eso lo veremos en un artículo próximo.

El helper BeginForm

Bueno, ya que hablamos de helpers para generar formularios este es un buen momento para introducir el helper BeginForm. Este helper lo que genera es… el tag <form>. Bueno, de hecho el tag <form> y su parejo </form>. Es por ello que ese helper se "usa" de una forma un poco… distinta:

@using (Html.BeginForm()) {
<!-- Codigo del formulario -->
}

El código que genera el formulario se incluye dentro de las llaves de apertura y cierre. Cuando se encuentre la llave de cierra se generará el tag </form>. Al usar BeginForm() debemos tener un poco más de cuidado con Razor. El siguiente código da error:

@Html.LabelFor(x => x.Nombre) @Html.EditorFor(x => x.Nombre)
<br />
Apellido: @Html.EditorFor(x => x.Apellido)
<br />
Fecha Nacimiento: @Html.EditorFor(x => x.FechaNacimiento)
<br />
Sueldo: @Html.EditorFor(x => x.Sueldo) <br />
Es Fijo: @Html.EditorFor(x => x.EsFijo)
<br />
<input type="submit" value="Enviar" />
}

La razón es que Razor interpreta que debe ejecutar el código que se encuentra entre las llaves. Por supuesto, como ya vimos, Razor es lo suficientemente inteligente para "ignorar" el código HTML (como <br />) pero no el texto plano. Es decir, Razor intentará ejecutar el código Apellido: (o Es Fijo:) que un código totalmente inválido en C#, de ahí el error.

¿La solución? La que vimos en el artículo que dedicamos a Razor, o bien usamos @: o bien usamos la etiqueta ficticia <text> para indicarle a Razor que este texto plano es eso… texto plano, que debe mandar a la salida HTML sin más:

@using (Html.BeginForm()) {
@Html.LabelFor(x => x.Nombre) @Html.EditorFor(x => x.Nombre)
<br />
@:Apellido: @Html.EditorFor(x => x.Apellido)
<br />
@:Fecha Nacimiento: @Html.EditorFor(x => x.FechaNacimiento)
<br />
<text>Sueldo:</text> @Html.EditorFor(x => x.Sueldo) <br />
<text>Es Fijo:</text> @Html.EditorFor(x => x.EsFijo)
<br />
<input type="submit" value="Enviar" />
}

¡Bueno! Suficiente por hoy, ¿no? En este artículo hemos visto que son los helpers para formulario, como se usan y cuáles son sus ventajas. En el siguiente artículo vamos a hablar de un tema importantísimo: las validaciones

Autor

Eduard Tomàs

Apasionado de la informática, los videojuegos, rol y... la cerveza. Key Consultant en Pasiona y MVP en #aspnet

Compartir

Comentarios

Santiago Vera

30/11/2011
Formularios
Parece ser muy importante toda la explicación sobre los helpers para formularios, pero parece ser solamente para iniciados y no para aquellos que no tienen tantos conocimientos. Hace rato que sigo a Desarrollo Web, pero no encuentro como insertar un libro de visitas en mi página, pese a todas las explicaciones que me han brindado. Creo que deberían publicar un simple libro de visitas, desarrollando el código completo de tal forma que incluya html y todo lo demas, y podamos insertarlo sin problemas.

eiximenis

01/12/2011
RE: Formularios
Buenas Santiago,
No es el objetivo del manual el proporcionar un ejercicio completo porque creo que no es muy compatible con dicho formato, donde en cada artículo damos una pincelada sobre un tema de ASP.NET MVC.

Ahora bien, eso no quita que tu propuesta sea interesante... Por lo que, desde aquí y a lo Adolfo Suarez puedo prometer y prometo que vamos a ver como implementar un libro de visitas en ASP.NET MVC. Lo que no tengo claro es si vamos a verlo dentro del manual o hablo con Miguel para hacer una pequeña serie adicional de artículos (al margen del manual) viendo esta implementación.

De todos modos, puedes contactar conmigo a etomas_AT_gmail.com por si quieres preguntarme cualquier duda o tienes cualquier sugerencia sobre el manual (o sobre ASP.NET MVC).
También está el foro de MSDN de ASP.NET MVC en el cual participo de vez en cuando y donde puedes exponer tus dudas: http://social.msdn.microsoft.com/Forums/es-es/aspnetmvces/threads

Un saludo y muchas gracias por comentar!

will2012

09/1/2012
crear un formulario
como se hacen los formularios que usan las pagnas que editan fotos en linea... que hece q el usuario sube sus fotos a la web... como se hace

JLO

25/7/2012
ESTILO a controles
En la parte donde se explica lo de los Helpers "Inteligentes", en el codigo razor, donde le indicas la clase que usaran los controles?,
Veo que el HTML que genera trae una clase "class="text-box single-line""

valthern

07/2/2013
Problema con ejercicio de este Capítulo (Helpers para formularios):
Publico el desarrollo de un problema que tuve con el ejercicio de este capítulo para que si alguno tiene el inconveniente como e que tuve pueda salir adelante:

[YO]:
Al grano. Hice un ejemplo de prueba con ASP MVC 3 en el cual hice lo que se pide en este artículo:
1.- Crear una clase "Trabajador" en la carpeta Models,
2.- En la vista de "Index.cshtml" ingresé el formulario que aparece.
Traté de usar los helpers y de no usarlos para ver si yo hice algo mal, así que implementé una propiedad de manera "expandida" (así como nos sugeriste hacerlo sin usar los Helpers). Además lo intenté usando el helper de Html.BeginForm() y aun así por alguna razón cuando intento enviar el formulario vacío o con datos erróneos no recibo el resaltado que se menciona. De hecho probé haciendo que, cuando no uso Html.BeginForm() el formulario en su atributo action apunté a otro documento diferente para ver si lo que veo no es una "ilusión óptica" (en este caso lo dejé así: action="../Account/ChangePasswordSuccess").
De todos modos SI envía el formulario y NO me aparecen los "inputs de texto" con ese estilo, el cual si verifiqué que existiera en el site.css. Usé valores incorrectos y también con los campos vacíos, no obstante el resultado es el mismo: el envío del formulario y en consecuencia la aparición de la página ChangePasswordSuccess.cshtml.
Agregué esta cláusula hasta arriba del documento:
@using MvcCap09.Models
@model Trabajador
¿Qué es lo que me estaría haciendo falta declarar o qué podría estar pasando para que se produzca el error?
Mis códigos:
---Index.cshtml:
@using MvcCap09.Models
@model Trabajador

<h2>@ViewBag.Message</h2>
@*<form method="post" action="../Account/ChangePasswordSuccess">
*@
@using (Html.BeginForm()){
@Html.LabelFor(x => x.Nombre)@:: @Html.EditorFor(x => x.Nombre) <br />
<text>Apellido:</text> @Html.EditorFor(x => x.Apellido) <br />
@:Fecha de Nacimiento: @Html.EditorFor(x => x.FechaNacimiento) <br />
@:Sueldo: @Html.EditorFor(x => x.Sueldo)@*<input type="text" id="Sueldo" name="Sueldo" value="@(Model != null ? Model.Sueldo.ToString() : string.Empty)" class="@(ViewData.ModelState.ContainsKey("Sueldo") && ViewData.ModelState["Sueldo"].Errors.Any() ? "input-validation-error" : string.Empty)"/>*@ <br />
@:Es Fijo: @Html.EditorFor(x => x.EsFijo) <br />
<input type="submit" value="Enviar" />
}
@*</form>*@

---Trabajador.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.ComponentModel.DataAnnotations;
namespace MvcCap09.Models
{
public class Trabajador
{
[Required]
public string Nombre { get; set; }
public string Apellido { get; set; }
public DateTime FechaNacimiento { get; set; }
public double Sueldo { get; set; }
public bool EsFijo { get; set; }
}
}

Agradezco enormemente por la respuesta y por todo su trabajo el cual nos ayuda a comprender mejor estas tecnologías y su uso.
Saludos desde Puebla, México.
[Eduard Tomàs]:
Buenas Ismael.
Tengo una sospecha de lo que te puede estar pasando, pero necesitaria ver el código del controlador. De ambas acciones :
Me lo mandas? ;-)
Saludos!
[Yo]:
Por supuesto que sí. Ahora mismo envío una copia de mi archivo del HomeController tal cual lo tengo.

---HomeController.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
namespace MvcCap09.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{
ViewBag.Message = "Helpers en ASP.NET MVC";

return View();
}

public ActionResult About()
{
return View();
}
}
}
La verdad ahora que lo pienso, no sé? Creo saber si hallé el problema. Revisando en el internet precisamente encontré esta página:

http://msdn.microsoft.com/es-es/library/dd410405%28v=vs.98%29.aspx
Creo que según veo el ?modo? en el que se envía el formulario también debe coincidir un Método con el tipo correspondiente de la forma de envío. Es decir, no estoy seguro pero creo que no funciona porque no tengo ningún método con la acción ?Index()? que tenga el atributo [AcceptVerbs(HttpVerbs.Post)] o el atributo [HttpPost] el cual nos comentaste? Y con relación a esto ¿Qué diferencia hay entre estos dos Atributos al declararlos en el Método? ¿Hay algo más que falte además de esto?
A decir verdad soy un novato en esto del MVC, y por lo que voy viendo en este artículo de DesarrolloWeb, francamente me va gustando más que Web Forms.
De nueva cuenta agradezco mucho por su tiempo y su respuesta a estas inquietudes, no solo mías sino de todos los que nos apasiona el desarrollo de software.
Saludos.
[Eduard Tomàs]:
Buenas!
Bueno... efectivamente el problema que tienes es el que sospechaba y tiene que ver con lo que tu has encontrado.
Que te está pasando exactamente?
Bien, cuando haces el POST a /Home/Index, este post es gestionado por la acción Index. Y tu tienes tan solo un método para esta acción declarado.
Entonces...
1. Cuando llamas vía GET (poniendo la URL en el navegador, a través de un enlace) se llama al método Index() que devuelve la vista --> OK
2. Cuando llamas via POST (submit de form) se llama a este mismo método ya que ASP.NET MVC no encuentra otro. Por lo que... devuelves de nuevo la vista vacía. De ahí que parezca "no funcionar".
Cual es la solución?
Debes añadir un método para gestionar el POST del formulario a tu controlador. Además en este método debes declarar un objeto del mismo tipo de modelo de la vista (en tu caso Trabajador), para poder recibir los datos (si no ASP.NET MVC no te podría mandar los datos):
[HttpPost]
public ActionResult Index(Trabajador trabajador)
{
if (!ModelState.IsValid) return View(trabajador); // Datos incorrectos, devolvemos la vista de nuevo.
// Datos correctos, procesarlos y devolver lo que toque!!!! xD
}

La llamada a ModelState.IsValid comprueba si el modelo tiene errores o no (p.ej. un campo [Required] no entrado). Si hay errores lo que se hace es devolver la vista pero se le pasa el modelo. Además del modelo se le pasa el ModelState (pero eso ya lo hace ASP.NET MVC automáticamente). Con esa doble info:
1. La vista puede rellenar los campos con los datos que se introdujeron (en lugar de mostrar el form vacío)
2. La vista puede mostrar los errores.
Si el modelo es válido entonces ya te toca procesar los datos... haces lo que sea y al final debes devolver algo. Lo usual es devolver:
1. Otra vista (p. ej. return View("Ok")) y tener la vista ok.cshtml
2. Redireccionar a otra acción (return RedirectToAction("accion", "controlador"))
Saludos!