5. La vista y su modelo
5.1. Introduction
Volvamos a la arquitectura de una aplicación ASP.NET MVC:
![]() |
En el capítulo anterior, estudiamos cómo ASP.NET MVC presentaba la información de la consulta [1] a una acción [2a] en forma de un modelo que podía contener restricciones de validación. Este modelo se proporcionaba como entrada a la acción y lo llamamos el modelo de la acción. Ahora nos enfocamos en el resultado más común de una acción: el tipo [ViewResult], que corresponde a una vista V [3] acompañada de su modelo M [2c]. A este modelo lo llamaremos modelo de la vista V, que no debe confundirse con el modelo de la acción que acabamos de estudiar. Uno es la entrada de la acción, el otro es la salida.
Comencemos por crear un nuevo proyecto [Exemple-03] [1], siempre dentro de la misma solución, del tipo básico ASP.NET MVC:
![]() |
Creemos un controlador llamado [First] [2]. El código generado para este controlador es el siguiente:
using System.Web.Mvc;
namespace Exemple_03.Controllers
{
public class FirstController : Controller
{
public ActionResult Index()
{
return View();
}
}
}
- líneas 7-10: se ha creado una acción [Index]. El tipo del resultado del método [Index] es el de la clase [ActionResult], de la cual derivan la mayoría de los resultados posibles de una acción;
- línea 9: el método [View] de la clase [Controller] (línea 5) devuelve un tipo [ViewResult] que deriva de [ActionResult]. Este método admite numerosas sobrecargas. Veremos algunas de ellas. La principal es la siguiente:
![]() |
- el primer parámetro es el nombre de la vista. Si no se proporciona, se utiliza la vista que lleva el mismo nombre que la acción que genera el [ViewResult], y se buscará en la carpeta [/Views/{controller}], donde {controller} es el nombre del controlador;
- el segundo es la plantilla de la vista. Si no se incluye, la vista no tiene plantilla.
El método [Index] que se muestra a continuación:
public ActionResult Index()
{
return View();
}
solicita que se muestre la vista [/Views/First/Index.cshtml] . No le transmite ningún modelo. Creemos [1] en la carpeta [/Views/First]:
![]() |
y luego, dentro de ella, la vista [Index] [2]:
![]() |
![]() |
Indicamos el nombre de la vista como [3]. Esta se crea como [4]. El código generado es el siguiente:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
</div>
</body>
</html>
Aquí tenemos un código HTML clásico, excepto por las líneas 1 a 3, que son código C#. El programa que administra las vistas se denomina motor de vistas. Se encarga de gestionar todo lo que no sea HTML para convertirlo en HTML. Al final, de hecho, eso es lo que se enviará al cliente. El motor de vistas se llama aquí [Razor]. Permite incluir código C# en una vista. [Razor] interpretará este código C# y generará a partir de él código HTML. A continuación se presentan algunas reglas básicas para incluir código C# en una vista:
- la transición de HTML a C# se realiza al encontrar el carácter @ (línea 1). Si este carácter inicia un bloque de código, se colocarán las llaves (líneas 1 y 3). Si inicia una variable de la que se desea obtener el valor, simplemente se escribirá @variable;
- la conversión de C# a HTML se realiza al encontrar el carácter < (línea 5). A veces, es necesario forzar esta conversión, especialmente cuando se incluye en la página texto sin formato sin la etiqueta HTML. En ese caso, se utilizará la etiqueta <text> para introducir el texto: <text>aquí va el texto sin formato</text>.
La línea 2 anterior indica que la vista [Index] no tiene una página maestra.
Modifiquemos la vista de la siguiente manera:
@{
Layout = null;
string vue = "Index";
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
<h3>Vue @vue</h3>
</div>
</body>
</html>
- línea 3: define una variable C#;
- línea 15: muestra el valor de esta variable.
Ahora solicitemos la vista URL [/First/Index]:
![]() |
El código HTML recibido es el siguiente:
Es un documento HTML puro. Todo el código C# ha desaparecido.
5.2. Usar el [ViewBag] para pasar información a la vista
Creamos una nueva acción llamada [Action01] asociada a la vista [Action01.cshtml]:
![]() |
La acción [Action01] es la siguiente:
// Acción01
public ViewResult Action01()
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View();
}
- línea 4: se utiliza la propiedad [ViewBag] del controlador. Se trata de un objeto dinámico al que se le pueden agregar propiedades, tal como se hace en la línea 4. Este objeto tiene la particularidad de que también es accesible desde la vista. Por lo tanto, es una forma de transmitirle información;
- línea 5: se solicita la vista predeterminada de la acción. Es la vista [/First/Action01.cshtml]. No se le transmite ningún modelo.
La vista [Action01.cshtml] es la siguiente:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action01</title>
</head>
<body>
<div>
<h4>@ViewBag.info</h4>
</div>
</body>
</html>
- línea 14: se muestra la propiedad [ViewBag.info].
Probemos. Solicitamos URL y [/First/Action01]:
![]() |
5.3. Usar un modelo fuertemente tipado para pasar información a la vista
El método anterior tiene el inconveniente de que no permite detectar errores antes de la ejecución. Por lo tanto, si la vista [Action01.cshtml] utiliza el código
<h4>@ViewBag.Info</h4>
se producirá un error porque la propiedad [Info] no existe. La creada por la acción [Action01] se llama [info]. Por lo tanto, se puede utilizar un modelo fuertemente tipado para evitar este inconveniente.
En uno de los ejemplos analizados anteriormente, la acción era la siguiente:
// Acción10
public ContentResult Action10(ActionModel03 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3}, info3={4}, erreurs={5}",
modèle.Email, modèle.Jour, modèle.Info1, modèle.Info2, modèle.Info3, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
La acción [Action10] transmitía a su cliente seis datos (correo electrónico, día, Info1, Info2, Info3, errores) en forma de cadena de caracteres. Vamos a transmitir esta información en un modelo de vista [ViewModel01]. Como este modelo retoma información de [ActionModel03], lo derivaremos de esa clase.
Comenzamos copiando [ActionModel03] del proyecto [Exemple-02] al proyecto actual [Exemple-03]:
![]() |
y cambiamos su espacio de nombres para que coincida con el del proyecto [Exemple-03]:
using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
public class ActionModel03
{
[Required(ErrorMessage = "Le paramètre email est requis")]
[EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
public string Email { get; set; }
[Required(ErrorMessage = "Le paramètre jour est requis")]
[RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
public string Jour { get; set; }
[Required(ErrorMessage = "Le paramètre info1 est requis")]
[MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
public string Info1 { get; set; }
[Required(ErrorMessage = "Le paramètre info2 est requis")]
[MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
public string Info2 { get; set; }
[Required(ErrorMessage = "Le paramètre info3 est requis")]
[MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
[MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
public string Info3 { get; set; }
}
}
- línea 2: el nuevo espacio de nombres;
Luego creamos la clase [ViewModel01]:
![]() |
El código de [ViewModel01] es el siguiente:
namespace Exemple_03.Models
{
public class ViewModel01 : ActionModel03
{
public string Erreurs { get; set; }
}
}
- línea 3: la clase hereda de [ActionModel03] y, por lo tanto, de las propiedades de [Email, Jour, Info1, Info2, Info3];
- línea 5: se le agrega la propiedad [Erreurs].
Ahora escribimos la acción [Action02], la cual:
- acepta como entrada el modelo de acción [ActionModel03];
- y genera como salida el modelo de vista [ViewModel01].
Su código es el siguiente:
// Acción 02
public ViewResult Action02(ActionModel03 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
return View(new ViewModel01(){Email=modèle.Email, Jour=modèle.Jour, Info1=modèle.Info1, Info2=modèle.Info2, Info3=modèle.Info3, Erreurs=erreurs});
}
- línea 1: [Action02] recibe el modelo de acción [ActionModel03]. Devuelve un resultado de tipo [ViewResult];
- línea 4: los errores relacionados con el modelo de acción [ActionModel03] se agrupan en la cadena de caracteres [erreurs]. El método [getErrorMessagesFor] se describió en la página 65 y se incluyó en el controlador [First] del nuevo proyecto;
- línea 5: se invoca el método [View] con un parámetro. Este es el modelo de la vista. No se especifica cuál es esta vista. Por lo tanto, se utilizará la vista predeterminada [/Views/First/Action02]. El modelo de vista [ViewModel01] se instancia e inicializa con los cinco datos del modelo de acción [ActionModel03] y el dato [erreurs] construido en la línea 4.
Ahora construimos la vista [/First/Action02.cshtml]:
![]() |
Su código es el siguiente:
@model Exemple_03.Models.ViewModel01
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action02</title>
</head>
<body>
<h3>Informations du modèle de vue</h3>
<ul>
<li>Email : @Model.Email</li>
<li>Jour : @Model.Jour</li>
<li>Info1 : @Model.Info1</li>
<li>Info2 : @Model.Info2</li>
<li>Info3 : @Model.Info3</li>
<li>Erreurs : @Model.Erreurs</li>
</ul>
</body>
</html>
- La novedad se encuentra en la línea 1. La notación [@model] establece el tipo de plantilla de la vista. A continuación, se hace referencia a esta plantilla mediante la notación [@Model] (líneas 16-21);
- líneas 15-22: la información del modelo se muestra en una lista.
Veamos algunos ejemplos de ejecución de la acción [Action02].
Primero, sin parámetros:
![]() |
luego con parámetros incorrectos:
![]() |
y luego con parámetros correctos:
![]() |
En este ejemplo, la plantilla de la vista [ViewModel01] toma la información de la plantilla de acción [ActionModel03]. Esto suele ocurrir. Por lo tanto, se puede utilizar una sola plantilla que sirva tanto como plantilla de acción como de vista. Creamos un nuevo modelo [ActionModel04]:
![]() |
que tendrá el siguiente aspecto:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
[Bind(Exclude="Erreurs")]
public class ActionModel04
{
// ---------------------- Acción --------------------------------
[Required(ErrorMessage = "Le paramètre email est requis")]
[EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
public string Email { get; set; }
[Required(ErrorMessage = "Le paramètre jour est requis")]
[RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
public string Jour { get; set; }
[Required(ErrorMessage = "Le paramètre info1 est requis")]
[MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
public string Info1 { get; set; }
[Required(ErrorMessage = "Le paramètre info2 est requis")]
[MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
public string Info2 { get; set; }
[Required(ErrorMessage = "Le paramètre info3 est requis")]
[MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
[MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
public string Info3 { get; set; }
// ---------------------- vista --------------------------------
public string Erreurs { get; set; }
}
}
- líneas 8-28: el modelo de la acción con sus restricciones de integridad. Estos campos también formarán parte de la vista;
- línea 31: una propiedad propia del modelo de la vista. Se ha excluido del modelo de la acción mediante la anotación de la línea 5.
Creamos la siguiente acción nueva [Action03]:
// Acción03
public ViewResult Action03(ActionModel04 modèle)
{
modèle.Erreurs = getErrorMessagesFor(ModelState);
return View(modèle);
}
- línea 2: [Action03] recibe el modelo de acción de tipo [ActionModel04];
- línea 5: y devuelve como modelo de vista ese mismo modelo;
- línea 4: complementado con la información [Erreurs];
Solo nos queda crear la vista [/First/Action03.cshtml]:
![]() |
- en [1]: haz clic con el botón derecho en el código de [Action03] y luego en [Ajouter une vue];
- en [2]: el nombre de la vista propuesto por defecto;
- en [3]: indicar que se está creando una vista fuertemente tipada;
- en [4]: seleccionar de la lista desplegable la clase correcta, en este caso la clase [ActionModel04];
- en [5]: la vista creada.
Le asignamos a la vista [Action03] el mismo código que a la vista [Action02]. Solo cambian la plantilla de la vista (línea 1) y el título de la página (línea 11):
@model Exemple_03.Models.ActionModel04
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action03</title>
</head>
<body>
<h3>Informations du modèle de vue</h3>
<ul>
<li>Email : @Model.Email</li>
<li>Jour : @Model.Jour</li>
<li>Info1 : @Model.Info1</li>
<li>Info2 : @Model.Info2</li>
<li>Info3 : @Model.Info3</li>
<li>Erreurs : @Model.Erreurs</li>
</ul>
</body>
</html>
Ahora ejecutemos la acción [Action03] sin parámetros:
![]() |
Los resultados son los mismos que antes. Es común utilizar el mismo modelo tanto para la acción como para la vista, ya que el modelo de la vista suele tomar información del modelo de la acción. Por lo tanto, se utiliza un modelo más amplio, que puede ser utilizado tanto por la acción como por la vista que esta genera. Hay que asegurarse de excluir del enlace de datos la información que no pertenezca al modelo de la acción. De lo contrario, un usuario bien informado podría inicializar partes del modelo de la vista sin nuestro conocimiento.
5.4. [Razor] – primeros pasos
Ahora presentaremos algunos elementos de las vistas [Razor], principalmente las instrucciones foreach y if.
Supongamos que queremos presentar una lista de personas en una tabla HTML. La plantilla de la vista podría ser la siguiente [ViewModel02]:
![]() |
namespace Exemple_03.Models
{
public class ViewModel02
{
public Personne[] Personnes { get; set; }
public ViewModel02()
{
Personnes = new Personne[] { new Personne { Nom = "Pierre", Age = 44 }, new Personne { Nom = "Pauline", Age = 12 } };
}
}
public class Personne
{
public string Nom { get; set; }
public int Age { get; set; }
}
}
- la vista del modelo es la clase [ViewModel02], líneas 3-10;
- línea 5: el modelo cuenta con un array de personas de tipo [Personne] definido en las líneas 12 a 16;
- líneas 6-10: el constructor del modelo inicializa la propiedad [Personnes] de la línea 5 con una tabla de dos personas.
La acción que generará este modelo como salida será la siguiente [Action04]:
// Acción04
public ViewResult Action04()
{
return View(new ViewModel02());
}
- línea 2: la acción no tiene ningún modelo de entrada;
- línea 4: pasa a su vista predeterminada, una instancia de la plantilla [ViewModel02] que acabamos de definir.
La vista [Action04.cshtml] mostrará el modelo [ViewModel02]:
![]() |
El código de la vista [Action04.cshtml] es el siguiente:
@model Exemple_03.Models.ViewModel02
@using Exemple_03.Models
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action04</title>
</head>
<body>
<table border="1">
<thead>
<tr>
<th>Nom</th>
<th>Age</th>
</tr>
</thead>
<tbody>
@foreach (Personne p in Model.Personnes)
{
<tr>
<td>@p.Nom</td>
<td>@p.Age</td>
</tr>
}
</tbody>
</table>
</body>
</html>
- línea 1: la plantilla de la vista;
- línea 2: la importación del espacio de nombres de la clase [Personne] utilizada en la línea 24;
- líneas 16-32: la tabla HTML que muestra a las personas del modelo;
- línea 24: el inicio del código C# se indica con el carácter @. La instrucción [foreach] recorrerá en bucle a todas las personas del modelo;
- líneas 26-27: el carácter < detiene el C# y da inicio al HTML. Luego, nuevamente, el carácter @ para cambiar a C# y escribir el nombre de la persona. A continuación, nuevamente el carácter <, que cambia al modo HTML;
- línea 28: se escribe la edad de la persona.
La ejecución de la acción [Action04] da el siguiente resultado:
![]() |
Otros elementos de una vista pueden alimentarse mediante una colección: las listas, desplegables o no, los botones de opción y las casillas de selección. Veamos el siguiente ejemplo nuevo que muestra una lista desplegable.
El modelo [ViewModel05] será el siguiente:
![]() |
namespace Exemple_03.Models
{
public class ViewModel05
{
public Personne2[] Personnes { get; set; }
public int SelectedId { get; set; }
public ViewModel05()
{
Personnes = new Personne2[] {
new Personne2 { Id = 1, Prénom = "Pierre", Nom = "Martino" },
new Personne2 { Id = 2, Prénom = "Pauline", Nom = "Pereiro" },
new Personne2 { Id = 3, Prénom = "Jacques", Nom = "Alfonso" } };
SelectedId = 2;
}
}
public class Personne2
{
public int Id { get; set; }
public string Nom { get; set; }
public string Prénom { get; set; }
}
}
- línea 18: una clase [Personne2] con tres propiedades;
- línea 3: el modelo [ViewModel05] de la vista;
- línea 5: la lista de personas que se mostrarán en la lista desplegable en el formato [Prénom Nom];
- línea 6: el [Id] de la persona que se seleccionará en la lista desplegable;
- líneas 8-16: el constructor que crea una tabla de tres personas (líneas 10-13) y establece el [Id] de la persona que debe aparecer seleccionada.
La vista [Action05.cshtml] mostrará este modelo:
![]() |
Su código es el siguiente:
@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action05</title>
</head>
<body>
<select>
@foreach (Personne2 p in Model.Personnes)
{
string selected = "";
if (p.Id == Model.SelectedId)
{
selected = "selected=\"selected\"";
}
<option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
}
</select>
</body>
</html>
Las características de la lista desplegable HTML se presentaron en el párrafo 2.5.2.6. Recordémoslas:
Combo | <select size="1" name="cmbValeurs"> <option value="1">opción1</option> <option selected="selected" value="2">opción 2</option> <option value="3">opción 3</option> </select> |
etiqueta HTML | <select size=".." name=".."> <option [selected="selected"] value=”v”>...</option> ... </select> muestra en una lista los textos que se encuentran entre las etiquetas <option>...</option> |
atributos | name="cmbValeurs": nombre del control. size="1": número de elementos visibles de la lista. size="1" convierte la lista en el equivalente a un cuadro combinado. selected="selected": si esta palabra clave está presente para un elemento de la lista, este último aparece seleccionado en la lista. En nuestro ejemplo anterior, el elemento de la lista choix2 aparece como el elemento seleccionado del cuadro combinado cuando este se muestra por primera vez. value=”v”: si el usuario selecciona el elemento, es este valor [v] el que se envía al servidor. Si no se incluye este atributo, se envía al servidor el texto que se muestra y está seleccionado. |
El código de las líneas 17 a 25 genera las etiquetas <option> que se colocan dentro de la etiqueta <select> de la línea 16.
- línea 17: se recorre la lista de personas del modelo;
- línea 20: se verifica si la persona actual es la que debe seleccionarse. Si es así, se prepara el texto selected="selected" que debe insertarse en la etiqueta <option>;
- línea 24: se escribe la etiqueta <option>.
Solicitemos la acción [Action05]:
![]() |
- en [1,2], las personas se muestran en el formato [Prénom Nom];
- En [1,2], la persona seleccionada es aquella cuyo valor en [Id] es igual a 2.
Ahora analicemos el código fuente HTML de la página anterior:
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action05</title>
</head>
<body>
<select>
<option value="1" >Pierre Martino</option>
<option value="2" selected="selected">Pauline Pereiro</option>
<option value="3" >Jacques Alfonso</option>
</select>
</body>
</html>
- líneas 10-12: las tres etiquetas <option> generadas por el código [Razor];
- línea 11: efectivamente, se seleccionó la persona de [Id]=2.
Los dos ejemplos anteriores nos bastarán. Al escribir una vista [Razor], hay que resistir la tentación de incluir lógica en ella. El código C# nos lo permitiría. Sin embargo, en el modelo MVC, la lógica debe estar en la acción o en las capas inferiores [Metier, DAO], pero no en la vista. Incluso al respetar el modelo MVC, es posible que termine habiendo mucha lógica en la vista para calcular valores intermedios. Esto puede indicar que el modelo utilizado no es lo suficientemente detallado. Este debe contener los valores finales que la vista necesita para que no tenga que calcularlos por sí misma. Una buena vista es aquella en la que hay un mínimo de lógica y en la que la estructura HTML de la vista se mantiene clara. Si se inserta demasiado código C#, la estructura HTML puede volverse ilegible.
En el ejemplo anterior, un usuario podría utilizar la lista desplegable y, en ese caso, querríamos saber qué persona ha seleccionado. Para ello, necesitamos un formulario.
5.5. Formulario: primeros pasos
El formulario que se le mostrará al usuario será el siguiente:
![]() |
La plantilla de la vista será la plantilla [ViewModel05] que ya se utilizó anteriormente. La acción que mostrará esta vista será la siguiente:
// Acción06-GET
[HttpGet]
public ViewResult Action06()
{
return View("Action06Get",new ViewModel05());
}
- línea 2: la acción solo puede ser solicitada mediante un comando HTTP GET;
- línea 5: la vista [/First/Action06Get.cshtml] se mostrará utilizando como modelo una instancia de tipo [ViewModel05].
La vista [/First/Action06Get.cshtml] será la siguiente:
![]() |
@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action06-GET</title>
</head>
<body>
<h3>Action06 - GET</h3>
<p>Choisissez une personne</p>
<form method="post" action="/First/Action06">
<select name="personneId">
@foreach (Personne2 p in Model.Personnes)
{
string selected = "";
if (p.Id == Model.SelectedId)
{
selected = "selected=\"selected\"";
}
<option value="@p.Id" @selected>@p.Prénom @p.Nom</option>
}
</select>
<input name="valider" type="submit" value="Valider" />
</form>
</body>
</html>
Las principales novedades son las siguientes:
- línea 18: para que el navegador pueda transmitir la información ingresada por un usuario, necesitamos un formulario. La etiqueta <form> de las líneas 18 y 31 es la que lo delimita.
La etiqueta HTML <form> se presentó en el párrafo 2.5.2.1. Recordemos sus características:
formulario | |
etiqueta HTML | <form name="..." method="..." action="...">...</form> |
atributos | name="frmexemplo": nombre del formulario — opcional method="..." : método que utiliza el navegador para enviar al servidor web los valores recopilados en el formulario action="...": URL a la que se enviarán los valores recopilados en el formulario. Un formulario web está delimitado por las etiquetas <form>...</form>. El formulario puede tener un nombre (name="xx"). Esto aplica a todos los controles que se pueden encontrar en un formulario. El objetivo de un formulario es recopilar la información ingresada por el usuario mediante el teclado o el ratón y enviarla a una URL del servidor web. ¿Cuál? La que se indica en el atributo action="URL". Si este atributo no está presente, la información se enviará al documento en el que se encuentra el formulario. Un cliente web puede utilizar dos métodos diferentes, llamados POST y GET, para enviar datos a un servidor web. El atributo method="méthode", con method igual a GET o POST, de la etiqueta <form> le indica al navegador el método que debe utilizar para enviar la información recopilada en el formulario a la dirección URL especificada por el atributo action="URL". Cuando no se especifica el atributo method, se utiliza el método GET por defecto. |
- línea 18: se observa que los valores del formulario se enviarán a URL [/First/Action06] mediante un comando HTTP POST;
- línea 30: un formulario debe tener un botón de tipo [submit]. Este es el que activa el envío de los valores ingresados a URL, especificado por el atributo [action] de la etiqueta <form>.
¿Qué es exactamente lo que transmitirá el navegador cuando el usuario haga clic en el botón [Valider]? Esto se explicó en el párrafo 2.5.3.1. Recordemos lo que se dijo:
control HTML | visual | valor(es) devuelto(s) |
<input type="radio" value="Sí" name="R1"/>Sí <input type="radio" name="R1" value="no" checked="checked"/>No | R1=Sí - el valor del atributo value del botón de opción marcado por el usuario. | |
<input type="checkbox" name="C1" value="uno"/>1 <input type="checkbox" name="C2" value="dos" checked="checked"/>2 <input type="checkbox" name="C3" value="tres"/>3 | C1=uno C2=dos - valores de los atributos value de las casillas marcadas por el usuario | |
<input type="text" name="txtSaisie" size="20" value="unas palabras"/> | txtEntrada = programación+Web - texto escrito por el usuario en el campo de entrada. Los espacios se han reemplazado por el signo + | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"/> | txtMdp=estoesecreto - texto escrito por el usuario en el campo de entrada | |
<textarea rows="2" name="areaSaisie" cols="20"> línea1 línea 2 línea 3 </textarea> | areaIntroducción=los+fundamentos+de+la%0D%0A programación+web - texto escrito por el usuario en el campo de entrada. %OD%OA es el marcador de fin de línea. Los espacios se han reemplazado por el signo + | |
<select size="1" name="cmbValeurs"> <option value='1'>opción1</option> <option selected="selected" value='2'>opción2</option> <option value='3'>opción 3</option> </select> | cmbValores=3 - atributo [value] del elemento seleccionado por el usuario | |
<select size="3" name="lst1"> <option selected="selected" value='1'>lista1</option> <option value='2'>lista2</option> <option value='3'>lista3</option> <option value='4'>lista4</option> <option value='5'>lista5</option> </select> | ![]() | lst1=3 - atributo [value] del elemento seleccionado por el usuario |
<select size="3" name="lst2" multiple="multiple"> <option selected="selected" value='1'>lista1</option> <option value='2'>lista2</option> <option selected="selected" value='3'>lista3</option> <option value='4'>lista4</option> <option value='5'>lista5</option> </select> | lst2=1 lst2=3 - Atributos [value] de los elementos seleccionados por el usuario | |
<input type="submit" value="Enviar" name="cmdRenvoyer"/> | cmdRenvoyer=Enviar - nombre y atributo value del botón que se utilizó para enviar los datos del formulario al servidor | |
<input type="hidden" name="secret" value="uneValeur"/> | secret=unValor - atributo value del campo oculto |
En nuestro formulario, tenemos dos etiquetas que pueden enviar un valor:
<select name="personneId">
...
</select>
y
<input name="valider" type="submit" value="Valider" />
Si el usuario selecciona a la persona n.º 2, los valores se enviarán de la siguiente forma:
Los nombres de los parámetros son los de los atributos [name] de las etiquetas a las que se refiere el POST. Sin este atributo, las etiquetas no envían ningún valor. Por lo tanto, en el ejemplo anterior, se podría omitir el atributo name="valider" del botón [submit]. El valor enviado es el atributo [value] del botón. En este caso, esa información no nos interesa. A veces, los formularios tienen varios botones del tipo [submit]. En ese caso, es importante saber qué botón se presionó. Por lo tanto, se asignará el atributo [name] a los distintos botones.
La etiqueta <select> está compuesta por una secuencia de etiquetas <option>:
<select name="personneId">
<option value="1" >Pierre Martino</option>
<option value="2" selected="selected">Pauline Pereiro</option>
<option value="3" >Jacques Alfonso</option>
</select>
Se envía el valor del atributo [value] de la opción seleccionada. Si no existe este atributo, se envía el texto que muestra la opción, por ejemplo, [Pierre Martino].
La cadena
se publicará en la siguiente URL [/First/Action06]:
// Acción06-POST
[HttpPost]
public ViewResult Action06(ActionModel06 modèle)
{
return View("Action06Post",modèle);
}
Quizás recordemos que ya teníamos una acción [Action06]:
// Acción06-GET
[HttpGet]
public ViewResult Action06()
{
return View("Action06Get",new ViewModel05());
}
Es posible tener dos acciones con el mismo nombre, siempre y cuando no procesen los mismos comandos HTTP:
- [Action06] de la línea 3 gestiona un POST (línea 2);
- [Action06] de la línea c gestiona un GET (línea b).
La acción [Action06], que gestiona el POST, recibirá la siguiente cadena de parámetros:
Necesitamos un modelo de acción para encapsular estos valores. Será el siguiente modelo [ActionModel06]:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
public class ActionModel06
{
[Required(ErrorMessage = "Le paramètre [personneId] est requis")]
public int PersonneId { get; set; }
[Required(ErrorMessage = "Le paramètre [valider] est requis")]
public string Valider { get; set; }
}
}
La acción [Action06] recibe esta plantilla y la transmite tal cual a la vista [Action06Post] (línea 5 de la acción) siguiente:
![]() |
@model Exemple_03.Models.ActionModel06
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action06Post</title>
</head>
<body>
<h3>Action06 - POST</h3>
Valeurs postées :
<ul>
<li>ID de la personne sélectionnée : @Model.PersonneId</li>
<li>Commande utilisée : @Model.Valider</li>
</ul>
</body>
</html>
La plantilla se muestra en las líneas 18 y 19.
Veamos un ejemplo:
![]() |
En [1] se selecciona la tercera persona de [Id], que es igual a 3. En [2] se envía el formulario. En [3], los valores recibidos. En [4,5], se observa que se ha llamado a la misma URL, una vez mediante un GET [4], y la otra por un POST y un [5]. Esto no se observa en el URL.
En la vista que se muestra después del POST, tal vez se prefiera ver los nom y prénom de la persona seleccionada en lugar de su número. Por lo tanto, es necesario actualizar la vista del POST y su modelo.
Creamos una acción [Action07] para manejar este caso. Esta acción deberá utilizar la sesión del usuario para almacenar allí la lista de personas. Seguiremos el modelo analizado en el párrafo 4.10, que permite incluir los datos de ámbito [Application] y [Session] en el modelo de la acción.
El modelo de la sesión será la siguiente clase [SessionModel]:
![]() |
namespace Exemple_03.Models
{
public class SessionModel
{
public Personne2[] Personnes { get; set; }
}
}
- línea 2: la sesión almacenará la lista de personas que aparecen en la lista desplegable;
Debemos vincular el tipo anterior [SessionModel] a un binder al que llamaremos [SessionModelBinder]. Este será el mismo que el descrito en la página 82:
![]() |
using System.Web.Mvc;
namespace Exemple_03.Infrastructure
{
public class SessionModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// se devuelven los datos de ámbito [Session]
return controllerContext.HttpContext.Session["data"];
}
}
}
La vinculación entre el modelo [SessionModel] y sus modelos binder y [SessionModelBinder] se realiza en [Global.asax]:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
...
// enlazadores de modelos
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
// Sesión
public void Session_Start()
{
Session["data"] = new SessionModel();
}
}
- línea 8: la vinculación del modelo con su binder se realiza en [Application_Start];
- línea 13: se inicia una instancia de tipo [SessionModel] en la sesión asociada a la clave [data].
Una vez hecho esto, la acción [Action07] es la siguiente:
// Acción07-GET
[HttpGet]
public ViewResult Action07(SessionModel session)
{
ViewModel05 modèleVue = new ViewModel05();
session.Personnes= modèleVue.Personnes;
return View("Action07Get", modèleVue);
}
- línea 3: la acción recupera un tipo [SessionModel], es decir, el dato de ámbito [Session] asociado a la clave [data];
- línea 5: se crea el modelo de la vista;
- línea 6: se coloca en la sesión la tabla de personas. La necesitaremos en la siguiente consulta, la de POST. El protocolo HTTP es un protocolo sin estado. Es necesario utilizar una sesión para disponer de memoria entre consultas. Una sesión es específica de un usuario y la administra el servidor web;
- línea 7: se muestra la vista [Action07Get.cshtml]. Es la siguiente:
@model Exemple_03.Models.ViewModel05
@using Exemple_03.Models
...
<body>
<h3>Action07 - GET</h3>
<p>Choisissez une personne</p>
<form method="post" action="/First/Action07">
....
</form>
</body>
</html>
Es idéntica a la vista [Action06Get.cshtml] que ya hemos analizado. La principal diferencia se encuentra en la línea 7: la vista URL a la que se enviarán los valores del formulario. Estos serán procesados por la siguiente acción [Action07]:
// Acción07-POST
[HttpPost]
public ViewResult Action07(SessionModel session, ActionModel06 modèle)
{
Personne2 personne = session.Personnes.Where(p => p.Id == modèle.PersonneId).First<Personne2>();
string strPersonne = string.Format("{0} {1}", personne.Prénom, personne.Nom);
return View("Action07Post", (object)strPersonne);
}
- línea 3: los valores enviados se encapsulan en el modelo de acción [ActionModel06], que ya se utilizó anteriormente (a continuación):
using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
public class ActionModel06
{
[Required(ErrorMessage = "Le paramètre [personneId] est requis")]
public int PersonneId { get; set; }
[Required(ErrorMessage = "Le paramètre [valider] est requis")]
public string Valider { get; set; }
}
}
- línea 3: el primer parámetro es el dato de ámbito [Session] asociado a la clave [data];
- línea 5: una consulta LINQ recupera a la persona con el [Id] que se publicó;
- línea 6: se construye la cadena de caracteres que debe mostrar la vista [Action07Post] (línea 8);
- línea 7: para llamar al constructor correcto [View], hay que cambiar el tipo [string] por [object].
La vista [Action07Post.cshtml] es la siguiente:
@model string
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action07-Post</title>
</head>
<body>
<h3>Action07-POST</h3>
Vous avez sélectionné [@Model].
</body>
</html>
- línea 1: el modelo es de tipo [string];
- línea 16: se muestra la cadena de caracteres.
A continuación se muestra un ejemplo de ejecución:
![]() | ![]() |
5.6. Formulario: un ejemplo completo
En el apartado 2.5.2.1, analizamos el siguiente formulario HTML:
1 ![]() |
Ahora analizaremos una acción [Action08Get] que muestra (GET) este formulario y una acción [Action08Post] que procesa (POST) los valores ingresados por el usuario. Un esquema clásico.
La plantilla de la vista [1] anterior será una instancia de la clase [ViewModel08]. Esta clase será a la vez:
- el modelo de la vista generada por un GET en la acción [Action08Get];
- el modelo de la acción [Action08Post] para una solicitud POST.
![]() |
![]() |
5.6.1. La plantilla de ámbito [Application]
Supondremos que los elementos que muestran los botones de radio, las casillas de selección y las distintas listas son datos del ámbito [Application]. Un caso frecuente. Esta información proviene de un archivo de configuración o de una base de datos que se procesan al iniciar la aplicación en el método [Application_Start] de [Global.asax]. Este método se desarrolla de la siguiente manera:
protected void Application_Start()
{
....
// enlazadores de modelos
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
// datos de ámbito [Application]
Application["data"] = new ApplicationModel();
}
- línea 7: el tipo [ApplicationModel], que describiremos en breve, se asocia al enlazador de datos [ApplicationModelBinder], que ya presentamos en la página 82;
- línea 10: se registra una instancia del tipo [ApplicationModel] en el diccionario de la aplicación, asociada a la clave [data].
La clase [ApplicationModel] sirve para encapsular todos los datos del ámbito [Application]. En este caso, encapsulará los datos que debe mostrar el formulario:
![]() |
namespace Exemple_03.Models
{
public class ApplicationModel
{
// las colecciones que se mostrarán en el formulario
public Item[] RadioButtonFieldItems { get; set; }
public Item[] CheckBoxesFieldItems { get; set; }
public Item[] DropDownListFieldItems { get; set; }
public Item[] SimpleChoiceListFieldItems { get; set; }
public Item[] MultipleChoiceListFieldItems { get; set; }
// inicialización de campos y colecciones
public ApplicationModel()
{
RadioButtonFieldItems = new Item[]{
new Item {Value="1",Label="oui"},
new Item {Value="2", Label="non"}
};
CheckBoxesFieldItems = new Item[]{
new Item {Value="1",Label="1"},
new Item {Value="2", Label="2"},
new Item {Value="3", Label="3"}
};
DropDownListFieldItems = new Item[]{
new Item {Value="1",Label="choix1"},
new Item {Value="2", Label="choix2"},
new Item {Value="3", Label="choix3"}
};
SimpleChoiceListFieldItems = new Item[]{
new Item {Value="1",Label="liste1"},
new Item {Value="2", Label="liste2"},
new Item {Value="3", Label="liste3"},
new Item {Value="4", Label="liste4"},
new Item {Value="5", Label="liste5"}
};
MultipleChoiceListFieldItems = new Item[]{
new Item {Value="1",Label="liste1"},
new Item {Value="2", Label="liste2"},
new Item {Value="3", Label="liste3"},
new Item {Value="4", Label="liste4"},
new Item {Value="5", Label="liste5"}
};
}
// el elemento de las colecciones
public class Item
{
public string Label { get; set; }
public string Value { get; set; }
}
}
}
- líneas 45-49: el elemento de las diferentes colecciones del formulario. [Label] es el texto que muestra el elemento del formulario, [Value] el valor enviado por este elemento cuando se selecciona;
- línea 6: la colección que muestra el botón de opción;
- línea 7: la colección que muestran las casillas de selección;
- línea 8: la colección que muestra la lista desplegable;
- línea 9: la colección que muestra la lista de selección única;
- línea 10: la colección que muestra la lista de selección múltiple;
- líneas 13-43: estas colecciones son inicializadas por el constructor sin parámetros de la clase.
Las diferentes colecciones alimentarán el siguiente formulario:
![]() |
5.6.2. El modelo de la acción [Action08Get]
El formulario anterior se mostrará mediante la siguiente acción [Action08Get]:
// Acción08-GET
[HttpGet]
public ViewResult Action08Get(ApplicationModel application)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View("Formulaire", new ViewModel08(application));
}
- línea 2: [Action08Get] solo responderá a un comando [GET];
- línea 3: recibe como parámetro el modelo de la aplicación que acabamos de describir;
- línea 5: inicializa una información en el contenedor dinámico [ViewBag];
- línea 6: muestra la vista [/First/Formulaire.cshtml] con el modelo [ViewModel08]. Este modelo será el del formulario presentado anteriormente. Para ello, se le pasa al constructor el modelo de la aplicación que define los elementos que se deben mostrar.
5.6.3. El modelo de la vista [Formulaire]
La clase [ViewModel08] será el modelo del formulario. Esta clase es la siguiente:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
namespace Exemple_03.Models
{
public class ViewModel08
{
// los campos de entrada
public string RadioButtonField { get; set; }
public string[] CheckBoxesField { get; set; }
public string TextField { get; set; }
public string PasswordField { get; set; }
public string TextAreaField { get; set; }
public string DropDownListField { get; set; }
public string SimpleChoiceListField { get; set; }
public string[] MultipleChoiceListField { get; set; }
// las colecciones que se mostrarán en el formulario
public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }
// constructores
public ViewModel08()
{
}
public ViewModel08(ApplicationModel application)
{
// inicialización de colecciones
RadioButtonFieldItems = application.RadioButtonFieldItems;
CheckBoxesFieldItems = application.CheckBoxesFieldItems;
DropDownListFieldItems = application.DropDownListFieldItems;
SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
// inicialización de campos
RadioButtonField = "2";
CheckBoxesField = new string[] { "2" };
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
}
}
}
- En un formulario hay dos tipos de elementos: los que se muestran y los que se pueden ingresar;
- las líneas 20-24 definen los elementos que se mostrarán. Estas son las diferentes colecciones del formulario. Se encuentran en el modelo de la aplicación (líneas 34-38);
- líneas 10-17: definen los campos de entrada del formulario;
- línea 10: [RadioButtonField] recuperará el valor enviado por las siguientes líneas del formulario:
<!-- los botones de opción -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input type="radio" name="RadioButtonField" value="1" />oui
<input type="radio" name="RadioButtonField" value="2" checked="checked"/>non
</td>
</tr>
Cabe destacar, en las líneas 5 y 6, que el atributo [name] de los dos botones de opción es el nombre de la propiedad que se inicializará. En los datos enviados, se encontrará una cadena con el siguiente formato:
param1=val1&RadioButtonField=2¶m2=val2
si el usuario ha marcado la opción denominada [non]. De hecho, lo que se envía es el atributo [value] de la opción marcada.
- línea 11: [CheckBoxesField] recuperará los valores enviados por las siguientes líneas del formulario:
<!-- casillas de selección -->
<tr>
<td>Cases à cocher</td>
<td>
<input type="checkbox" name="CheckBoxesField" value="1" />1
<input type="checkbox" name="CheckBoxesField" value="2" checked="checked"/>2
<input type="checkbox" name="CheckBoxesField" value="3" />3
</td>
Cabe destacar, en las líneas 5 y 6, que el atributo [name] de las casillas de selección es el nombre de la propiedad que se va a inicializar. En los datos enviados, se encontrará una cadena con el siguiente formato:
param1=val1&CheckBoxesField=2&CheckBoxesField=3¶m2=val2
si el usuario ha marcado las casillas de selección denominadas [2] y [3]. Lo que se envía es el atributo [value] de las opciones marcadas. Dado que se pueden enviar varios parámetros con el mismo nombre, [CheckBoxesField] es una matriz de valores y no un valor simple. Si no se marca ninguna casilla, el parámetro [CheckBoxesField] no aparecerá en la cadena enviada y la propiedad del mismo nombre en el modelo no se inicializará. Esto puede resultar problemático, como veremos.
- línea 12: [TextField] recuperará el valor enviado por las siguientes líneas del formulario:
<!-- el campo de entrada de texto de una sola línea -->
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="TextField" value="quelques mots" size="30" />
</td>
</tr>
En la línea 5, el atributo [name] del campo de entrada es el nombre de la propiedad que se va a inicializar. En los datos enviados, encontraremos una cadena con el siguiente formato:
param1=val1&TextField=abcdef¶m2=val2
si el usuario ha ingresado [abcdef] en el campo de entrada.
- línea 13: [PasswordField] recuperará el valor enviado por las siguientes líneas del formulario:
<!-- el campo de entrada de contraseña -->
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="PasswordField" value="secret" size="30" />
</td>
</tr>
En la línea 5, el atributo [name] del campo de entrada es el nombre de la propiedad que se va a inicializar. En los datos enviados, se encontrará una cadena con el siguiente formato:
param1=val1&PasswordField=abcdef¶m2=val2
si el usuario ha ingresado [abcdef] en el campo de entrada.
- línea 14: [TextAreaField] recuperará el valor enviado por las siguientes líneas del formulario:
<!-- el campo de entrada de texto de varias líneas -->
<tr>
<td>Boîte de saisie</td>
<td>
<textarea name="TextAreaField" cols="40" rows="3">ligne1
ligne2</textarea>
</td>
</tr>
Línea 5: el atributo [name] del campo de entrada es el nombre de la propiedad que se va a inicializar. En los datos enviados, se encontrará una cadena con el siguiente formato:
param1=val1&TextAreaField=abcdef%0D%OAhijk¶m2=val2
si el usuario ha ingresado [abcdef] seguido de un salto de línea y de [ijk] en el campo de entrada.
- línea 15: [DropDownListField] recuperará el valor enviado por las siguientes líneas del formulario:
<!-- la lista desplegable -->
<tr>
<td>Liste déroulante</td>
<td>
<select name="DropDownListField">
<option value="1" >choix1</option>
<option value="2" selected="selected">choix2</option>
<option value="3" >choix3</option>
</select>
</tr>
En la línea 5, el atributo [name] de la etiqueta <select> es el nombre de la propiedad que se va a inicializar. En los datos enviados, se encontrará una cadena con el siguiente formato:
param1=val1&DropDownListField=1¶m2=val2
si el usuario ha seleccionado la opción [choix1]. Lo que se envía es el atributo [value] de la opción seleccionada.
- línea 16: [SingleChoiceListField] recuperará el valor enviado por las siguientes líneas del formulario:
<!-- la lista de opción única -->
<tr>
<td>Liste à choix unique</td>
<td>
<select name="SimpleChoiceListField" size="3">
<option value="1" >liste1</option>
<option value="2" >liste2</option>
<option value="3" selected="selected">liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
</select>
</tr>
En la línea 5, el atributo [name] de la etiqueta <select> es el nombre de la propiedad que se va a inicializar. Es el atributo [size="3"] el que hace que no haya una lista desplegable. En los datos enviados, se encontrará una cadena con el siguiente formato:
param1=val1&SimpleChoiceListField=3¶m2=val2
si el usuario ha seleccionado la opción [liste3]. Es el atributo [value] de la opción seleccionada el que se envía. El parámetro [SingleChoiceListField] puede no aparecer en la cadena enviada si no se ha seleccionado ningún elemento.
- línea 17: [MultipleChoiceListField] recuperará los valores enviados por las siguientes líneas del formulario:
<!-- la lista de opción múltiple -->
<tr>
<td>Liste à choix multiple</td>
<td>
<select name="MultipleChoiceListField" size="3" multiple="multiple">
<option value="1" selected="selected">liste1</option>
<option value="2" >liste2</option>
<option value="3" selected="selected">liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
</select>
</tr>
Línea 5: el atributo [name] de la etiqueta <select> es el nombre de la propiedad que se va a inicializar. El atributo [size="3"] es el que impide que aparezca un menú desplegable, y el atributo [multiple] es el que permite al usuario seleccionar varios elementos al mantener presionada la tecla [Ctrl]. En los datos enviados, se encontrará una cadena con el siguiente formato:
param1=val1&MultipleChoiceListField=1&MultipleChoiceListField=3¶m2=val2
si el usuario ha seleccionado las opciones [liste1] y [liste3]. Lo que se envía es el atributo [value] de las opciones seleccionadas. Debido a que se pueden enviar varios parámetros con el mismo nombre, [MultipleChoiceListField] es una matriz de valores y no un valor simple. Si no se marca ninguna casilla, el parámetro [MultipleChoiceListField] no aparecerá en la cadena enviada y la propiedad del mismo nombre en el modelo no se inicializará.
Los distintos campos de entrada presentados anteriormente recibirán los valores enviados por el formulario. También se pueden inicializar antes de enviar el formulario. Esto es lo que se ha hecho aquí:
// inicialización de campos
RadioButtonField = "2";
CheckBoxesField = new string[] { "2" };
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
Si estos valores se hubieran obtenido tras un envío del formulario con el código POST, significaría que el usuario:
- línea 2: marcado la opción [non] del botón de radio;
- línea 3: marcado la opción [2] de las casillas de selección;
- línea 4: escrito [quelques mots] en el campo de entrada;
- línea 5: ha escrito [secret] como contraseña;
- línea 6: ingresó [ligne1\nligne2] en el campo de entrada de varias líneas;
- línea 7: seleccionó la opción [choix2] de la lista desplegable;
- línea 8: se seleccionó la opción [liste3] de la lista de opción única;
- línea 9: seleccionó las opciones [liste1] y [liste3] de la lista de selección múltiple;
Vamos a suponer que se ha ejecutado un POST y que queremos devolver el formulario tal como se ingresó. Esto es lo que se hace, por ejemplo, cuando se devuelve al usuario un formulario con errores. Este se devuelve tal como se ingresó.
5.6.4. La vista [Formulaire]
La vista [/First/Formulaire.cshtml] muestra el formulario:
![]() |
@model Exemple_03.Models.ViewModel08
@using Exemple_03.Models
@{
Layout = null;
}
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Formulaire</title>
</head>
<body>
<form method="post" action="Action08Post">
<h2>Formulaire ASP.NET MVC</h2>
<h3>Affiché par : @ViewBag.info</h3>
<table>
<thead></thead>
<tbody>
<!-- los botones de radio -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
@foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
{
string strChecked = item.Value == @Model.RadioButtonField ? "checked=\"checked\"" : "";
<input type="radio" name="RadioButtonField" value="@item.Value" @strChecked/>@item.Label
<text/>
}
</td>
</tr>
...
</tbody>
</table>
<input type="submit" value="Valider" />
</form>
</body>
</html>
- línea 1: [ViewModel08] es la plantilla del formulario;
- línea 12: la etiqueta <form> del formulario. Este se enviará mediante el método [POST] (atributo method) al URL [/First/Action08Post] (atributo action);
- línea 33: el botón de tipo [submit] que sirve para enviar el formulario;
- líneas 22-27: muestran los botones de opción:
- línea 22: se recorre la colección mostrada por el botón de opción;
- línea 24: el botón que tiene como atributo [value] el valor de la propiedad [RadioButtonField] debe estar marcado. Para ello, debe tener el atributo [checked="checked"];
- línea 25: generación de la etiqueta <input type="radio"> con el valor [@item.Value] y el texto [@item.Label];
- línea 26: la etiqueta <text/> no es una etiqueta HTML reconocida. Está ahí para [Razor]. Al encontrarla, [Razor] generará un salto de línea. Esto no afecta al formulario que se muestra, pero sí al código HTML generado. Las etiquetas <input type="radio"> quedan entonces en dos líneas diferentes en lugar de estar en la misma línea. Esto hace que el código sea más legible cuando, desde el navegador, se solicita ver el código fuente de la página que se muestra;
Repasamos los demás elementos de la vista:
<!-- casillas de selección -->
<tr>
<td>Cases à cocher</td>
<td>
@{
foreach (ApplicationModel.Item item in @Model.CheckBoxesFieldItems)
{
string strChecked = @Model.CheckBoxesField.Contains(item.Value) ? "checked=\"checked\"" : "";
<input type="checkbox" name="CheckBoxesField" value="@item.Value" @strChecked/>@item.Label
<text/>
}
}
</td>
- línea 6: recorremos la colección que muestran las casillas de selección;
- línea 8: se debe marcar una casilla que tenga como atributo [value] uno de los valores de la propiedad [CheckBoxesField]. Para ello, debe tener el atributo [checked="checked"]. Se utiliza una expresión LINQ que permite determinar si un valor está presente en una tabla;
- línea 25: generación de la etiqueta <input type="checkbox"> con el valor [@item.Value] y el texto [@item.Label];
<!-- el campo de entrada de texto de una sola línea -->
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="TextField" value="@Model.TextField" size="30" />
</td>
</tr>
<!-- el campo de entrada de contraseña -->
<tr>
<td>Mot de passe</td>
<td>
<input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
</td>
</tr>
<!-- el campo de entrada de texto de varias líneas -->
<tr>
<td>Boîte de saisie</td>
<td>
<textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
</td>
</tr>
- líneas 5, 12: se le asigna al atributo [value] de la etiqueta el valor de la plantilla;
- línea 19: lo mismo, pero con una sintaxis diferente.
<!-- la lista desplegable -->
<tr>
<td>Liste déroulante</td>
<td>
<select name="DropDownListField">
@{
foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
{
string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
- línea 7: se recorre la colección que muestra la lista desplegable;
- línea 9: se debe seleccionar una opción que tenga como atributo [value] el valor de la propiedad [DropDownListField]. Para ello, debe tener el atributo [selected="selected"];
- línea 25: generación de la etiqueta <option value="valeur">libellé</option> con el valor [@item.Value] y el texto [@item.Label];
<!-- la lista de opción única -->
<tr>
<td>Liste à choix unique</td>
<td>
<select name="SimpleChoiceListField" size="3">
@{
foreach (ApplicationModel.Item item in @Model.SimpleChoiceListFieldItems)
{
string strChecked = item.Value == @Model.SimpleChoiceListField ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
La explicación es la misma que para la lista desplegable.
<!-- la lista de selección múltiple -->
<tr>
<td>Liste à choix multiple</td>
<td>
<select name="MultipleChoiceListField" size="3" multiple="multiple">
@{
foreach (ApplicationModel.Item item in @Model.MultipleChoiceListFieldItems)
{
string strChecked = @Model.MultipleChoiceListField.Contains(item.Value) ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
- línea 7: se recorre la colección que muestra la lista;
- línea 9: se debe seleccionar una opción que tenga como atributo [value] uno de los valores de la propiedad [MultipleChoiceListField]. Para ello, debe tener el atributo [selected="selected"]. Se utiliza una expresión LINQ que permite determinar si un valor está contenido en una matriz;
- línea 10: generación de la etiqueta valeurlibellé</option> con el valor [@item.Value] y el texto [@item.Label];
5.6.5. Procesamiento del POST del formulario
Hemos visto que el formulario se enviaría a la acción [Action08Post]:
<form method="post" action="Action08Post">
La acción [Action08Post] es la siguiente:
// Acción08-POST
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
ViewModel08 modèle = new ViewModel08(application);
TryUpdateModel(modèle,posted);
return View("Formulaire", modèle);
}
- línea 3: la plantilla de la aplicación se pasa como parámetro, junto con los valores enviados. Estos están disponibles en un tipo [FormCollection]. El valor del parámetro [RadioButtonField] enviado se obtiene mediante la expresión posted[" RadioButtonField"]. De esta forma se obtiene una cadena de caracteres o el puntero null. Si se escribe posted[" CheckBoxesField"], se obtendrá una matriz de cadenas de caracteres o el puntero null;
- ¿por qué no escribir:
public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)
Hay dos razones:
- la primera es que el marco de trabajo instanciará el modelo [ViewModel08] con el constructor sin parámetros, lo que hará que no se inicialicen las colecciones del modelo;
- la segunda es que queremos controlar qué se incluye en el modelo. Sabemos que hay cuatro fuentes posibles para el modelo: los parámetros de un GET, de un POST, de la ruta utilizada y los de un archivo uploadé. Aquí queremos inicializar el modelo únicamente con los valores enviados.
- línea 6: se instancia el modelo utilizando el constructor adecuado;
- línea 7: lo inicializamos con los valores enviados. Tras esta operación, el modelo corresponde a la información ingresada por el usuario;
- línea 8: se vuelve a mostrar el formulario. El usuario lo verá tal como lo ingresó.
Veamos un ejemplo:
![]() |
En [2], el resultado de [POST] refleja correctamente lo que se ingresó en [1].
5.6.6. Manejo de anomalías en POST
Ya mencionamos que, si no se marcaba ni se seleccionaba ningún valor para los campos de [CheckBoxesField, SimpleChoiceListField, MultipleChoiceListField], los parámetros correspondientes no formaban parte de la cadena enviada y, por lo tanto, las propiedades del modelo con los mismos nombres no se inicializaban.
Veamos el siguiente ejemplo:
![]() |
- en [1], no se marcó ninguna casilla;
- en [2], el [POST] devuelve una casilla marcada.
La explicación es la siguiente:
- como no hay ninguna casilla marcada, el parámetro [CheckBoxesField] no forma parte de los valores enviados;
- la acción [Action08Post] procede de la siguiente manera:
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = ...
ViewModel08 modèle = new ViewModel08(application);
TryUpdateModel(modèle,posted);
return View("Formulaire", modèle);
}
- línea 5: se instancia el modelo del formulario. Sin embargo, el constructor utilizado asigna la tabla ["2"] a la propiedad [CheckBoxesField];
- línea 6: los valores contabilizados se registran en el modelo. Dado que el parámetro [CheckBoxesField] no forma parte de los valores enviados, la propiedad del mismo nombre no se asigna. Por lo tanto, conserva su valor ["2"], lo que hace que, al mostrarse, la casilla n.º 2 aparezca marcada cuando no debería estarlo.
Este problema se puede resolver de varias maneras. Optamos por resolverlo en el código de la acción [Action08Post]:
// Acción08-POST
[HttpPost]
public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
ViewModel08 modèle = new ViewModel08(application);
TryUpdateModel(modèle,posted);
// procesamiento de valores no enviados
if (posted["CheckBoxesField"] == null)
{
modèle.CheckBoxesField = new string[] { };
}
if (posted["SimpleChoiceListField"] == null)
{
modèle.SimpleChoiceListField = "";
}
if (posted["MultipleChoiceListField"] == null)
{
modèle.MultipleChoiceListField = new string[] { };
}
// Visualización del formulario
return View("Formulaire", modèle);
}
- líneas 9-20: se verifica si ciertos parámetros se han enviado o no. Si no es así, se inicializan con el valor que corresponde a la ausencia de entrada por parte del usuario. La verificación no se realizó para la lista desplegable, la cual siempre tiene un elemento seleccionado, lo cual no ocurre con las demás listas.
Se invita al lector a probar esta nueva versión.
5.7. Uso de métodos especializados en la generación de formularios
5.7.1. El nuevo formulario
Creamos un nuevo formulario [Formulaire2.cshtml] que generará un formulario idéntico al anterior:
![]() |
Volvamos al código utilizado para generar la lista desplegable del formulario:
<!-- la lista desplegable -->
<tr>
<td>Liste déroulante</td>
<td>
<select name="DropDownListField">
@{
foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
{
string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
<option value="@item.Value" @strChecked>@item.Label</option>
}
}
</select>
</tr>
Este código presenta dos inconvenientes:
- el más importante es que, debido a la complejidad del código, se pierde de vista la naturaleza del componente, en este caso una lista desplegable;
- línea 5: si nos equivocamos en el nombre de la propiedad del modelo que se utilizará como atributo [name], no nos daremos cuenta hasta el momento de la ejecución.
ASP.NET MVC ofrece métodos especializados llamados [HTML Helpers] que, como su nombre lo indica, tienen como objetivo facilitar la generación del HTML, especialmente para los formularios. Con estas clases, la lista desplegable anterior se escribe de la siguiente manera:
<!-- la lista desplegable -->
<tr>
<td>Liste déroulante</td>
<td>@Html.DropDownListFor(m => m.DropDownListField,
new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
</td>
</tr>
La lista desplegable se genera mediante las líneas 4 y 5. El código es mucho menos complejo. El código HTML generado para la lista desplegable es el siguiente:
<!-- la lista desplegable -->
<tr>
<td>Liste déroulante</td>
<td><select id="DropDownListField" name="DropDownListField"><option value="1">choix1</option>
<option selected="selected" value="2">choix2</option>
<option value="3">choix3</option>
</select></td>
</tr>
- línea 4: el atributo [name] es correcto;
- líneas 4-6: las opciones se generan correctamente y se ha seleccionado la opción correcta.
Volvamos al código que generó estas líneas HTML:
@Html.DropDownListFor(m => m.DropDownListField, new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
- el primer parámetro es una función lambda (ese es su nombre), donde m representa el modelo de la vista y m.DropDowListField es una propiedad de ese modelo. El generador de código HTML utilizará el nombre de esta propiedad para generar los atributos [id] y [name] del [select] que se generará. Si se utiliza una propiedad que no existe, se producirá un error durante la compilación y no durante la ejecución. Esto representa una mejora con respecto a la solución anterior, en la que los errores de nomenclatura solo se detectaban durante la ejecución;
- el segundo parámetro sirve para designar la colección de elementos que alimentará la lista desplegable. La clase [SelectList] permite construir esta colección:
- su primer parámetro es cualquier colección de elementos. En este caso, tenemos una colección de tipo [Item];
- su segundo parámetro es la propiedad de los elementos que proporcionará el valor de la etiqueta <option>. En este caso, es la propiedad [Value] de la clase [Item];
- su tercer parámetro es la propiedad de los elementos que proporcionará el texto de la etiqueta <option>. En este caso, es la propiedad [Label] de la clase [Item];
- para saber qué opción debe seleccionarse (atributo selected), el marco de trabajo hace lo mismo que nosotros: compara el valor de la opción con el valor actual de la propiedad [DropDownListField].
Veamos ahora los otros métodos que podemos utilizar:
Botones de radio
El nuevo código es el siguiente:
<!-- los botones de opción -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
@{
foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
{
@Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)@item.Label
<text/>
}
}
</td>
</tr>
El código HTML generado es el siguiente:
<!-- los botones de opción -->
<tr>
<td>Etes-vous marié(e)</td>
<td>
<input id="RadioButtonField" name="RadioButtonField" type="radio" value="1" />oui
<input checked="checked" id="RadioButtonField" name="RadioButtonField" type="radio" value="2" />non
</td>
</tr>
El método utilizado es [Html.RadioButtonFor]:
- el primer parámetro es la propiedad del modelo que se asociará al botón de opción (atributo [name]);
- el segundo parámetro es el valor que se le asignará al botón de opción (atributo [value]).
Casillas de selección
El código cambia de la siguiente manera:
<!-- las casillas de selección -->
<tr>
<td>Cases à cocher</td>
<td>
@{
@Html.CheckBoxFor(m=>m.CheckBoxField1) @Model.CheckBoxesFieldItems[0].Label
@Html.CheckBoxFor(m=>m.CheckBoxField2) @Model.CheckBoxesFieldItems[1].Label
@Html.CheckBoxFor(m=>m.CheckBoxField3) @Model.CheckBoxesFieldItems[2].Label
}
</td>
El método utilizado para generar casillas de selección es [Html.CheckBoxFor]:
El parámetro es la propiedad booleana del modelo que se asociará a la casilla de selección. Si es [Propriété=true], la casilla estará marcada. Si es [Propriété=false], la casilla no estará marcada. En cualquier caso, el atributo [value] tiene el valor true. El código HTML generado es el siguiente:
<input id="Propriété" name="Propriété" type="checkbox" value="true" />
<input name="Propriété" type="hidden" value="false" />
- línea 1: la casilla de selección con el atributo [value="true"];
- línea 2: un campo oculto (type=hidden) con el mismo nombre, [Propriété], que la casilla de selección con el atributo [value="false"]. ¿Por qué hay dos etiquetas [input] con el mismo nombre? Hay dos casos:
- la casilla de la línea 1 está marcada. En ese caso, la cadena de parámetros enviada es Propiedad=true&Propiedad=false (líneas 1 y 2). Como la propiedad [Propriété] solo espera un valor, se podría pensar que el marco de trabajo asigna el valor [true] a [Propriété]. Bastaría con realizar una operación lógica OU entre los valores recibidos para lograrlo;
- la casilla de la línea 1 no está marcada. Por lo tanto, la cadena de parámetros enviada es Propiedad=false (solo en la línea 2) y, por lo tanto, la propiedad [Propriété] recibe el valor [false], lo cual es correcto (la casilla no se marcó).
Campo de entrada de una sola línea
El nuevo código es el siguiente:
<!-- el campo de texto de una línea -->
<tr>
<td>Champ de saisie</td>
<td>
@Html.TextBoxFor(m => m.TextField, new { size = "30" })
</td>
</tr>
El código HTML generado es el siguiente:
<!-- el campo de entrada de texto de una sola línea -->
<tr>
<td>Champ de saisie</td>
<td>
<input id="TextField" name="TextField" size="30" type="text" value="quelques mots" />
</td>
</tr>
El método utilizado es el siguiente:
@Html.TextBoxFor(m => m.TextField, new { size = "30" })
- el primer parámetro especifica la propiedad del modelo asociada al campo de entrada. El nombre de la propiedad se utilizará en los atributos [name] y [id] de la etiqueta <input> generada, y su valor se asignará al atributo [value];
- el segundo parámetro es una clase anónima que especifica ciertos atributos de la etiqueta HTML generada, en este caso el atributo [size].
Campo de ingreso de contraseña
El nuevo código es el siguiente:
<!-- el campo de entrada de contraseña -->
<tr>
<td>Mot de passe</td>
<td>
@Html.PasswordFor(m => m.PasswordField, new { size = "15" })
</td>
</tr>
El código HTML generado es el siguiente:
<!-- el campo de entrada de contraseña -->
<tr>
<td>Mot de passe</td>
<td>
<input id="PasswordField" name="PasswordField" size="15" type="password" />
</td>
</tr>
El método utilizado es el siguiente:
@Html.PasswordFor(m => m.PasswordField, new { size = "15" })
El funcionamiento es similar al del método [Html.TexBoxFor].
Campo de entrada de varias líneas
El nuevo código es el siguiente:
<!-- el campo de entrada de texto de varias líneas -->
<tr>
<td>Boîte de saisie</td>
<td>
@Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
</td>
</tr>
El código HTML generado es el siguiente:
<!-- el campo de entrada de texto de varias líneas -->
<tr>
<td>Boîte de saisie</td>
<td>
<textarea cols="30" id="TextAreaField" name="TextAreaField" rows="5">
ligne1
ligne2</textarea>
</td>
</tr>
El método utilizado es el siguiente:
@Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
El funcionamiento es similar al del método [Html.TexBoxFor].
Lista de opción única
El nuevo código es el siguiente:
<!-- la lista de opción única -->
<tr>
<td>Liste à choix unique</td>
<td>
@Html.DropDownListFor(m => m.SimpleChoiceListField, new SelectList(@Model.SimpleChoiceListFieldItems, "Value", "Label"), new { size = "3" })
</tr>
y el código HTML generado es el siguiente:
<!-- la lista de opción única -->
<tr>
<td>Liste à choix unique</td>
<td>
<select id="SimpleChoiceListField" name="SimpleChoiceListField" size="3">
<option value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>
Ya hemos analizado el método [Html.DropDownListFor]. La única diferencia aquí es el tercer parámetro, que sirve para especificar un atributo [size] distinto de 1. Es esta característica la que permite pasar de una lista desplegable [size=1] a una lista simple.
La lista de opción múltiple
El nuevo código es el siguiente:
<!-- la lista de selección múltiple -->
<tr>
<td>Liste à choix multiple</td>
<td>
@Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
</tr>
y el código HTML generado es el siguiente:
<!-- la lista de selección múltiple -->
<tr>
<td>Liste à choix multiple</td>
<td>
<select id="MultipleChoiceListField" multiple="multiple" name="MultipleChoiceListField" size="5">
<option selected="selected" value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>
El método
@Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
funciona igual que el método [Html.DropDownListFor], salvo que genera una lista de selección múltiple. Las opciones seleccionadas son aquellas cuyo valor (atributo value) figura en la tabla [MultipleChoiceListField].
La etiqueta <form> también se puede generar con un método:
@using (Html.BeginForm("Action09Post", "First"))
{
...
}
El código HTML generado es el siguiente:
<form action="/First/Action09Post" method="post">
...
</form>
El método
Html.BeginForm("Action09Post", "First")
tiene como primer parámetro el nombre de una acción y como segundo parámetro el nombre de un controlador.
5.7.2. Las acciones y el modelo
El formulario será generado por la siguiente acción [Action09Get]:
// Acción09-GET
[HttpGet]
public ViewResult Action09Get(ApplicationModel application)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View("Formulaire2", new ViewModel09(application));
}
La vista generada en la línea 6 es [Formulaire2], asociada al siguiente modelo [ViewModel09]:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
namespace Exemple_03.Models
{
public class ViewModel09
{
// los campos de entrada
public string RadioButtonField { get; set; }
public bool CheckBoxField1 { get; set; }
public bool CheckBoxField2 { get; set; }
public bool CheckBoxField3 { get; set; }
public string TextField { get; set; }
public string PasswordField { get; set; }
public string TextAreaField { get; set; }
public string DropDownListField { get; set; }
public string SimpleChoiceListField { get; set; }
public string[] MultipleChoiceListField { get; set; }
// las colecciones que se mostrarán en el formulario
public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }
// constructores
public ViewModel09()
{
}
public ViewModel09(ApplicationModel application)
{
// Inicialización de colecciones
RadioButtonFieldItems = application.RadioButtonFieldItems;
CheckBoxesFieldItems = application.CheckBoxesFieldItems;
DropDownListFieldItems = application.DropDownListFieldItems;
SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
// inicialización de campos
RadioButtonField = "2";
CheckBoxField2 = true;
TextField = "quelques mots";
PasswordField = "secret";
TextAreaField = "ligne1\nligne2";
DropDownListField = "2";
SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };
}
}
}
[ViewModel09] se diferencia de [ViewModel08] en su manejo de las casillas de selección. En lugar de tener una tabla con tres casillas de selección, se utilizaron tres casillas de selección separadas (líneas 11-13).
El formulario será procesado por la siguiente acción [Action09Post]:
// Acción09-POST
[HttpPost]
public ViewResult Action09Post(ApplicationModel application, FormCollection posted)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
ViewModel09 modèle = new ViewModel09(application);
TryUpdateModel(modèle, posted);
// procesamiento de valores no enviados
if (posted["SimpleChoiceListField"] == null)
{
modèle.SimpleChoiceListField = "";
}
if (posted["MultipleChoiceListField"] == null)
{
modèle.MultipleChoiceListField = new string[] { };
}
// visualización del formulario
return View("Formulaire2", modèle);
}
La acción [Action09Post] es idéntica a la acción [Action08Post], salvo en dos puntos:
- línea 18: se utiliza la vista [Formulaire2] en lugar de la vista [Formulaire];
- ya no se gestiona el caso de las casillas de selección que no se marcaron. Ahora esto se gestiona correctamente mediante el método [Html.CheckBoxFor].
5.8. Generación de un formulario a partir de los metadatos del modelo
Existen otros métodos, además de los anteriores, para generar un formulario. Uno de ellos consiste en asociar información a un campo del modelo que le permitirá al marco de trabajo MVC saber qué etiqueta de entrada debe generar. A esta información se le denomina metadatos.
Consideremos el siguiente modelo de vista [ViewModel10]:
![]() |
using System;
using System.ComponentModel.DataAnnotations;
using System.Drawing;
namespace Exemple_03.Models
{
public class ViewModel10
{
[Display(Name="Text")]
[DataType(DataType.Text)]
public string Text { get; set; }
[Display(Name = "TextArea")]
[DataType(DataType.MultilineText)]
public string MultiLineText { get; set; }
[Display(Name = "Number")]
public int Number { get; set; }
[Display(Name = "Decimal")]
[UIHint("Decimal")]
public double Decimal { get; set; }
[Display(Name = "Tel")]
[DataType(DataType.PhoneNumber)]
public string Tel { get; set; }
[Display(Name = "Date")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
[Display(Name = "Time")]
[DataType(DataType.Time)]
public DateTime Time { get; set; }
[Display(Name = "HiddenInput")]
[UIHint("HiddenInput")]
public string HiddenInput { get; set; }
[Display(Name = "Boolean")]
[UIHint("Boolean")]
public bool Boolean { get; set; }
[Display(Name = "Email")]
[DataType(DataType.EmailAddress)]
public string Email{ get; set; }
[Display(Name = "Url")]
[DataType(DataType.Url)]
public string Url { get; set; }
[Display(Name = "Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
[Display(Name = "Currency")]
[DataType(DataType.Currency)]
public double Currency { get; set; }
[Display(Name = "CreditCard")]
[DataType(DataType.CreditCard)]
public string CreditCard { get; set; }
// constructor
public ViewModel10()
{
Text = "tra la la";
MultiLineText = "ligne1\nligne2";
Number = 4;
Decimal = 10.2;
Tel = "0617181920";
Date = DateTime.Now;
Time = DateTime.Now;
HiddenInput = "caché";
Boolean = true;
Email = "x@y.z";
Url = "http://istia.univ-angers.fr";
Password = "mdp";
Currency = 4.2;
CreditCard = "0123456789012345";
}
}
}
Los metadatos están formados por las etiquetas [Display, DataType, UIHint].
Esta plantilla de vista se generará mediante la siguiente acción [Action10Get]:
// Acción10-GET
[HttpGet]
public ViewResult Action10Get()
{
return View(new ViewModel10());
}
En la línea 5 anterior, se le pide a la vista predeterminada de la acción [/First/Action10Get.cshtml ] que muestre la plantilla de vista de tipo [ViewModel10]. Esta vista es la siguiente:
![]() |
@model Exemple_03.Models.ViewModel10
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action10Get</title>
</head>
<body>
<h3>Formulaire ASP.NET MVC - 2</h3>
@using (Html.BeginForm("Action10Post", "First"))
{
<table>
<thead>
<tr>
<th>LabelFor</th>
<th>EditorFor</th>
<th>DisplayFor</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.LabelFor(m => m.Text)</td>
<td>@Html.EditorFor(m => m.Text)</td>
<td>@Html.DisplayFor(m => m.Text)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.MultiLineText)</td>
<td>@Html.EditorFor(m => m.MultiLineText)</td>
<td>@Html.DisplayFor(m => m.MultiLineText)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Number)</td>
<td>@Html.EditorFor(m => m.Number)</td>
<td>@Html.DisplayFor(m => m.Number)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Decimal)</td>
<td>@Html.EditorFor(m => m.Decimal)</td>
<td>@Html.DisplayFor(m => m.Decimal)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Tel)</td>
<td>@Html.EditorFor(m => m.Tel)</td>
<td>@Html.DisplayFor(m => m.Tel)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Date)</td>
<td>@Html.EditorFor(m => m.Date)</td>
<td>@Html.DisplayFor(m => m.Date)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Time)</td>
<td>@Html.EditorFor(m => m.Time)</td>
<td>@Html.DisplayFor(m => m.Time)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.HiddenInput)</td>
<td>@Html.EditorFor(m => m.HiddenInput)</td>
<td>@Html.DisplayFor(m => m.HiddenInput)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Boolean)</td>
<td>@Html.EditorFor(m => m.Boolean)</td>
<td>@Html.DisplayFor(m => m.Boolean)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Email)</td>
<td>@Html.EditorFor(m => m.Email)</td>
<td>@Html.DisplayFor(m => m.Email)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Url)</td>
<td>@Html.EditorFor(m => m.Url)</td>
<td>@Html.DisplayFor(m => m.Url)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Password)</td>
<td>@Html.EditorFor(m => m.Password)</td>
<td>@Html.DisplayFor(m => m.Password)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Currency)</td>
<td>@Html.EditorFor(m => m.Currency)</td>
<td>@Html.DisplayFor(m => m.Currency)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.CreditCard)</td>
<td>@Html.EditorFor(m => m.CreditCard)</td>
<td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>
</tbody>
</table>
<input type="submit" value="Valider" />
}
</body>
</html>
Para cada una de las propiedades del modelo, utilizamos el método:
- Html.LabelFor para mostrar el valor del metadato [DisplayName] de la propiedad;
- Html.EditorFor para generar la etiqueta HTML de entrada del valor de la propiedad. Este método utilizará los metadatos [DataType] y [UIHint] de la propiedad;
- Html.DisplayFor para mostrar el valor de la propiedad según el formato indicado por el metadato [DataType].
A continuación se muestra un ejemplo de ejecución con el navegador Chrome:

