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, hemos visto cómo ASP.NET MVC presentaba la información de la consulta [1] a una acción [2a] en forma de modelo que podía contener restricciones de validación. Este modelo se proporcionaba como entrada a la acción y lo denominamos «modelo de la acción». Ahora nos centramos en el resultado más habitual 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 y el otro es la salida.
Empecemos 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 que 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 especifica, se utiliza la vista que tiene 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 especifica, la vista no tiene plantilla.
El método [Index] que se muestra a continuación:
public ActionResult Index()
{
return View();
}
solicita a la vista [/Views/First/Index.cshtml] que se muestre. No le transmite ninguna plantilla. Creemos [1] en la carpeta [/Views/First]:
![]() |
y, a continuación, creemos en 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>
Se trata de un código HTML clásico, salvo por las líneas 1-3, que son código C#. El programa que gestiona las vistas se denomina motor de vistas. Se encarga de gestionar todo lo que no sea HTML para convertirlo en HTML. Al fin y al cabo, 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 indican algunas reglas básicas para incluir código C# en una vista:
- el cambio de HTML a C# se produce al encontrar el carácter @ (línea 1). Si este carácter introduce un bloque de código, se pondrán las llaves (líneas 1 y 3). Si introduce una variable cuyo valor se desea recuperar, simplemente se escribirá @variable;
- el cambio de C# a HTML se produce al encontrar el carácter < (línea 5). A veces es necesario forzar esta conversión, sobre todo 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 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:
Se trata de un documento HTML puro. Todo el código C# ha desaparecido.
5.2. Utilizar 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ón 01
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 añadir propiedades, tal y 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. Se trata de la vista [/First/Action01.cshtml]. No se le transmite ninguna plantilla.
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].
Probémoslo. Solicitamos URL y [/First/Action01]:
![]() |
5.3. Utilizar 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. Así, 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]. Dado que este modelo retoma información de [ActionModel03], lo derivaremos de esta clase.
Empezamos 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;
A continuación, 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 añade la propiedad [Erreurs].
Ahora escribimos la acción [Action02], que:
- acepta como entrada el modelo de acción [ActionModel03];
- y devuelve 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 ha descrito en la página 65 y se ha incluido 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 última. Por lo tanto, se utilizará la vista por defecto [/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] creado en la línea 4.
Ahora creamos 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:
![]() |
y, a continuación, con parámetros incorrectos:
![]() |
y, por último, con parámetros correctos:
![]() |
En este ejemplo, la plantilla de vista [ViewModel01] recoge la información de la plantilla de acción [ActionModel03]. Esto suele ocurrir a menudo. En ese caso, se puede utilizar una única plantilla que sirva tanto de plantilla de acción como de vista. Creamos una nueva plantilla [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 nueva acción [Action03] de la siguiente manera:
// Acción 03
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: completada con la información [Erreurs];
Solo nos queda crear la vista [/First/Action03.cshtml]:
![]() |
- en [1]: clic con el botón derecho en el código de [Action03] y, a continuación, 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 en la lista desplegable la clase correcta, en este caso la clase [ActionModel04];
- en [5]: la vista creada.
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 habitual utilizar la misma plantilla tanto para la acción como para la vista, ya que la plantilla de la vista suele tomar información de la plantilla de la acción. De este modo, se utiliza una plantilla más amplia, que puede ser utilizada tanto por la acción como por la vista que esta genera. Hay que tener cuidado 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
A continuación, vamos a presentar 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 tiene un array de personas de tipo [Personne] definido en las líneas 12-16;
- líneas 6-10: el constructor del modelo inicializa la propiedad [Personnes] de la línea 5 con una matriz de dos personas.
La acción que generará este modelo como resultado será la siguiente [Action04]:
// Acción04
public ViewResult Action04()
{
return View(new ViewModel02());
}
- línea 2: la acción no tiene ninguna plantilla de entrada;
- línea 4: pasa a su vista por defecto, 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 matriz 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á todas las personas del modelo;
- líneas 26-27: el carácter < detiene el C# y da inicio al HTML. A continuación, vuelve a aparecer el carácter @ para cambiar a C# y escribir el nombre de la persona. Después, vuelve a aparecer 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.
La plantilla [ViewModel05] será la 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: la plantilla [ViewModel05] de la vista;
- línea 5: la lista de personas que se mostrarán en el menú desplegable con el formato [Prénom Nom];
- línea 6: el [Id] de la persona que se va a seleccionar en la lista desplegable;
- líneas 8-16: el constructor que crea una tabla con 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 han presentado en el apartado 2.5.2.6. Recordémoslas:
Lista desplegable | <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 comprendidos entre las etiquetas <option>...</option> |
atributos | name="cmbValeurs": nombre del control. size="1": número de elementos de la lista visibles. 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 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-25 genera las etiquetas <option> que se insertan dentro de la etiqueta <select> de la línea 16.
- línea 17: se recorre la lista de personas de la plantilla;
- línea 20: se comprueba si la persona actual es la que debe seleccionarse. En caso afirmativo, se prepara el texto selected="selected" que debe insertarse en la etiqueta <option>;
- línea 24: se escribe la etiqueta <option>.
Solicitamos 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.
Veamos ahora 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 ha seleccionado la persona de [Id]=2.
Los dos ejemplos anteriores nos bastarán. Al escribir una vista [Razor], hay que resistirse a 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 respetando el modelo MVC, podemos encontrarnos con mucha lógica en la vista para calcular valores intermedios. Esto puede significar que el modelo utilizado no es lo suficientemente detallado. Este debe contener los valores finales que necesita la vista para que no tenga que calcularlos ella 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 sigue siendo clara. Si se inserta demasiado código C#, la estructura HTML puede volverse ilegible.
En el ejemplo anterior, un usuario podría utilizar el menú 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 mostrará al usuario será el siguiente:
![]() |
La plantilla de la vista será la plantilla [ViewModel05] que ya se ha utilizado 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 solicitarse 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 introducida 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 apartado 2.5.2.1. Recordemos sus características:
formulario | |
etiqueta HTML | <form name="..." method="..." action="...">...</form> |
atributos | name="frmexemple": nombre del formulario — opcional method="..." : método utilizado por 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 se aplica a todos los controles que pueden encontrarse en un formulario. El objetivo de un formulario es recopilar la información introducida 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 URL del documento en el que se encuentra el formulario. Un cliente web puede utilizar dos métodos diferentes, denominados 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> indica al navegador el método que debe utilizar para enviar la información recopilada en el formulario a la URL especificada por el atributo action="URL". Cuando no se especifica el atributo method, se utiliza por defecto el método GET. |
- 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]. Es este el que activa el envío de los valores introducidos a URL, especificado por el atributo [action] de la etiqueta <form>.
¿Qué es lo que va a transmitir exactamente el navegador cuando el usuario haga clic en el botón [Valider]? Esto se ha explicado en el apartado 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 radio 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"/> | txtSaisie = programación + Web - texto introducido por el usuario en el campo de entrada. Los espacios se han sustituido por el signo + | |
<input type="password" name="txtMdp" size="20" value="unMotDePasse"/> | txtMdp=estoesecreto - texto introducido 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 introducido por el usuario en el campo de entrada. %OD%OA es el marcador de fin de línea. Los espacios se han sustituido 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ón3</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 ha utilizado 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. Así, 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, esta información no nos interesa. A veces, los formularios tienen varios botones del tipo [submit]. En ese caso, es importante saber en qué botón se ha hecho clic. 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>
Lo que se envía es 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á 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 que 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 aprecia en el URL.
En la vista que se muestra tras el POST, podría ser preferible ver los nom y prénom de la persona seleccionada en lugar de su número. Por lo tanto, hay que actualizar la vista del POST y su plantilla.
Creamos una acción [Action07] para gestionar este caso. Esta acción deberá utilizar la sesión del usuario para almacenar en ella la lista de personas. Seguiremos el modelo estudiado en el apartado 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 el menú desplegable;
Tenemos que 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 del á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()
{
...
// Enlaces 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 a 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 la plantilla de la vista;
- línea 6: se introduce 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 gestiona 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 visto. La principal diferencia está 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 la plantilla de acción [ActionModel06], ya utilizada 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 ha publicado;
- 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: la plantilla 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 hemos estudiado el siguiente formulario HTML:
1 ![]() |
Vamos a estudiar una acción [Action08Get] que muestra (GET) este formulario y una acción [Action08Post] que procesa (POST) los valores introducidos 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 sobre la acción [Action08Get];
- el modelo de la acción [Action08Post] para una consulta POST.
![]() |
![]() |
5.6.1. La plantilla de ámbito [Application]
Supondremos que los elementos mostrados por los botones de radio, las casillas de selección y las distintas listas son datos del ámbito [Application]. Es un caso frecuente. Esta información procede 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 hemos presentado 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 mostrada por 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. La plantilla 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 la plantilla 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 pasa al constructor el modelo de la aplicación que define los elementos que se van a mostrar.
5.6.3. La plantilla 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 rellenar;
- las líneas 20-24 definen los elementos que se van a mostrar. Son las diferentes colecciones del formulario. Estas se encuentran en la plantilla de la aplicación (líneas 34-38);
- líneas 10-17: definen los campos de introducción de datos 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 va a 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 del 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 texto de una 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 introducido [abcdef] en el campo de entrada.
- línea 13: [PasswordField] recuperará el valor enviado por las siguientes líneas del formulario:
<!-- el campo de introducción 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 introducido [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>
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&TextAreaField=abcdef%0D%OAhijk¶m2=val2
si el usuario ha introducido [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 selecció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 un menú desplegable. En los datos enviados, encontraremos una cadena con el siguiente formato:
param1=val1&SimpleChoiceListField=3¶m2=val2
si el usuario ha seleccionado la opción [liste3]. El atributo [value] de la opción seleccionada es 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 manteniendo pulsada la tecla [Ctrl]. En los datos enviados, encontraremos 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. Dado 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 del 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. Eso 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 ha:
- 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: ha introducido [quelques mots] en el campo de entrada;
- línea 5: ha escrito [secret] como contraseña;
- línea 6: introduce [ligne1\nligne2] en el campo de entrada multilínea;
- línea 7: seleccionada la opción [choix2] de la lista desplegable;
- línea 8: se ha seleccionado la opción [liste3] de la lista de selección única;
- línea 9: se han seleccionado las opciones [liste1] y [liste3] de la lista de selección múltiple;
Vamos a suponer que se ha producido un POST y que queremos devolver el formulario tal y como se ha rellenado. Esto es lo que se hace, en particular, cuando se devuelve al usuario un formulario con errores. Este se devuelve tal y como se ha rellenado.
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 opción -->
<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 cuyo atributo [value] tenga 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 mostrada;
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: se recorre la colección mostrada mediante las casillas de selección;
- línea 8: debe marcarse 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 saber si un valor está contenido 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 texto de una línea -->
<tr>
<td>Champ de saisie</td>
<td>
<input type="text" name="TextField" value="@Model.TextField" size="30" />
</td>
</tr>
<!-- el campo de introducción 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 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 y 12: se 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 el menú desplegable;
- línea 9: a continuación, debe seleccionarse una opción cuyo atributo [value] tenga 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 selecció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 mostrada por la lista;
- línea 9: debe seleccionarse 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 saber 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 iba a enviar 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 este modo 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 framework instanciará el modelo [ViewModel08] con el constructor sin parámetros, lo que tendrá como consecuencia 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é. En este caso, queremos inicializar el modelo únicamente con los valores enviados.
- línea 6: se instancía el modelo utilizando el constructor adecuado;
- línea 7: se inicializa con los valores enviados. Tras esta operación, el modelo se corresponde con los datos introducidos por el usuario;
- línea 8: se vuelve a mostrar el formulario. El usuario lo encontrará tal y como lo ha rellenado.
Veamos un ejemplo:
![]() |
En [2], el resultado de [POST] refleja correctamente lo que se ha introducido en [1].
5.6.6. Tratamiento de las anomalías de POST
Hemos dicho 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 ha marcado 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 la plantilla del formulario. Sin embargo, el constructor utilizado asigna la matriz ["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 diversas 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 contabilizados
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 comprueba si se han enviado determinados parámetros o no. Si no es así, se inicializan con el valor correspondiente a la ausencia de entrada por parte del usuario. La comprobación no se ha realizado para la lista desplegable, que siempre tiene un elemento seleccionado, lo que 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 el menú 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 va a utilizar como atributo [name], no nos daremos cuenta hasta el momento de la ejecución.
ASP.NET MVC ofrece métodos especializados denominados [HTML Helpers] que, como su nombre indica, tienen por objeto 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 han generado correctamente y se ha seleccionado la opción correcta.
Volvamos al código que ha generado 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 dicho modelo. El generador de código HTML utilizará el nombre de esta propiedad para generar los atributos [id] y [name] del [select] que se va a generar. Si se utiliza una propiedad inexistente, se producirá un error en la compilación y no ya en la ejecución. Se trata de una mejora con respecto a la solución anterior, en la que los errores de nomenclatura solo se detectaban en 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 una colección cualquiera 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, se trata de 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, se trata de la propiedad [Label] de la clase [Item];
- para saber qué opción debe seleccionarse (atributo selected), el framework hace lo mismo que nosotros: compara el valor de la opción con el valor actual de la propiedad [DropDownListField].
Veamos ahora los demás métodos que podemos utilizar:
Botones de radio
El nuevo código es el siguiente:
<!-- 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 va a 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). Dado que la propiedad [Propriété] solo admite un valor, cabría pensar que el framework asigna el valor [true] a [Propriété]. Bastaría con realizar una operación lógica OU entre los valores recibidos para conseguirlo;
- 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 ha marcado).
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 texto de una 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 determinados atributos de la etiqueta HTML generada, en este caso el atributo [size].
Campo de introducción de contraseña
El nuevo código es el siguiente:
<!-- el campo de introducción 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 introducción 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 selección única
El nuevo código es el siguiente:
<!-- la lista de selecció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 selecció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 hace que se pase de una lista desplegable [size=1] a una lista simple.
La lista de selecció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 introducción de datos
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 la gestión de las casillas de selección. En lugar de tener una tabla con tres casillas de selección, se han utilizado tres casillas de selección independientes (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 aspectos:
- línea 18: se utiliza la vista [Formulaire2] en lugar de la vista [Formulaire];
- ya no se gestiona el estado de las casillas de selección que no se han marcado. 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 permitirá al framework MVC saber qué etiqueta de entrada debe generar. A esta información se la 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 solicita 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 introducción 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 utilizado, 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 aún compatibles con esta versión. En el ejemplo anterior, el navegador Chrome la admite parcialmente.
5.8.1. El [POST] del formulario
El [POST] del formulario se procesa mediante 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 recogen 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.
Examinemos ahora 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] ha generado la etiqueta <label> de la línea 2. El valor del atributo [for] es el nombre de la propiedad «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 procede de esta manera. No volveremos sobre ello para las demás propiedades.
- El método [Html.EditorFor] ha generado 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] ha adquirido el valor [text] debido a los metadatos
[DataType(DataType.Text)]
- el método [Html.DisplayFor] ha generado 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 se ve influido por el metadato
[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] ha generado la etiqueta <textarea> de la línea 3. Cabe destacar 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] ha generado el texto de las líneas 4 y 5. Este es el valor de la propiedad «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] ha generado 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 framework de JavaScript para la validación de datos del lado del cliente;
- el método [Html.DisplayFor] ha generado 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. He aquí dos ejemplos:
Se introduce un número erróneo y se valida:
![]() |
En el ejemplo anterior, la validación se ha realizado del lado del cliente. El formulario no se enviará hasta que se haya corregido el error.
Otro ejemplo: no se introduce nada:
![]() |
En el caso de [1] anterior, [Action10Post] indica un error. Quizá recordemos que ya habíamos obtenido este comportamiento al utilizar el atributo [Required] en la propiedad que se desea controlar (véase la página 69), en este caso la propiedad [Number]. En este caso, no ha sido necesario 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [text]. El resto de 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 señala ningún error de validación en el lado del cliente, a diferencia del caso anterior. El error solo lo señala 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [tel]. Este valor se ha generado a partir de los metadatos:
[DataType(DataType.PhoneNumber)]
El tipo [tel] para una etiqueta <input> es una novedad respecto a HTML5. El navegador Chrome la ha tratado como una etiqueta <input> con el tipo [text].
Validación
No se ha detectado ningún error de validación ni en el lado del cliente ni en el del servidor. Se puede introducir 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [date]. Este valor se ha generado 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 introducir la fecha mediante un calendario. Por otra parte, la fecha introducida se muestra en formato [jj/mm/aaaa], es decir, Chrome adapta el formato de fecha al del navegador ([locale]).
- 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 señala una fecha no válida en el lado del cliente ([1]), lo que impide que el POST del formulario se envíe al servidor.
![]() |
La ausencia de fecha no se señala en el lado del cliente, pero sí en el 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [time]. Este valor se ha generado 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 introducir 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 introducir una hora no vá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] ha generado 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 ha generado 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] ha generado 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 ha generado porque la propiedad es booleana:
public bool Boolean { get; set; }
- el método [Html.DisplayFor] ha generado 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [email]. Este tipo es nuevo en HTML5. Este tipo se ha generado 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 ha detectado una dirección no válida en el lado del cliente [1]:
![]() |
La ausencia de datos 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [url]. Este tipo es nuevo en HTML5. Se ha generado debido a los metadatos:
[DataType(DataType.Url)]
Chrome parece tratar este tipo como un tipo [text].
- El método [Html.DisplayFor] ha generado la línea 4: un enlace al URL.
Validación
Se ha detectado un URL no válido en el lado del cliente [1]:
![]() |
La ausencia de datos no provoca 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [password]. Este tipo se ha generado debido a los metadatos:
[DataType(DataType.Password)]
- El método [Html.DisplayFor] ha generado 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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [text];
- el método [Html.DisplayFor] ha generado la línea 4, un número con dos decimales y un símbolo monetario. Se ha utilizado este formato debido a los metadatos:
[DataType(DataType.Currency)]
Validación
Se señala en el servidor un valor no válido [1] o la ausencia de valor [2]:
![]() |
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] ha generado la etiqueta <input> de la línea 3 con un atributo [type] de tipo [text]. El método [Html.DisplayFor] ha generado la línea 4. Aquí no se aprecia qué aporta el metadato:
[DataType(DataType.CreditCard)]
Validación
No se realiza ninguna comprobación, ni en el lado del cliente ni en el 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 apartado 4.5 y en los apartados siguientes. Volvemos sobre esta cuestión en el contexto de un formulario:
- cómo señalar los errores de introducción de datos al usuario;
- realizar las validaciones tanto del lado del cliente como del lado del servidor para notificar los errores al usuario con mayor rapidez.
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 introducción de datos 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 los datos introducidos mediante el método [Html.EditorFor],
- la columna 3 muestra el posible error de introducción de datos mediante 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 introducción de datos:
// Acción 11-POST
[HttpPost]
public ViewResult Action11Post(ViewModel11 modèle)
{
return View("Action11Get", modèle);
}
- línea 3: se crea la plantilla [ViewModel11] y, a continuación, se inicializa con los valores enviados. En este momento pueden producirse errores. A cada propiedad errónea P de la plantilla se le asocia un mensaje de error. Este mensaje es el que permite obtener el método [Html.ValidationMessageFor] del formulario.
A continuación se muestra un ejemplo de ejecución:
![]() | ![]() |
He aquí otro ejemplo:
![]() |
Cabe señalar que ambas fechas son erróneas (hoy es 11/10/2013), pero que los errores no se señalan. Estos errores se detectan mediante 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 han realizado en el servidor. Por lo tanto, es necesario 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 señalar al usuario sus errores lo antes posible y, en cualquier caso, antes de la función POST. Esta última solo puede ejecutarse cuando se hayan corregido todos los errores detectados.
Retomamos la plantilla [ViewModel11] anterior, pero ahora la 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 (véase 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-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ón 12-GET
[HttpGet]
public ViewResult Action12Get()
{
return View("Action12Get", new ViewModel11());
}
El formulario rellenado se procesará mediante 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:
![]() |
En cuanto se introduce un carácter en [1], aparece el mensaje de [2] porque el valor esperado debe tener al menos cuatro caracteres. De este modo, la validación se realiza con cada nuevo carácter introducido. El mensaje de error desaparece al introducir el cuarto carácter. Una vez hecho esto, validemos el formulario:
![]() |
El URL [3] nos muestra que el [POST] no se ha producido. Sin embargo, al hacer clic en el botón [Valider] se han activado todas las validaciones del lado del cliente y han aparecido nuevos mensajes de error.
Veamos, por ejemplo, el código HTML generado para la primera entrada:
<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 errónea: [data-val-regex],
- la expresión regular para la cadena introducida [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 integrado en la vista. Si este no está presente, dichos atributos simplemente se ignoran y, por lo tanto, no se realiza ninguna 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. Gestión de enlaces de navegación y de acción
Vamos a crear las dos vistas siguientes para ilustrar la gestión de enlaces en una vista:
![]() |
- en [1] y [2], hay 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 se genera mediante la vista [Action16Get.cshtml] siguiente:
@{
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 va a generar la vista;
- líneas 23-28: un formulario;
- línea 25: una etiqueta para el campo [data];
- línea 26: un campo de entrada denominado [data];
- línea 27: un enlace de tipo [submit]. Al hacer clic en él, se ejecuta la función 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 vista [Action17Get.cshtml] siguiente:
@{
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();
}
- en la 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 campo POST del formulario de la vista [Action16Get.cshtml]. Recibe el parámetro denominado [data]. Recordemos que este es el nombre del campo de entrada del formulario;
- línea 13: se introduce información en el campo [ViewBag];
- línea 14: se muestra la vista [Action16Get.cshtml].
Se invita al lector a probar este ejemplo.































