Dependiendo del navegador que se utilice, pueden aparecer páginas diferentes. De hecho, la vista generada utiliza las nuevas etiquetas introducidas por la versión 5 de HTML, denominada HTML5. No todos los navegadores son compatibles aún con esta versión. En el ejemplo anterior, el navegador Chrome la soporta parcialmente.
5.8.1. El [POST] del formulario
El [POST] del formulario es procesado por la siguiente acción [Action10Post]:
// Acción10-POST
[HttpPost]
public ContentResult Action10Post(ViewModel10 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, valide={2}, erreurs={3}", RouteData.Values["controller"], RouteData.Values["action"], ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- línea 3: la acción [Action10Post] tiene como modelo de entrada el formulario enviado;
- línea 5: se recuperan los errores de validación de este formulario;
- línea 6: se prepara la respuesta de texto para el cliente;
- línea 7: se envía.
Ahora analicemos las propiedades del modelo [ViewModel10] una por una y veamos cómo los metadatos asociados influyen en el HTML generado y en la validación de los campos de entrada.
5.8.2. Propiedad [Text]
Definición
[Display(Name="Text")]
[DataType(DataType.Text)]
public string Text { get; set; }
...
Text = "tra la la";
Vista
<tr>
<td>@Html.LabelFor(m => m.Text)</td>
<td>@Html.EditorFor(m => m.Text)</td>
<td>@Html.DisplayFor(m => m.Text)</td>
</tr>
Imagen
![]() |
HTML generado
<tr>
<td><label for="Text">Text</label></td>
<td><input class="text-box single-line" id="Text" name="Text" type="text" value="tra la la" /></td>
<td>tra la la</td>
</tr>
Comentarios
- El método [Html.LabelFor] generó la etiqueta <label> de la línea 2. El valor del atributo [for] es el nombre de la propiedad de parámetro del método [Html.LabelFor]
public string Text { get; set; }
El texto que aparece entre el inicio y el final de la etiqueta es el texto de los metadatos
[Display(Name="Text")]
El método [Html.LabelFor] siempre funciona así. No volveremos a abordar este tema para las demás propiedades.
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3. Cabe señalar que tiene un atributo [class] que asocia la clase CSS [text-box single-line] a la etiqueta. Los atributos [id] y [name] tienen como valor el nombre [Text] de la propiedad «parámetro» del método [Html.EditorFor]. El atributo [type] tomó el valor [text] debido al metadato
[DataType(DataType.Text)]
- el método [Html.DisplayFor] generó el texto de la línea 4. Este es el valor de la propiedad de parámetro del método [Html.DisplayFor ]. Este método está influenciado por los metadatos
[DataType(DataType.Text)]
, lo que hace que el valor se muestre como texto sin formato.
5.8.3. Propiedad [MultiLineText]
Definición
[Display(Name = "TextArea")]
[DataType(DataType.MultilineText)]
public string MultiLineText { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.MultiLineText)</td>
<td>@Html.EditorFor(m => m.MultiLineText)</td>
<td>@Html.DisplayFor(m => m.MultiLineText)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="MultiLineText">TextArea</label></td>
<td><textarea class="text-box multi-line" id="MultiLineText" name="MultiLineText">
ligne1
ligne2</textarea></td>
<td>ligne1
ligne2</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <textarea> de la línea 3. Cabe señalar que tiene un atributo [class] que asocia la clase CSS [text-box multi-line] a la etiqueta. Los atributos [id] y [name] tienen como valor el nombre [MultiLineText] de la propiedad «parámetro» del método [Html.EditorFor]. Siempre es así. No lo volveremos a mencionar. La etiqueta generada es <textarea> debido al metadato
[DataType(DataType.MultilineText)]
que especificaba que la propiedad era un texto de varias líneas.
- El método [Html.DisplayFor] generó el texto de las líneas 4 y 5. Este es el valor de la propiedad de parámetro del método [Html.DisplayFor ].
5.8.4. Propiedad [Number]
Definición
[Display(Name = "Number")]
public int Number { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Number)</td>
<td>@Html.EditorFor(m => m.Number)</td>
<td>@Html.DisplayFor(m => m.Number)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Number">Number</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le champ Number doit être un nombre." data-val-required="Le champ Number est requis." id="Number" name="Number" type="number" value="4" /></td>
<td>4</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [number]. Al parecer, simplemente porque la propiedad tiene el tipo [int]. Los atributos [data-val], [data-val-number] y [data-val-required] son atributos no reconocidos por HTML5. Son utilizados por un marco de trabajo de JavaScript para la validación de datos del lado del cliente;
- el método [Html.DisplayFor] generó el texto de la línea 4, el valor de la propiedad.
Validación
Los atributos [data-x] influyen en la validación de datos del lado del cliente. A continuación se presentan dos ejemplos:
Se ingresa un número incorrecto y se valida:
![]() |
En el ejemplo anterior, la validación se realizó del lado del cliente. El formulario no se enviará hasta que se corrija el error.
Otro ejemplo: no se ingresa nada:
![]() |
En el caso de [1] anterior, [Action10Post] indica un error. Quizás recordemos que ya habíamos obtenido este comportamiento al usar el atributo [Required] en la propiedad a controlar (véase la página 69), en este caso la propiedad [Number]. En este caso, no tuvimos que hacerlo.
5.8.5. Propiedad [Decimal]
Definición
[Display(Name = "Decimal")]
[UIHint("Decimal")]
public double Decimal { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Decimal)</td>
<td>@Html.EditorFor(m => m.Decimal)</td>
<td>@Html.DisplayFor(m => m.Decimal)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Decimal">Decimal</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le champ Decimal doit être un nombre." data-val-required="Le champ Decimal est requis." id="Decimal" name="Decimal" type="text" value="10,20" /></td>
<td>10,20</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [text]. Los demás atributos son idénticos a los generados para la propiedad [Number] anterior. Los metadatos:
[UIHint("Decimal")]
hace que el valor de la propiedad se muestre con dos decimales en los dos métodos [Html.EditorFor] y [Html.DisplayFor]
Validación
No se reporta ningún error de validación del lado del cliente, a diferencia del caso anterior. El error solo se reporta mediante la acción [Action10Post]. Una vez más, se requiere el número decimal sin necesidad de asignarle el atributo [Required].
5.8.6. Propiedad [Tel]
Definición
[Display(Name = "Tel")]
[DataType(DataType.PhoneNumber)]
public string Tel { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Tel)</td>
<td>@Html.EditorFor(m => m.Tel)</td>
<td>@Html.DisplayFor(m => m.Tel)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Tel">Tel</label></td>
<td><input class="text-box single-line" id="Tel" name="Tel" type="tel" value="0617181920" /></td>
<td>0617181920</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [tel]. Este valor se generó debido a los metadatos:
[DataType(DataType.PhoneNumber)]
El tipo [tel] para una etiqueta <input> es una novedad respecto a HTML5. El navegador Chrome la procesó como una etiqueta <input> con el tipo [text].
Validación
No se reporta ningún error de validación ni del lado del cliente ni del lado del servidor. Se puede ingresar cualquier cosa.
5.8.7. Propiedad [Date]
Definición
[Display(Name = "Date")]
[DataType(DataType.Date)]
public DateTime Date { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Date)</td>
<td>@Html.EditorFor(m => m.Date)</td>
<td>@Html.DisplayFor(m => m.Date)</td>
</tr>
Imagen
![]() |
HTML generado
<tr>
<td><label for="Date">Date</label></td>
<td><input class="text-box single-line" data-val="true" data-val-date="Le champ Date doit être une date." data-val-required="Le champ Date est requis." id="Date" name="Date" type="date" value="11/10/2013" /></td>
<td>11/10/2013</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [date]. Este valor se generó debido a los metadatos:
[DataType(DataType.Date)]
El tipo [date] para una etiqueta <input> es una novedad de HTML5. El navegador Chrome lo reconoce y permite ingresar la fecha mediante un calendario. Por otra parte, la fecha ingresada se presenta en el formato [jj/mm/aaaa], es decir, Chrome adapta el formato de fecha al de [locale] del navegador.
- El método [Html.DisplayFor] también escribió la fecha en el formato [jj/mm/aaaa], siempre debido a la presencia del metadato [Date].
Validación
Se reporta una fecha inválida en el lado del cliente ([1]), lo que impide que el POST del formulario se envíe al servidor.
![]() |
La falta de fecha no se señala del lado del cliente, pero sí del lado del servidor: [2].
5.8.8. Propiedad [Time]
Definición
[Display(Name = "Time")]
[DataType(DataType.Time)]
public DateTime Time { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Time)</td>
<td>@Html.EditorFor(m => m.Time)</td>
<td>@Html.DisplayFor(m => m.Time)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Time">Time</label></td>
<td><input class="text-box single-line" data-val="true" data-val-required="Le champ Time est requis." id="Time" name="Time" type="time" value="11:17" /></td>
<td>11:17</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [time]. Este valor se generó debido a los metadatos:
[DataType(DataType.Time)]
El tipo [time] para una etiqueta <input> es una novedad respecto a HTML5. El navegador Chrome lo reconoce y permite ingresar una hora en el formato [hh:mm];
- el método [Html.DisplayFor] también escribió la hora en el formato [hh:mm], siempre debido a la presencia del metadato [Time].
Validación
Técnicamente no es posible ingresar una hora inválida. La ausencia de hora se señala en el servidor:
![]() |
5.8.9. Propiedad [HiddenInput]
Definición
[Display(Name = "HiddenInput")]
[UIHint("HiddenInput")]
public string HiddenInput { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.HiddenInput)</td>
<td>@Html.EditorFor(m => m.HiddenInput)</td>
<td>@Html.DisplayFor(m => m.HiddenInput)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="HiddenInput">HiddenInput</label></td>
<td>caché<input id="HiddenInput" name="HiddenInput" type="hidden" value="oculto" /></td>
<td>caché</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [hidden], es decir, un campo oculto (pero que, no obstante, se envía). Este valor se generó debido a los metadatos:
[UIHint("HiddenInput")]
- El método [Html.DisplayFor] escribió el valor del campo oculto.
5.8.10. Propiedad [Boolean]
Definición
[Display(Name = "Boolean")]
public bool Boolean { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Boolean)</td>
<td>@Html.EditorFor(m => m.Boolean)</td>
<td>@Html.DisplayFor(m => m.Boolean)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Boolean">Boolean</label></td>
<td><input checked="checked" class="check-box" data-val="true" data-val-required="Le champ Boolean est requis." id="Boolean" name="Boolean" type="checkbox" value="true" /><input name="Boolean" type="hidden" value="false" /></td>
<td><input checked="checked" class="check-box" disabled="disabled" type="checkbox" /></td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [checkbox], es decir, una casilla de selección. Este valor se generó porque la propiedad es booleana:
public bool Boolean { get; set; }
- el método [Html.DisplayFor] generó la línea 4, también una casilla de selección (atributo type), pero desactivada (atributo disabled).
5.8.11. Propiedad [Email]
Definición
[Display(Name = "Email")]
[DataType(DataType.EmailAddress)]
public string Email{ get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Email)</td>
<td>@Html.EditorFor(m => m.Email)</td>
<td>@Html.DisplayFor(m => m.Email)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Email">Email</label></td>
<td><input class="text-box single-line" id="Email" name="Email" type="email" value="x@y.z" /></td>
<td><a href="mailto:x@y.z">x@y.z</a></td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [email]. Este tipo es nuevo en HTML5. Este tipo se generó debido a los metadatos:
[DataType(DataType.EmailAddress)]
Chrome parece haber tratado este tipo como un tipo [text].
- El método [Html.DisplayFor] generó la línea 4: un enlace a la dirección de correo electrónico.
Validación
Se reporta una dirección inválida en el lado del cliente [1]:
![]() |
La falta de entrada no provoca ningún error.
5.8.12. Propiedad [Url]
Definición
[Display(Name = "Url")]
[DataType(DataType.Url)]
public string Url { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Url)</td>
<td>@Html.EditorFor(m => m.Url)</td>
<td>@Html.DisplayFor(m => m.Url)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Url">Url</label></td>
<td><input class="text-box single-line" id="Url" name="Url" type="url" value="http://istia.univ-angers.fr" /></td>
<td><a href="http://istia.univ-angers.fr">http://istia.univ-angers.fr</a></td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [url]. Este tipo es nuevo en HTML5. Se generó debido a los metadatos:
[DataType(DataType.Url)]
Chrome parece tratar este tipo como un tipo [text].
- El método [Html.DisplayFor] generó la línea 4: un enlace al URL.
Validación
Se reporta un URL inválido en el lado del cliente [1]:
![]() |
La falta de entrada no genera ningún error.
5.8.13. Propiedad [Password]
Definición
[Display(Name = "Password")]
[DataType(DataType.Password)]
public string Password { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Password)</td>
<td>@Html.EditorFor(m => m.Password)</td>
<td>@Html.DisplayFor(m => m.Password)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Password">Password</label></td>
<td><input class="text-box single-line password" id="Password" name="Password" type="password" value="mdp" /></td>
<td>mdp</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [password]. Este tipo se generó debido a los metadatos:
[DataType(DataType.Password)]
- El método [Html.DisplayFor] generó la línea 4.
5.8.14. Propiedad [Currency]
Definición
[Display(Name = "Currency")]
[DataType(DataType.Currency)]
public double Currency { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.Currency)</td>
<td>@Html.EditorFor(m => m.Currency)</td>
<td>@Html.DisplayFor(m => m.Currency)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="Currency">Currency</label></td>
<td><input class="text-box single-line" data-val="true" data-val-number="Le champ Currency doit être un nombre." data-val-required="Le champ Currency est requis." id="Currency" name="Currency" type="text" value="4,2" /></td>
<td>4,20 €</td>
</tr>
Comentarios
- el método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [text];
- el método [Html.DisplayFor] generó la línea 4, un número con dos decimales y un símbolo monetario. Se utilizó este formato debido a los metadatos:
[DataType(DataType.Currency)]
Validación
Se reporta un valor no válido [1] o la ausencia de un valor [2] del lado del servidor:
![]() |
5.8.15. Propiedad [CreditCard]
Definición
[Display(Name = "CreditCard")]
[DataType(DataType.CreditCard)]
public string CreditCard { get; set; }
Vista
<tr>
<td>@Html.LabelFor(m => m.CreditCard)</td>
<td>@Html.EditorFor(m => m.CreditCard)</td>
<td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>
Imagen
HTML generado
<tr>
<td><label for="CreditCard">CreditCard</label></td>
<td><input class="text-box single-line" id="CreditCard" name="CreditCard" type="text" value="0123456789012345" /></td>
<td>0123456789012345</td>
</tr>
Comentarios
- El método [Html.EditorFor] generó la etiqueta <input> de la línea 3 con un atributo [type] de tipo [text]. El método [Html.DisplayFor] generó la línea 4. Aquí no se ve qué aporta el metadato:
[DataType(DataType.CreditCard)]
Validación
No se realiza ninguna verificación, ni del lado del cliente ni del lado del servidor.
5.9. Validación de un formulario
Ya hemos abordado el problema de la validación del modelo de una acción en el párrafo 4.5 y los párrafos siguientes. Volvemos a abordar esta cuestión en el contexto de un formulario:
- cómo notificarle al usuario los errores de entrada;
- realizar las validaciones tanto del lado del cliente como del lado del servidor para notificar los errores al usuario más rápidamente.
5.9.1. Validación del lado del servidor
Consideremos el siguiente modelo:
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Mail;
namespace Exemple_03.Models
{
public class ViewModel11 : IValidatableObject
{
[Required(ErrorMessage = "Information requise")]
[Display(Name = "Chaîne d'au moins quatre caractères")]
[RegularExpression(@"^.{4,}$", ErrorMessage = "Information incorrecte")]
public string Chaine1 { get; set; }
[Display(Name = "Chaîne d'au plus quatre caractères")]
[Required(ErrorMessage = "Information requise")]
[RegularExpression(@"^.{1,4}$", ErrorMessage = "Information incorrecte")]
public string Chaine2 { get; set; }
[Required(ErrorMessage = "Information requise")]
[Display(Name = "Chaîne de quatre caractères exactement")]
[RegularExpression(@"^.{4,4}$", ErrorMessage = "Information incorrecte")]
public string Chaine3 { get; set; }
[Required(ErrorMessage = "Information requise")]
[Display(Name = "Nombre entier")]
public int Entier1 { get; set; }
[Display(Name = "Nombre entier dans l'intervalle [1,100]")]
[Required(ErrorMessage = "Information requise")]
[Range(1, 100, ErrorMessage = "Information incorrecte")]
public int Entier2 { get; set; }
[Display(Name = "Nombre réel")]
[Required(ErrorMessage = "Information requise")]
public double Reel1 { get; set; }
[Display(Name = "Nombre réel dans l'intervalle [10.2, 11.3]")]
[Required(ErrorMessage = "Information requise")]
[Range(10.2, 11.3, ErrorMessage = "Information incorrecte")]
public double Reel2 { get; set; }
[Display(Name = "Adresse mail")]
[Required(ErrorMessage = "Information requise")]
public string Email1 { get; set; }
[Display(Name = "Date sous la forme dd/jj/aaaa")]
[RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessage = "Information incorrecte")]
[Required(ErrorMessage = "Information requise")]
public string Regexp1 { get; set; }
[Display(Name = "Date postérieure à celle d'aujourd'hui")]
[Required(ErrorMessage = "Information requise")]
[DataType(DataType.Date)]
public DateTime Date1 { get; set; }
// validación
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> résultats = new List<ValidationResult>();
// Fecha 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
}
// Correo electrónico 1
try
{
new MailAddress(Email1);
}
catch
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
}
// se muestra la lista de errores
return résultats;
}
}
}
Este modelo se mostrará en la siguiente vista [Action11Get.cshtml]:
![]() |
@model Exemple_03.Models.ViewModel11
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action11Get</title>
<link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
<h3>Formulaire ASP.NET MVC – Validation 1</h3>
@using (Html.BeginForm("Action11Post", "First"))
{
<table>
<thead>
<tr>
<th>Type attendu</th>
<th>Valeur saisie</th>
<th>Message d'erreur</th>
</tr>
</thead>
<tbody>
<tr>
<td>@Html.LabelFor(m => m.Chaine1)</td>
<td>@Html.EditorFor(m => m.Chaine1)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Chaine2)</td>
<td>@Html.EditorFor(m => m.Chaine2)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Chaine3)</td>
<td>@Html.EditorFor(m => m.Chaine3)</td>
<td>@Html.ValidationMessageFor(m => m.Chaine3)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Entier1)</td>
<td>@Html.EditorFor(m => m.Entier1)</td>
<td>@Html.ValidationMessageFor(m => m.Entier1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Entier2)</td>
<td>@Html.EditorFor(m => m.Entier2)</td>
<td>@Html.ValidationMessageFor(m => m.Entier2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Reel1)</td>
<td>@Html.EditorFor(m => m.Reel1)</td>
<td>@Html.ValidationMessageFor(m => m.Reel1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Reel2)</td>
<td>@Html.EditorFor(m => m.Reel2)</td>
<td>@Html.ValidationMessageFor(m => m.Reel2)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Email1)</td>
<td>@Html.EditorFor(m => m.Email1)</td>
<td>@Html.ValidationMessageFor(m => m.Email1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Regexp1)</td>
<td>@Html.EditorFor(m => m.Regexp1)</td>
<td>@Html.ValidationMessageFor(m => m.Regexp1)</td>
</tr>
<tr>
<td>@Html.LabelFor(m => m.Date1)</td>
<td>@Html.EditorFor(m => m.Date1)</td>
<td>@Html.ValidationMessageFor(m => m.Date1)</td>
</tr>
</tbody>
</table>
<p>
<input type="submit" value="Valider" />
</p>
}
</body>
</html>
- línea 12: se hace referencia a la hoja de estilo [Site.css]. Por defecto, contiene clases que se utilizan para resaltar los errores de entrada en el formulario;
- líneas 18-25: una tabla de tres columnas:
- la columna 1 muestra texto con el método [Html.LabelFor],
- la columna 2 muestra la entrada con el método [Html.EditorFor],
- la columna 3 muestra el posible error de entrada con el método [Html.ValidationMessageFor];
La acción [Action11Get] sirve para mostrar el formulario:
// Acción 11-GET
[HttpGet]
public ViewResult Action11Get()
{
return View("Action11Get", new ViewModel11());
}
La acción [Action11Post] sirve para volver a mostrar el formulario con los posibles errores de entrada:
// Acción11-POST
[HttpPost]
public ViewResult Action11Post(ViewModel11 modèle)
{
return View("Action11Get", modèle);
}
- línea 3: se crea el modelo [ViewModel11] y luego se inicializa con los valores enviados. En este punto pueden producirse errores. A cada propiedad errónea P del modelo se le asocia un mensaje de error. Este es el mensaje que permite obtener el método [Html.ValidationMessageFor] del formulario.
A continuación se muestra un ejemplo de ejecución:
![]() | ![]() |
A continuación se muestra otro ejemplo:
![]() |
Cabe señalar que ambas fechas son incorrectas (hoy es 11/10/2013), pero que los errores no se señalan. Estos errores son detectados por el método [Validate] del modelo:
// Validación
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> résultats = new List<ValidationResult>();
// Fecha 1
if (Date1.Date <= DateTime.Now.Date)
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
}
// Correo electrónico 1
try
{
new MailAddress(Email1);
}
catch
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
}
// Expresión regular 1
try
{
DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
}
catch
{
résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Regexp1" }));
}
// se muestra la lista de errores
return résultats;
}
El método [Validate] solo se ejecuta cuando se han superado todas las validaciones por atributos. Así lo muestra un último ejemplo:
![]() |
5.9.2. Validación del lado del cliente
Todas las validaciones anteriores se realizaron del lado del servidor. Por lo tanto, se requiere un intercambio de datos entre el cliente y el servidor para que el usuario se dé cuenta de sus errores. La validación del lado del cliente utiliza código JavaScript para notificarle al usuario sus errores lo antes posible y, en cualquier caso, antes de que se ejecute POST. Esta última solo puede ejecutarse una vez que se hayan corregido todos los errores detectados.
Retomamos el modelo [ViewModel11] anterior, pero ahora lo mostramos con la siguiente vista [Action12Get.cshtml]:
![]() |
@model Exemple_03.Models.ViewModel11
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action12Get</title>
<link rel="stylesheet" href="~/Content/Site.css" />
<script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js" ></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.min.js" ></script>
<script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js" ></script>
</head>
<body>
<h3>Formulaire ASP.NET MVC - Validation 1</h3>
@using (Html.BeginForm("Action11Post", "First"))
{
<table>
<thead>
<tr>
<th>Type attendu</th>
<th>Valeur saisie</th>
<th>Message d'erreur</th>
</tr>
</thead>
<tbody>
...
</tbody>
</table>
<p>
<input type="submit" value="Valider" />
</p>
}
</body>
</html>
Nota: línea 13, adapta la versión de jQuery a la que tengas con tu versión de Visual Studio (ver más abajo).
La validación del lado del cliente requiere que la línea 3 que se muestra a continuación esté presente en el archivo [Web.config] de la aplicación.
<appSettings>
...
<add key="ClientValidationEnabled" value="true" />
</appSettings>
- líneas 1-4: la sección [appSettings] debe ser un elemento directo de la sección [configuration] del archivo [Web.config];
La vista [Action12Get] es idéntica a la vista [Action11Get] anterior, salvo por las líneas 13 a 15. Estas incluyen en la vista los scripts de JavaScript necesarios para la validación del lado del cliente. Estos scripts se encuentran en la carpeta [Scripts] del proyecto:
![]() |
Cada script tiene una versión normal [.js] y una versión minificada [min.js]. Esta última versión es más ligera, pero ilegible. Se utiliza en producción. La versión legible se utiliza en desarrollo.
La vista [Action12Get.cshtml] se mostrará mediante la siguiente acción [Action12Get]:
// Acción12-GET
[HttpGet]
public ViewResult Action12Get()
{
return View("Action12Get", new ViewModel11());
}
El formulario ingresado será procesado por la siguiente acción [Action12Post]:
// Acción12-POST
[HttpPost]
public ViewResult Action12Post(ViewModel11 modèle)
{
return View("Action12Get", modèle);
}
Veamos qué cambia con un ejemplo:
![]() |
Tan pronto como se escribe un carácter en [1], aparece el mensaje en [2] porque el valor esperado debe tener al menos cuatro caracteres. De este modo, la validación se realiza con cada nuevo carácter que se escribe. El mensaje de error desaparece al escribir el cuarto carácter. Una vez hecho esto, validemos el formulario:
![]() |
El URL [3] nos muestra que el [POST] no se ha ejecutado. Sin embargo, al hacer clic en el botón [Valider] se activaron todas las validaciones del lado del cliente y aparecieron nuevos mensajes de error.
Veamos, por ejemplo, el código HTML generado para el primer registro:
<tr>
<td><label for="Chaine1">Chaîde al menos cuatro caracteres</label></td>
<td><input class="text-box single-line" data-val="true" data-val-regex="Information incorrecte" data-val-regex-pattern="^.{4,}$" data-val-required="Information requise" id="Chaine1" name="Chaine1" type="text" value="" /></td>
<td><span class="field-validation-valid" data-valmsg-for="Chaine1" data-valmsg-replace="true"></span></td>
</tr>
- En la línea 3, encontramos:
- el mensaje de error en caso de que falte la entrada [data-val-required],
- el mensaje de error en caso de que la entrada sea incorrecta: [data-val-regex],
- la expresión regular para la cadena ingresada [data-val-regex-pattern];
- línea 4, otros atributos [data-x] utilizados para mostrar el posible mensaje de error;
Los atributos [data-x] de las etiquetas generadas son procesados por el JavaScript que hemos incorporado en la vista. Si este no está presente, dichos atributos simplemente se ignoran y, por lo tanto, no hay validación del lado del cliente. Funciona igual que en el ejemplo anterior. De ahí el término [unobtrusive] para esta técnica.
5.10. Manejo de enlaces de navegación y de acción
Crearemos las dos vistas siguientes para ilustrar la gestión de enlaces en una vista:
![]() |
- en [1] y [2], tenemos dos enlaces de navegación;
- en [3], hay un enlace de acción que envía el formulario. No sirve para navegar.
La página 1 es generada por la siguiente vista [Action16Get.cshtml]:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action16Get</title>
<script>
function postForm() {
// se recupera el formulario del documento
var form = document.forms[0];
// envío
form.submit();
}
</script>
</head>
<body>
<h3>Navigation - page 1</h3>
<h4>@ViewBag.info</h4>
@using (Html.BeginForm("Action16Post", "Second"))
{
@Html.Label("data", "Tapez un texte")
@Html.TextBox("data")
<a href="javascript:postForm()">Valider</a>
}
<p>
@Html.ActionLink("Page 2", "Action17Get", "Second")
</p>
</body>
</html>
- línea 22: una información inicializada por la acción que generará la vista;
- líneas 23-28: un formulario;
- línea 25: un texto descriptivo para el campo [data];
- línea 26: un campo de entrada llamado [data];
- línea 27: un enlace de tipo [submit]. Al hacer clic en él, se ejecuta la función de JavaScript [postForm] (atributo href). Esta función se define en las líneas 12-17;
- línea 14: se obtiene una referencia al primer formulario del documento, el de la línea 23;
- línea 16: se envía este formulario. Al final, todo ocurre como si se hubiera hecho clic en un botón de tipo [submit]. El formulario se envía al controlador y a la acción especificados en la línea 23;
- línea 30: un enlace de navegación. El código HTML generado es el siguiente:
<a href="/Second/Action17Get">Page 2</a>
El método utilizado es ActionLink(Texto, Acción, Controlador).
La página 2 se genera mediante la siguiente vista [Action17Get.cshtml]:
@{
Layout = null;
}
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Action17Get</title>
</head>
<body>
<h3>Navigation - Page 2</h3>
<h4>@ViewBag.info</h4>
<p>
@Html.ActionLink("Page 1", "Action16Get", "Second")
</p>
</body>
</html>
Las acciones que generan estas vistas son las siguientes:
// Acción16-GET
[HttpGet]
public ViewResult Action16Get()
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View("Action16Get");
}
// Acción16-POST
[HttpPost]
public ViewResult Action16Post(string data)
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}, Data={2}", RouteData.Values["controller"], RouteData.Values["action"], data);
return View("Action16Get");
}
// Acción17-GET
[HttpGet]
public ViewResult Action17Get()
{
ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
return View();
}
- línea 6, la acción [Action16Get] genera la vista [Action16Get.cshtml], es decir, la página 1 del ejemplo. Esta vista tiene como modelo la [ViewBag] (línea 5);
- línea 19: la acción [Action17Get] genera la vista [Action17Get.cshtml], es decir, la página 2 del ejemplo. Esta vista tiene como modelo la [ViewBag] (línea 21);
- línea 11: la acción [Action16Post] procesa el POST del formulario de la vista [Action16Get.cshtml]. Recibe el parámetro denominado [data]. Recordemos que este es el nombre del campo de entrada en el formulario;
- línea 13: se ingresa información en el campo [ViewBag];
- línea 14: se muestra la vista [Action16Get.cshtml].
Se invita al lector a probar este ejemplo.































































