Skip to content

5. La vue et son modèle

5.1. Introduction

Revenons à l'architecture d'une application ASP.NET MVC :

Dans le chapitre précédent, nous avons étudié comment ASP.NET MVC présentait les informations de la requête [1] à une action [2a] sous la forme d'un modèle qui pouvait contenir des contraintes de validation. Ce modèle était fourni en entrée à l'action et nous l'avons appelé le modèle de l'action. Nous nous intéressons maintenant au résultat le plus courant d'une action, le type [ViewResult] qui correspond à une vue V [3] accompagnée de son modèle M [2c]. Ce modèle sera appelé modèle de la vue V, à ne pas confondre avec le modèle de l'action que nous venons d'étudier. L'un est en entrée de l'action, l'autre est en sortie.

Commençons par créer un nouveau projet [Exemple-03] [1] toujours à l'intérieur de la même solution, de type ASP.NET MVC de base :

Créons un contrôleur nommé [First] [2]. Le code généré pour ce contrôleur est le suivant :


using System.Web.Mvc;

namespace Exemple_03.Controllers
{
  public class FirstController : Controller
  {
    public ActionResult Index()
    {
      return View();
    }

  }
}
  • lignes 7-10 : une action [Index] a été créée. Le type du résultat de la méthode [Index] est celui de la classe [ActionResult] dont dérivent la plupart des résultats possibles d'une action ;
  • ligne 9 : la méthode [View] de la classe [Controller] (ligne 5) rend un type [ViewResult] qui dérive de [ActionResult]. Cette méthode admet de nombreuses surcharges. Nous en verrons quelques unes. La principale est la suivante :
 
  • le premier paramètre est le nom de la vue. S'il est absent, la vue utilisée est celle qui porte le même nom que l'action qui produit le [ViewResult] et qui sera cherchée dans le dossier [/Views/{controller}] où {controller} est le nom du contrôleur ;
  • le second est le modèle de la vue. S'il est absent, la vue n'a pas de modèle.

La méthode [Index] ci-dessous :


public ActionResult Index()
    {
      return View();
}

demande à la vue [/Views/First/Index.cshtml] de s'afficher. Elle ne lui transmet aucun modèle. Créons [1] le dossier [/Views/First] :

puis créons dedans la vue [Index] [2] :

On indique le nom de la vue en [3]. Celle-ci est créée en [4]. Le code généré est le suivant :


@{
    Layout = null;
}

<!DOCTYPE html>

<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Index</title>
</head>
<body>
    <div>
        
    </div>
</body>
</html>

On a là du HTML classique sauf pour les lignes 1-3 qui sont du code C#. Le programme qui gère les vues est appelé un moteur de vues. Il s'occupe de gérer tout ce qui n'est pas du HTML pour en faire du HTML. Au final en effet, c'est ce qui va être envoyé au client. Le moteur de vue s'appelle ici [Razor]. Il permet d'inclure du code C# dans une vue. [Razor] va interpréter ce code C# et produire à partir de lui du code HTML. Voici quelques règle de base pour l'inclusion de code C# dans une vue :

  • le basculement du HTML vers le C# se fait à la rencontre du caractère @ (ligne 1). Si ce caractère introduit un bloc de code, on mettra les accolades (lignes 1 et 3). S'il introduit une variable dont on veut récupérer la valeur, on écrira simplement @variable ;
  • le basculement du C# vers le HTML se fait à la rencontre du caractère < (ligne 5). Parfois, on est amené à forcer ce basculement notamment lorsqu'on inclut dans la page du texte brut sans balise HTML. On utilisera alors la balise <text> pour introduire le texte : <text>ici du texte brut</text>.

La ligne 2 ci-dessus indique que la vue [Index] n'a pas de page maître.

Modifions la vue de la façon suivante :


@{
  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>
  • ligne 3 : définit une variable C# ;
  • ligne 15 : affiche la valeur de cette variable.

Demandons maintenant l'URL [/First/Index] :

 

Le code HTML reçu est le suivant :

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Index</title>
</head>
<body>
  <div>
    <h3>Vue Index</h3>
  </div>
</body>
</html>

C'est un pur document HTML. Tout code C# a disparu.

5.2. Utiliser le [ViewBag] pour passer des informations à la vue

Nous créons une nouvelle action appelée [Action01] associée à la vue [Action01.cshtml] :

L'action [Action01] est la suivante :


    // Action01
    public ViewResult Action01()
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View();
}
  • ligne 4 : on utilise la propriété [ViewBag] du contrôleur. C'est un objet dynamique auquel on peut ajouter des propriétés comme il est fait ligne 4. Cet objet a la particularité d'être également accessible à la vue. C'est donc une façon de lui transmettre de l'information ;
  • ligne 5 : la vue par défaut de l'action est demandée. C'est la vue [/First/Action01.cshtml]. Aucun modèle ne lui est transmis.

La vue [Action01.cshtml] est la suivante :


@{
  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>
  • ligne 14 : la propriété [ViewBag.info] est affichée.

Testons. Nous demandons l'URL [/First/Action01] :

 

5.3. Utiliser un modèle fortement typé pour passer des informations à la vue

La méthode précédente a l'inconvénient de ne pas permettre la détection d'erreurs avant l'exécution. Ainsi si la vue [Action01.cshtml] utilise le code


<h4>@ViewBag.Info</h4>

on aura une erreur car la propriété [Info] n'existe pas. Celle créée par l'action [Action01] s'appelle [info]. On peut alors utiliser un modèle fortement typé pour éviter ce désagrément.

Dans un des exemples étudiés précédemment, l'action était la suivante :


    // Action10
    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);
}

L'action [Action10] transmettait à son client six informations (Email, Jour, Info1, Info2, Info3, erreurs) sous la forme d'une chaîne de caractères. Nous allons transmettre ces informations dans un modèle de vue [ViewModel01]. Comme ce modèle reprend des informations de [ActionModel03] nous allons le faire dériver de cette classe.

Nous commençons par recopier [ActionModel03] du projet [Exemple-02] dans le projet [Exemple-03] actuel :

et nous changeons son espace de noms pour reprendre celui du projet [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; }
  }
}
  • ligne 2 : le nouvel espace de noms ;

Puis nous créons la classe [ViewModel01] :

Le code de [ViewModel01] est le suivant :


namespace Exemple_03.Models
{
  public class ViewModel01 : ActionModel03
  {
    public string Erreurs { get; set; }
  }
}
  • ligne 3 : la classe hérite de [ActionModel03] et donc des propriétés [Email, Jour, Info1, Info2, Info3] ;
  • ligne 5 : on lui rajoute la propriété [Erreurs].

Nous écrivons maintenant l'action [Action02] qui :

  • accepte en entrée le modèle d'action [ActionModel03] ;
  • et délivre en sortie le modèle de vue [ViewModel01].

Son code est le suivant :


    // Action02
    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});
}
  • ligne 1 : [Action02] reçoit le modèle d'action [ActionModel03]. Elle rend un résultat de type [ViewResult] ;
  • ligne 4 : les erreurs liées au modèle d'action [ActionModel03] sont agrégées dans la chaîne de caractères [erreurs]. La méthode [getErrorMessagesFor] a été décrite page 65 et a été incluse dans le contrôleur [First] du nouveau projet ;
  • ligne 5 : la méthode [View] est appelée avec un paramètre. Celui est le modèle de la vue. Cette dernière n'est pas précisée. Ce sera donc la vue par défaut [/Views/First/Action02] qui sera utilisée. Le modèle de vue [ViewModel01] est instancié et initialisé avec les cinq informations du modèle d'action [ActionModel03] et l'information [erreurs] construite ligne 4.

Nous construisons maintenant la vue [/First/Action02.cshtml] :

Son code est le suivant :


@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 nouveauté réside en ligne 1. La notation [@model] fixe le type du modèle de la vue. Ce modèle est ensuite référencé par la notation [@Model] (lignes 16-21) ;
  • lignes 15-22 : les informations du modèle sont affichées dans une liste.

Voyons quelques exemples d'exécution de l'action [Action02].

D'abord sans paramètres :

 

puis avec des paramètres incorrects :

puis avec des paramètres corrects :

Dans cet exemple, le modèle de la vue [ViewModel01] reprend les informations du modèle d'action [ActionModel03]. C'est souvent le cas. On peut alors utiliser un unique modèle qui servira à la fois de modèle d'action et de vue. Nous créons un nouveau modèle [ActionModel04] :

  

qui sera le suivant :


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
  [Bind(Exclude="Erreurs")]
  public class ActionModel04
  {
    // ---------------------- Action --------------------------------
    [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; }

    // ---------------------- vue --------------------------------
    public string Erreurs { get; set; }
  }
}
  • lignes 8-28 : le modèle de l'action avec ses contraintes d'intégrité. Ces champs feront également partie de la vue ;
  • ligne 31 : une propriété propre au modèle de la vue. Elle a été exclue du modèle de l'action par l'annotation de la ligne 5.

Nous créons la nouvelle action [Action03] suivante :


    // Action03
    public ViewResult Action03(ActionModel04 modèle)
    {
      modèle.Erreurs = getErrorMessagesFor(ModelState);
      return View(modèle);
}
  • ligne 2 : [Action03] reçoit le modèle d'action de type [ActionModel04] ;
  • ligne 5 : et rend comme modèle de vue, ce même modèle ;
  • ligne 4 : complété par l'information [Erreurs] ;

Il ne nous reste qu'à créer la vue [/First/Action03.cshtml] :

  • en [1] : clic droit dans le code de [Action03] puis [Ajouter une vue] ;
  • en [2] : le nom de la vue proposée par défaut ;
  • en [3] : indiquer qu'on crée une vue fortement typée ;
  • en [4] : prendre dans la liste déroulante la bonne classe, ici la classe [ActionModel04] ;
  • en [5] : la vue créée.

Nous donnons à la vue [Action03] le même code qu'à la vue [Action02]. Seul le modèle de vue (ligne 1) et le titre de page (ligne 11) changent :


@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>

Maintenant demandons l'action [Action03] sans paramètres :

 

Les résultats sont les mêmes qu'auparavant. Il est fréquent d'utiliser le même modèle pour l'action et la vue car le modèle de la vue reprend souvent des informations du modèle de l'action. On utilise alors un modèle plus large, utilisable à la fois par l'action et la vue que celle-ci génère. On prendra soin d'exclure de la liaison de données, les informations qui n'appartiennent pas au modèle de l'action. Sinon, un utilisateur bien renseigné pourrait initialiser des parties du modèle de la vue à notre insu.

5.4. [Razor] – premiers pas

Nous allons maintenant présenter quelques éléments des vues [Razor], principalement les instructions foreach et if.

Supposons qu'on veuille présenter une liste de personnes dans un tableau HTML. Le modèle de la vue pourrait être le suivant [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 vue du modèle est la classe [ViewModel02], lignes 3-10 ;
  • ligne 5 : le modèle possède un tableau de personnes de type [Personne] défini lignes 12-16 ;
  • lignes 6-10 : le constructeur du modèle initialise la propriété [Personnes] de la ligne 5 avec un tableau de deux personnes.

L'action produisant ce modèle en sortie sera la suivante [Action04] :


    // Action04
    public ViewResult Action04()
    {
      return View(new ViewModel02());
}
  • ligne 2 : l'action n'a pas de modèle en entrée ;
  • ligne 4 : elle passe à sa vue par défaut, une instance du modèle [ViewModel02] que nous venons de définir.

La vue [Action04.cshtml] va afficher le modèle [ViewModel02] :

Le code de la vue [Action04.cshtml] est le suivant :


@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>
  • ligne 1 : le modèle de la vue ;
  • ligne 2 : l'import de l'espace de noms de la classe [Personne] utilisée ligne 24 ;
  • lignes 16-32 : le tableau HTML qui visualise les personnes du modèle ;
  • ligne 24 : le début du code C# est signalé par le caractère @. L'instruction [foreach] va boucler sur toutes les personnes du modèle ;
  • lignes 26-27 : le caractère < arrête le C# et commence le HTML. Puis de nouveau, le caractère @ pour basculer en C# et écrire le nom de la personne. Puis de nouveau le caractère < qui fait basculer en mode HTML ;
  • ligne 28 : on écrit l'âge de la personne.

L'exécution de l'action [Action04] donne le résultat suivant :

 

D'autres éléments d'une vue peuvent être alimentés par une collection : les listes, déroulantes ou non, les boutons radio, les cases à cocher. Considérons le nouvel exemple suivant qui affiche une liste déroulante.

Le modèle [ViewModel05] sera le suivant :


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; }
  }
}
  • ligne 18 : une classe [Personne2] avec trois propriétés ;
  • ligne 3 : le modèle [ViewModel05] de la vue ;
  • ligne 5 : la liste des personnes à afficher dans la liste déroulante sous la forme [Prénom Nom] ;
  • ligne 6 : l'[Id] de la personne à sélectionner dans la liste déroulante ;
  • lignes 8-16 : le constructeur qui crée un tableau de trois personnes (lignes 10-13) et fixe l'[Id] de la personne qui doit apparaître sélectionnée.

La vue [Action05.cshtml] va afficher ce modèle :

Son code est le suivant :


@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>

Les caractéristiques de la liste déroulante HTML ont été présentées au paragraphe 2.5.2.6. Rappelons-les :

Combo

<select size="1" name="cmbValeurs">

<option value="1">choix1</option>

<option selected="selected" value="2">choix2</option>

<option value="3">choix3</option>

</select>

 

balise HTML

<select size=".." name="..">

<option [selected="selected"] value=”v”>...</option>

...

</select>

affiche dans une liste les textes compris entre les balises <option>...</option>

attributs

name="cmbValeurs" : nom du contrôle.

size="1" : nombre d'éléments de liste visibles. size="1" fait de la liste l'équivalent d'un combobox.

selected="selected" : si ce mot clé est présent pour un élément de liste, ce dernier apparaît sélectionné dans la liste. Dans notre exemple ci-dessus, l'élément de liste choix2 apparaît comme l'élément sélectionné du combo lorsque celui-ci est affiché pour la première fois.

value=”v” : si l'élément est sélectionné par l'utilisateur, c'est cette valeur [v] qui est postée au serveur. En l'absence de cet attribut, c'est le texte affiché et sélectionné qui est posté au serveur.

Le code des lignes 17-25 génère les balises <option> qui prennent place dans la balise <select> de la ligne 16.

  • ligne 17 : on parcourt la liste des personnes du modèle ;
  • ligne 20 : on regarde si la personne courante est la personne qui doit être sélectionnée. Si oui, on prépare le texte selected="selected" qui doit être inséré dans la balise <option> ;
  • ligne 24 : la balise <option> est écrite.

Demandons l'action [Action05] :

  • en [1,2], les personnes sont affichées sous la forme [Prénom Nom] ;
  • en [1,2], la personne sélectionnée est celle qui à l'[Id] égal à 2.

Examinons maintenant le code source HTML de la page ci-dessus :


<!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=&quot;selected&quot;>Pauline Pereiro</option>
      <option value="3" >Jacques Alfonso</option>
  </select>
</body>
</html>
  • lignes 10-12 : les trois balises <option> générées par le code [Razor] ;
  • ligne 11 : c'est bien la personne d'[Id]=2 qui a été sélectionnée.

Les deux exemples ci-dessus nous suffiront. Lorsqu'on écrit une vue [Razor], il faut résister à la tentation de mettre de la logique dedans. Le code C# nous le permettrait. Cependant, dans le modèle MVC, la logique doit être dans l'action ou dans les couches basses [Metier, DAO] mais pas dans la vue. Même en respectant le modèle MVC, on peut se retrouver avec beaucoup de logique dans la vue pour calculer des valeurs intermédiaires. Cela peut vouloir signifier que le modèle utilisé n'est pas assez détaillé. Celui-ci doit contenir les valeurs finales dont la vue a besoin afin qu'elle n'ait pas à les calculer elle-même. Une bonne vue est une vue où il y a un minimum de logique et où la structure HTML de la vue reste claire. Si on insère trop de code C#, la structure HTML peut devenir illisible.

Dans l'exemple ci-dessus, la liste déroulante pourrait être utilisée par un internaute et on voudrait alors savoir quelle personne il a sélectionnée. Il nous faut pour cela un formulaire.

5.5. Formulaire – premiers pas

Le formulaire présenté à l'internaute sera le suivant :

 

Le modèle de la vue sera le modèle [ViewModel05] déjà utilisé précédemment. L'action qui affichera cette vue sera la suivante :


    // Action06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}
  • ligne 2 : l'action ne peut être demandée que par une commande HTTP GET ;
  • ligne 5 : la vue [/First/Action06Get.cshtml] sera affichée avec pour modèle une instance de type [ViewModel05].

La vue [/First/Action06Get.cshtml] sera la suivante :


@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>

Les principales nouveautés sont les suivantes :

  • ligne 18 : pour que le navigateur puisse transmettre les informations saisies par un utilisateur, il nous faut un formulaire. C'est la balise <form> des lignes 18 et 31 qui le délimitent.

La balise HTML <form> a été présentée au paragraphe 2.5.2.1. Rappelons ses caractéristiques :

formulaire


<form method="post" action="FormulairePost.aspx">

balise HTML

<form name="..." method="..." action="...">...</form>

attributs

name="frmexemple" : nom du formulaire - facultatif

method="..." : méthode utilisée par le navigateur pour envoyer au serveur Web les valeurs récoltées dans le formulaire

action="..." : URL à laquelle seront envoyées les valeurs récoltées dans le formulaire.

Un formulaire Web est entouré des balises <form>...</form>. Le formulaire peut avoir un nom (name="xx"). C'est le cas pour tous les contrôles qu'on peut trouver dans un formulaire. Le but d'un formulaire est de rassembler des informations données par l'utilisateur au clavier/souris et d'envoyer celles-ci à une URL de serveur Web. Laquelle ? Celle référencée dans l'attribut action="URL". Si cet attribut est absent, les informations seront envoyées à l'URL du document dans lequel se trouve le formulaire. Un client Web peut utiliser deux méthodes différentes appelées POST et GET pour envoyer des données à un serveur web. L'attribut method="méthode", avec method égal à GET ou POST, de la balise <form> indique au navigateur la méthode à utiliser pour envoyer les informations recueillies dans le formulaire à l'URL précisée par l'attribut action="URL". Lorsque l'attribut method n'est pas précisé, c'est la méthode GET qui est prise par défaut.

  • ligne 18 : on voit que les valeurs du formulaire seront envoyées à l'URL [/First/Action06] par une commande HTTP POST ;
  • ligne 30 : un formulaire doit avoir un bouton de type [submit]. C'est lui qui déclenche l'envoi des valeurs saisies à l'URL précisée par l'attribut [action] de la balise <form>.

Que va au juste transmettre le navigateur lorsque l'internaute va cliquer sur le bouton [Valider] ? Ceci a été expliqué au paragraphe 2.5.3.1. Rappelons ce qui avait été dit :


contrôle HTML



visuel



valeur(s) renvoyée(s)


<input type="radio" value="Oui" name="R1"/>Oui

<input type="radio" name="R1" value="non" checked="checked"/>Non

R1=Oui

- la valeur de l'attribut value du bouton radio coché par l'utilisateur.

<input type="checkbox" name="C1" value="un"/>1

<input type="checkbox" name="C2" value="deux" checked="checked"/>2

<input type="checkbox" name="C3" value="trois"/>3

C1=un

C2=deux

- valeurs des attributs value des cases cochées par l'utilisateur

<input type="text" name="txtSaisie" size="20" value="qqs mots"/>

txtSaisie=programmation+Web

- texte tapé par l'utilisateur dans le champ de saisie. Les espaces ont été remplacés par le signe +

<input type="password" name="txtMdp" size="20" value="unMotDePasse"/>

txtMdp=ceciestsecret

- texte tapé par l'utilisateur dans le champ de saisie

<textarea rows="2" name="areaSaisie" cols="20">

ligne1

ligne2

ligne3

</textarea>

areaSaisie=les+bases+de+la%0D%0A

programmation+Web

- texte tapé par l'utilisateur dans le champ de saisie. %OD%OA est la marque de fin de ligne. Les espaces ont été remplacés par le signe +

<select size="1" name="cmbValeurs">

<option value='1'>choix1</option>

<option selected="selected" value='2'>choix2</option>

<option value='3'>choix3</option>

</select>

cmbValeurs=3

- attribut [value] de l'élément sélectionné par l'utilisateur

<select size="3" name="lst1">

<option selected="selected" value='1'>liste1</option>

<option value='2'>liste2</option>

<option value='3'>liste3</option>

<option value='4'>liste4</option>

<option value='5'>liste5</option>

</select>

lst1=3

- attribut [value] de l'élément sélectionné par l'utilisateur

<select size="3" name="lst2" multiple="multiple">

<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>

lst2=1

lst2=3

- attributs [value] des éléments sélectionnés par l'utilisateur

<input type="submit" value="Envoyer" name="cmdRenvoyer"/>

 

cmdRenvoyer=Envoyer

- nom et attribut value du bouton qui a servi à envoyer les données du formulaire au serveur

<input type="hidden" name="secret" value="uneValeur"/>

 

secret=uneValeur

- attribut value du champ caché

Dans notre formulaire, nous avons deux balises susceptibles d'envoyer une valeur :


    <select name="personneId">
...
</select>

et


<input name="valider" type="submit" value="Valider" />

Si l'internaute sélectionne la personne n° 2, les valeurs postées le seront sous la forme :

personneId=2&valider=Valider

Les noms des paramètres sont ceux des attributs [name] des balises concernées par le POST. Sans cet attribut, les balises n'émettent pas de valeur. Ainsi ci-dessus, on pourrait omettre l'attribut name="valider" du bouton [submit]. La valeur envoyée est l'attribut [value] du bouton. Ici cette information ne nous intéresse pas. Parfois les formulaires ont plusieurs boutons de type [submit]. Il est alors important de savoir quel bouton a été cliqué. On mettra alors l'attribut [name] aux différents boutons.

La balise <select> est composée d'une suite de balises <option> :


    <select name="personneId">
        <option value="1" >Pierre Martino</option>
        <option value="2" selected=&quot;selected&quot;>Pauline Pereiro</option>
        <option value="3" >Jacques Alfonso</option>
</select>

C'est la valeur de l'attribut [value] de l'option sélectionnée qui est postée. En d'absence de cet attribut, c'est le texte affiché par l'option, par exemple [Pierre Martino], qui est posté.

La chaîne

personneId=2&valider=Valider

va être postée à l'URL [/First/Action06] suivante :


    // Action06-POST
    [HttpPost]
    public ViewResult Action06(ActionModel06 modèle)
    {
      return View("Action06Post",modèle);
}

On se rappelle peut-être que nous avions déjà une action [Action06] :


    // Action06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}

Il est possible d'avoir deux actions de même nom à condition qu'elle ne traite pas les mêmes commandes HTTP :

  • [Action06] de la ligne 3 gère un POST (ligne 2) ;
  • [Action06] de la ligne c gère un GET (ligne b).

L'action [Action06] qui gère le POST va recevoir la chaîne de paramètres suivant :

personneId=2&valider=Valider

Il nous faut un modèle d'action pour encapsuler ces valeurs. Ce sera le modèle [ActionModel06] suivant :


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'action [Action06] reçoit ce modèle et le transmet tel quel à la vue [Action06Post] (ligne 5 de l'action) suivante :


@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>

Le modèle est affiché aux lignes 18 et 19.

Voyons un exemple :

En [1] on sélectionne la troisième personne d'[Id] égal à 3. En [2] on poste le formulaire. En [3], les valeurs reçues. En [4,5], on voit que la même URL a été appelée, l'une par un GET [4], l'autre par un POST [5]. Ceci ne se voit pas dans l'URL.

Dans la vue affichée à la suite du POST, on pourrait vouloir les nom et prénom de la personne sélectionnée plutôt que son numéro. Il faut alors faire évoluer la vue du POST et son modèle.

Nous créons une action [Action07] pour traiter ce cas. Cette action va devoir utiliser la session de l'utilisateur pour y stocker la liste des personnes. Nous allons suivre le modèle étudié au paragraphe 4.10, qui permet d'inclure les données de portée [Application] et [Session] dans le modèle de l'action.

Le modèle de la session sera la classe [SessionModel] suivante :


namespace Exemple_03.Models
{
  public class SessionModel
  {
    public Personne2[] Personnes { get; set; }
  }
}
  • ligne 2 : la session mémorisera la liste des personnes affichées dans la liste déroulante ;

Il nous faut lier le type précédent [SessionModel] à un binder que nous appellerons [SessionModelBinder]. Celui-ci sera le même que celui décrit page 82 :

  

using System.Web.Mvc;

namespace Exemple_03.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // on rend les données de portée [Session]
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

La liaison entre le modèle [SessionModel] et son binder [SessionModelBinder] se fait dans [Global.asax] :


public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      ...

      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
    }
    // Session
    public void Session_Start()
    {
      Session["data"] = new SessionModel();
    }
  }
  • ligne 8 : la liaison du modèle à son binder se fait dans [Application_Start] ;
  • ligne 13 : une instance de type [SessionModel] est mise en session associée à la clé [data].

Ceci fait, l'action [Action07] est la suivante :


    // Action07-GET
    [HttpGet]
    public ViewResult Action07(SessionModel session)
    {
      ViewModel05 modèleVue = new ViewModel05();
      session.Personnes= modèleVue.Personnes;
      return View("Action07Get", modèleVue);
}
  • ligne 3 : l'action récupère un type [SessionModel], donc la donnée de portée [Session] associée à la clé [data] ;
  • ligne 5 : on crée le modèle de la vue ;
  • ligne 6 : on met dans la session le tableau des personnes. On en aura besoin dans la requête suivante, celle du POST. Le protocole HTTP est un protocole sans état. Il faut utiliser une session pour avoir de la mémoire entre les requêtes. Une session est propre à un utilisateur et est gérée par le serveur web ;
  • ligne 7 : la vue [Action07Get.cshtml] est affichée. C'est la suivante :

@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>

Elle est identique à la vue [Action06Get.cshtml] déjà étudiée. La principale différence est ligne 7 : l'URL à laquelle seront postées les valeurs du formulaire. Celles-ci seront traitées par l'action [Action07] suivante :


    // Action07-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);
}
  • ligne 3 : les valeurs postées sont encapsulées dans le modèle d'action [ActionModel06] déjà utilisé précédemment (ci-dessous) :

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; }
  }
}
  • ligne 3 : le premier paramètre est la donnée de portée [Session] associée à la clé [data] ;
  • ligne 5 : une requête LINQ récupère la personne ayant l'[Id] qui a été posté ;
  • ligne 6 : on construit la chaîne de caractères qui doit être affichée par la vue [Action07Post] (ligne 8) ;
  • ligne 7 : pour appeler le bon constructeur [View], il faut transtyper le type [string] en [object].

La vue [Action07Post.cshtml] est la suivante :


@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>
  • ligne 1 : le modèle est de type [string] ;
  • ligne 16 : la chaîne de caractères est affichée.

Voici un exemple d'exécution :

5.6. Formulaire – un exemple complet

Nous avons étudié au paragraphe 2.5.2.1, le formulaire HTML suivant :

1

 

Nous allons étudier une action [Action08Get] qui affiche (GET) ce formulaire et une action [Action08Post] qui traite (POST) les valeurs saisies par l'utilisateur. Un schéma classique.

Le modèle de la vue [1] ci-dessus sera une instance de la classe [ViewModel08]. Cette classe sera à la fois :

  • le modèle de la vue produite par un GET sur l'action [Action08Get] ;
  • le modèle de l'action [Action08Post] pour une requête POST.

5.6.1. Le modèle de portée [Application]

Nous allons supposer que les éléments affichés par les boutons radio, les cases à cocher et les différentes listes sont des données de portée [Application]. Un cas fréquent. Ces informations proviennent d'un fichier de configuration ou d'une base de données exploités au démarrage de l'application dans la méthode [Application_Start] de [Global.asax]. Cette méthode évolue de la façon suivante :


    protected void Application_Start()
    {
....

      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());

      // données de portée [Application]
      Application["data"] = new ApplicationModel();
}
  • ligne 7 : le type [ApplicationModel] que nous allons décrire bientôt est associé au lieur de données [ApplicationModelBinder] que nous avons déjà présenté page 82 ;
  • ligne 10 : une instance de type [ApplicationModel] est enregistrée dans le dictionnaire de l'application, associée à la clé [data].

La classe [ApplicationModel] sert à encapsuler toutes les données de portée [Application]. Ici elle encapsulera les données que doit afficher le formulaire :


namespace Exemple_03.Models
{
  public class ApplicationModel
  {
    // les collections à afficher dans le formulaire
    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; }

    // initialisation des champs et collections
    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"}
      };
    }
    // l'élément des collections
    public class Item
    {
      public string Label { get; set; }
      public string Value { get; set; }
    }

  }
}
  • lignes 45-49 : l'élément des différentes collections du formulaire. [Label] est le texte affiché par l'élément du formulaire, [Value] la valeur postée par cet élément lorsqu'il est sélectionné ;
  • ligne 6 : la collection affichée par le bouton radio ;
  • ligne 7 : la collection affichée par les cases à cocher ;
  • ligne 8 : la collection affichée par la liste déroulante ;
  • ligne 9 : la collection affichée par la liste à sélection unique ;
  • ligne 10 : la collection affichée par la liste à sélection multiple ;
  • lignes 13-43 : ces collections sont intialisées par le constructeur sans paramètres de la classe.

Le différentes collections vont alimenter le formulaire suivant :

5.6.2. Le modèle de l'action [Action08Get]

Le formulaire précédent sera affiché par l'action [Action08Get] suivante :


    // Action08-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));
}
  • ligne 2 : [Action08Get] ne répondra qu'à une commande [GET] ;
  • ligne 3 : elle reçoit en paramètre, le modèle de l'application que nous venons de décrire ;
  • ligne 5 : elle initialise une information dans le conteneur dynamique [ViewBag] ;
  • ligne 6 : elle affiche la vue [/First/Formulaire.cshtml] avec le modèle [ViewModel08]. Ce modèle sera celui du formulaire présenté précédemment. Pour cela, on passe au constructeur le modèle de l'application qui définit les éléments à afficher.

5.6.3. Le modèle de la vue [Formulaire]

La classe [ViewModel08] sera le modèle du formulaire. Cette classe est la suivante :


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;

namespace Exemple_03.Models
{
  public class ViewModel08
  {
    // les champs de saisie
    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; }

    // les collections à afficher dans le formulaire
    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; }

    // constructeurs
    public ViewModel08()
    {
    }

    public ViewModel08(ApplicationModel application)
    {
      // initialisation collections
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // initialisation champs
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}
  • dans un formulaire, il y a deux types d'éléments : ceux qu'on affiche et ceux qui font l'objet d'une saisie ;
  • les lignes 20-24 définissent les éléments à afficher. Ce sont les différentes collections du formulaire. Celles-ci sont trouvées dans le modèle de l'application (lignes 34-38) ;
  • lignes 10-17 : définissent les champs de saisie du formulaire ;
  • ligne 10 : [RadioButtonField] récupèrera la valeur postée par les lignes suivantes du formulaire :

        <!-- les boutons radio -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
<input type="radio" name="RadioButtonField" value="1" />oui              
<input type="radio" name="RadioButtonField" value="2" checked=&quot;checked&quot;/>non              
          </td>
</tr>

On notera lignes 5 et 6 que l'attribut [name] des deux boutons radio est le nom de la propriété qui va être initialisée. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&RadioButtonField=2&param2=val2

si l'utilisateur a coché l'option libellée [non]. C'est en effet l'attribut [value] de l'option cochée qui est posté.

  • ligne 11 : [CheckBoxesField] récupèrera les valeurs postées par les lignes suivantes du formulaire :

        <!-- les cases à cocher -->
        <tr>
          <td>Cases à cocher</td>
          <td>
<input type="checkbox" name="CheckBoxesField" value="1" />1              
<input type="checkbox" name="CheckBoxesField" value="2" checked=&quot;checked&quot;/>2              
<input type="checkbox" name="CheckBoxesField" value="3" />3              
</td>

On notera lignes 5 et 6 que l'attribut [name] des cases à cocher est le nom de la propriété qui va être initialisée. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&CheckBoxesField=2&CheckBoxesField=3&param2=val2

si l'utilisateur a coché les cases à cocher libellées [2] et [3]. C'est l'attribut [value] des options cochées qui est posté. C'est parce que plusieurs paramètres de même nom peuvent être postés que [CheckBoxesField] est un tableau de valeurs et non une valeur simple. Si aucune case n'est cochée, le paramètre [CheckBoxesField] sera absent de la chaîne postée et la propriété de même nom du modèle ne sera pas initialisée. Ce peut être gênant comme nous le verrons.

  • ligne 12 : [TextField] récupèrera la valeur postée par les lignes suivantes du formulaire :

          <!-- le champ de saisie texte monoligne -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="quelques mots" size="30" />
            </td>
</tr>

Ligne 5, l'attribut [name] du champ de saisie est le nom de la propriété qui va être initialisée. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&TextField=abcdef&param2=val2

si l'utilisateur a saisi [abcdef] dans le champ de saisie.

  • ligne 13 : [PasswordField] récupèrera la valeur postée par les lignes suivantes du formulaire :

        <!-- le champ de saisie d'un mot de passe -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="secret" size="30" />
          </td>
</tr>

Ligne 5, l'attribut [name] du champ de saisie est le nom de la propriété qui va être initialisée. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&PasswordField=abcdef&param2=val2

si l'utilisateur a saisi [abcdef] dans le champ de saisie.

  • ligne 14 : [TextAreaField] récupèrera la valeur postée par les lignes suivantes du formulaire :

        <!-- le champ de saisie texte multilignes -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">ligne1
ligne2</textarea>
          </td>
</tr>

Ligne 5, l'attribut [name] du champ de saisie est le nom de la propriété qui va être initialisée. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&TextAreaField=abcdef%0D%OAhijk&param2=val2

si l'utilisateur a saisi [abcdef] suivi d'un saut de ligne et de [ijk] dans le champ de saisie.

  • ligne 15 : [DropDownListField] récupèrera la valeur postée par les lignes suivantes du formulaire :

        <!-- la liste déroulante -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
<option value="1" >choix1</option>
<option value="2" selected=&quot;selected&quot;>choix2</option>
<option value="3" >choix3</option>
            </select>
</tr>

Ligne 5, l'attribut [name] de la balise <select> est le nom de la propriété qui va être initialisée. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&DropDownListField=1&param2=val2

si l'utilisateur a sélectionné l'option [choix1]. C'est l'attribut [value] de l'option sélectionnée qui est postée.

  • ligne 16 : [SingleChoiceListField] récupèrera la valeur postée par les lignes suivantes du formulaire :

        <!-- la liste à choix unique -->
        <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=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>

            </select>
</tr>

Ligne 5, l'attribut [name] de la balise <select> est le nom de la propriété qui va être initialisée. C'est l'attribut [size="3"] qui fait qu'on n'a pas une liste déroulante. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&SimpleChoiceListField=3&param2=val2

si l'utilisateur a sélectionné l'option [liste3]. C'est l'attribut [value] de l'option sélectionnée qui est postée. Le paramètre [SingleChoiceListField] peut être absent de la chaîne postée si aucun élément n'a été sélectionné.

  • ligne 17 : [MultipleChoiceListField] récupèrera les valeurs postées par les lignes suivantes du formulaire :

        <!-- la liste à choix multiple -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
            <select name="MultipleChoiceListField" size="3" multiple="multiple">
<option value="1" selected=&quot;selected&quot;>liste1</option>
<option value="2" >liste2</option>
<option value="3" selected=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
            </select>
</tr>

Ligne 5, l'attribut [name] de la balise <select> est le nom de la propriété qui va être initialisée. C'est l'attribut [size="3"] qui fait qu'on n'a pas une liste déroulante et l'attribut [multiple] qui fait que l'utilisateur peut sélectionner plusieurs éléments en maintenant appuyée la touche [Ctrl]. Dans les données postées, on trouvera une chaîne de la forme :


param1=val1&MultipleChoiceListField=1&MultipleChoiceListField=3&param2=val2

si l'utilisateur a sélectionné les options [liste1] et [liste3]. C'est l'attribut [value] des options sélectionnées qui est postée. C'est parce que plusieurs paramètres de même nom peuvent être postés que [MultipleChoiceListField] est un tableau de valeurs et non une valeur simple. Si aucune case n'est cochée, le paramètre [MultipleChoiceListField] sera absent de la chaîne postée et la propriété de même nom du modèle ne sera pas initialisée.

Les différents champs de saisie présentés précédemment vont recevoir les valeurs postées par le formulaire. On peut également les initialiser avant d'envoyer le formulaire. C'est ce qui a été fait ici :


      // initialisation champs
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };

Si ces valeurs avaient été obtenues après un POST du formulaire, cela signifierait que l'utilisateur a  :

  • ligne 2 : coché l'option [non] du bouton radio ;
  • ligne 3 : coché l'option [2] des cases à cocher ;
  • ligne 4 : tapé [quelques mots] dans le champ de saisie ;
  • ligne 5 : tapé [secret] comme mot de passe ;
  • ligne 6 : tapé [ligne1\nligne2] dans le champ de saisie multilignes ;
  • ligne 7 : sélectionné l'option [choix2] de la liste déroulante ;
  • ligne 8 : sélectionné l'option [liste3] de la liste à choix unique ;
  • ligne 9 : sélectionné les options [liste1] et [liste3] de la liste à choix multiples;

Nous allons faire comme si un POST avait eu lieu et que l'on voulait renvoyer le formulaire tel qu'il a été saisi. C'est notamment ce qui est fait lorsqu'on renvoie à l'utilisateur un formulaire erroné. Celui-ci est renvoyé tel qu'il a été saisi.

5.6.4. La vue [Formulaire]

La vue [/First/Formulaire.cshtml] affiche le formulaire :


@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>
        <!-- les boutons radio -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
            {
              string strChecked = item.Value == @Model.RadioButtonField ? "checked=\"checked\"" : "";
              <input type="radio" name="RadioButtonField" value="@item.Value" @strChecked/>@item.Label
              <text/>
            }
          </td>
        </tr>
...
      </tbody>
    </table>
    <input type="submit" value="Valider" />
  </form>
</body>
</html>
  • ligne 1 : [ViewModel08] est le modèle du formulaire ;
  • ligne 12 : la balise <form> du formulaire. Celui-ci sera posté avec la méthode [POST] (attribut method) à l'URL [/First/Action08Post] (attribut action) ;
  • ligne 33 : le bouton de type [submit] qui sert à poster le formulaire ;
  • lignes 22-27 : affichent les boutons radio :
  
  • ligne 22 : on parcourt la collection affichée par le bouton radio ;
  • ligne 24 : le bouton qui a comme attribut [value] la valeur de la propriété [RadioButtonField] doit être coché. Pour cela, il doit avoir l'attribut [checked="checked"] ;
  • ligne 25 : génération de la balise <input type="radio"> avec pour valeur [@item.Value] et pour libellé [@item.Label] ;
  • ligne 26 : la balise <text/> n'est pas une balise HTML reconnue. Elle est là pour [Razor]. A sa rencontre, [Razor] va générer un saut de ligne. Cela n'a pas d'incidence sur le formulaire affiché mais cela en a une sur le code HTML généré. Les balises <input type="radio"> sont alors sur deux lignes différentes au lieu d'être sur la même ligne. Cela rend plus lisible le code, lorsqu'avec le navigateur on demande à voir le code source de la page affichée ;

Nous passons en revue, les autres éléments de la vue :


        <!-- les cases à cocher -->
        <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>
  • ligne 6 : on parcourt la collection affichée par les cases à cocher ;
  • ligne 8 : une case qui a comme attribut [value] l'une des valeurs de la propriété [CheckBoxesField] doit être cochée. Pour cela, il doit avoir l'attribut [checked="checked"]. On utilise une expression LINQ qui permet de savoir si une valeur est contenue dans un tableau ;
  • ligne 25 : génération de la balise <input type="checkbox"> avec pour valeur [@item.Value] et pour libellé [@item.Label] ;

<!-- le champ de saisie texte monoligne -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="@Model.TextField" size="30" />
            </td>
          </tr>
        <!-- le champ de saisie d'un mot de passe -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
          </td>
        </tr>
        <!-- le champ de saisie texte multilignes -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
          </td>
        </tr>
  • lignes 5, 12 : on donne à l'attribut [value] de la balise, la valeur du modèle ;
  • ligne 19 : idem mais avec une syntaxe différente.

        <!-- la liste déroulante -->
        <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>
  • ligne 7 : on parcourt la collection affichée par la liste déroulante ;
  • ligne 9 : une option qui a comme attribut [value] la valeur de la propriété [DropDownListField] doit alors être sélectionnée. Pour cela, elle doit avoir l'attribut [selected="selected"] ;
  • ligne 25 : génération de la balise <option value="valeur">libellé</option> avec pour valeur [@item.Value] et pour libellé [@item.Label] ;

        <!-- la liste à choix unique -->
        <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>

L'explication est la même que pour la liste déroulante.


        <!-- la liste à choix multiple -->
        <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>
  • ligne 7 : on parcourt la collection affichée par la liste ;
  • ligne 9 : une option qui a comme attribut [value] l'une des valeurs de la propriété [MultipleChoiceListField] doit être sélectionnée. Pour cela, elle doit avoir l'attribut [selected="selected"]. On utilise une expression LINQ qui permet de savoir si une valeur est contenue dans un tableau ;
  • ligne 10 : génération de la balise <option value="valeur">libellé</option> avec pour valeur [@item.Value] et pour libellé [@item.Label] ;

5.6.5. Traitement du POST du formulaire

Nous avons vu que le formulaire allait être posté à l'action [Action08Post] :


  <form method="post" action="Action08Post">

L'action [Action08Post] est la suivante :


    // Action08-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);
}
  • ligne 3 : le modèle de l'application est en paramètre ainsi que les valeurs postées. Celles-ci sont disponibles dans un type [FormCollection]. La valeur du paramètre [RadioButtonField] posté est obtenue par l'expression posted[" RadioButtonField"]. On obtient là une chaîne de caractères ou le pointeur null. Si on écrit posted[" CheckBoxesField"], on aura un tableau de chaînes de caractères ou le pointeur null ;
  • pourquoi ne pas écrire :

public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)

Il y a deux raisons :

  • la première est que le framework va instancier le modèle [ViewModel08] avec le constructeur sans paramètres, ce qui aura pour effet de ne pas initialiser les collections du modèle ;
  • le seconde est qu'on veut contrôler ce qui va dans le modèle. On sait qu'il y a quatre sources possibles pour le modèle, les paramètres d'un GET, d'un POST, de la route utilisée et ceux d'un fichier uploadé. Ici, on veut initialiser le modèle uniquement avec les valeurs postées.
  • ligne 6 : on instancie le modèle en utilisant le bon constructeur ;
  • ligne 7 : on l'initialise avec les valeurs postées. Après cette opération, le modèle correspond à la saisie qui a été faite par l'utilisateur ;
  • ligne 8 : on affiche de nouveau le formulaire. L'utilisateur va le retrouver tel qu'il a été saisi.

Voyons un exemple :

En [2], le résultat du [POST] reflète bien ce qui a été saisi en [1].

5.6.6. Traitement des anomalies du POST

Nous avons dit que si aucune valeur n'était cochée ou sélectionnée pour les champs [CheckBoxesField, SimpleChoiceListField, MultipleChoiceListField] alors les paramètres correspondant ne faisaient pas partie de la chaîne postée et que donc les propriétés de mêmes noms du modèle n'étaient pas initialisées.

Voyons l'exemple suivant :

  • en [1], aucune case à cocher ne l'a été ;
  • en [2], le [POST] ramène une case cochée.

L'explication est la suivante :

  • parce qu'il n'y a aucune case cochée, le paramètre [CheckBoxesField] ne fait pas partie des valeurs postées ;
  • l'action [Action08Post] procède de la façon suivante :

    [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);
}
  • ligne 5 : le modèle du formulaire est instancié. Or le constructeur utilisé affecte le tableau ["2"] à la propriété [CheckBoxesField] ;
  • ligne 6 : les valeurs postées sont enregistrées dans le modèle. Comme le paramètre [CheckBoxesField] ne fait pas partie des valeurs postées, la propriété de même nom n'est pas affectée. Elle reste donc avec sa valeur ["2"], ce qui fait qu'à l'affichage la case n° 2 est cochée alors qu'elle ne devrait pas l'être.

Ce problème peut se régler de diverses façons. Nous choisissons de le régler dans le code de l'action [Action08Post] :


// Action08-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);
      // traitement des valeurs non postées
      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[] { };
      }
      // affichage formulaire
      return View("Formulaire", modèle);
    }
  • lignes 9-20 : on vérifie si certains paramètres ont été postés ou non. Si ce n'est pas le cas, on les initialise avec la valeur qui correspond à l'absence de saisie faite par l'utilisateur. Le test n'a pas été fait pour la liste déroulante qui elle, a toujours un élément sélectionné, ce qui n'est pas le cas pour les autres listes.

Le lecteur est invité à tester cette nouvelle version.

5.7. Utilisation de méthodes spécialisées dans la génération de formulaire

5.7.1. Le nouveau formulaire

Nous créons un nouveau formulaire [Formulaire2.cshtml] qui délivrera un formulaire identique au précédent :

Revenons sur le code utilisé pour générer la liste déroulante du formulaire :


        <!-- la liste déroulante -->
        <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>

Ce code présente deux inconvénients :

  • le plus important est qu'on perd de vue la nature du composant, ici une liste déroulante, à cause de la complexité du code ;
  • ligne 5 : si on se trompe dans le nom de la propriété du modèle à utiliser comme attribut [name], on ne s'en apercevra qu'à l'exécution.

ASP.NET MVC offre des méthodes spécialisées appelées [HTML Helpers] qui, comme leur nom l'indique, vise à faciliter la génération du HTML, notamment pour les formulaires. Avec ces classes, la liste déroulante précédente s'écrit comme suit :


        <!-- la liste déroulante -->
        <tr>
          <td>Liste déroulante</td>
          <td>@Html.DropDownListFor(m => m.DropDownListField,
           new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
          </td>
</tr>

La liste déroulante est générée par les lignes 4-5. Le code est nettement moins complexe. Le code HTML généré pour la liste déroulante est le suivant :


        <!-- la liste déroulante -->
        <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>
  • ligne 4 : l'attribut [name] est correct ;
  • lignes 4-6 : les options sont correctement générées et la bonne option a été sélectionnée.

Revenons au code qui a généré ces lignes HTML :


@Html.DropDownListFor(m => m.DropDownListField, new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
  • le premier paramètre est une fonction lambda (c'est son nom) où m représente le modèle de la vue et m.DropDowListField est une propriété de ce modèle. Le générateur de code HTML va utiliser le nom de cette propriété pour générer les attributs [id] et [name] du [select] qui va être généré. Si on utilise une propriété inexistante, on aura une erreur à la compilation et non plus à l'exécution. C'est une amélioration vis à vis de la solution précédente où les erreurs de nommage n'étaient détectées qu'à l'exécution ;
  • le second paramètre sert à désigner la collection d'éléments qui va alimenter la liste déroulante. La classe [SelectList] permet de construire cette collection :
    • son premier paramètre est une collection quelconque d'éléments. Ici on a une collection de type [Item] ;
    • son second paramètre est la propriété des éléments qui fournira la valeur de la balise <option>. Ici, c'est la propriété [Value] de la classe [Item] ;
    • son troisième paramètre est la propriété des éléments qui fournira le libellé de la balise <option>. Ici, c'est la propriété [Label] de la classe [Item] ;
  • pour savoir quelle option doit être sélectionnée (attribut selected), le framework fait comme nous : il compare la valeur de l'option à la valeur actuelle de la propriété [DropDownListField].

Voyons maintenant les autres méthodes que nous pouvons utiliser :

Boutons radio

Le nouveau code est le suivant :


        <!-- les boutons radio -->
        <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>

Le code HTML généré est le suivant :


        <!-- les boutons radio -->
        <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>

La méthode utilisée est [Html.RadioButtonFor] :

@Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)
  • le premier paramètre est la propriété du modèle qui va être associée au bouton radio (attribut [name]) ;
  • le second paramètre est la valeur à attribuer au bouton radio (attribut [value]).

Cases à cocher

Le code évolue de la façon suivante :


        <!-- les cases à cocher -->
        <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>

La méthode utilisée pour générer des cases à cocher est [Html.CheckBoxFor] :

Html.CheckBoxFor(m=>m.Propriété)

La paramètre est la propriété booléenne du modèle qui sera associée à la case à cocher. Si [Propriété=true], la case sera cochée. Si [Propriété=false], la case ne sera pas cochée. Dans tous les cas, l'attribut [value] vaut true. Le code HTML généré est le suivant :


<input id="Propriété" name="Propriété" type="checkbox" value="true" />
<input name="Propriété" type="hidden" value="false" />
  • ligne 1 : la case à cocher avec l'attribut [value="true"] ;
  • ligne 2 : un champ caché (type=hidden) de même nom [Propriété] que la case à cocher avec l'attribut [value="false"]. Pourquoi deux balises [input] de même nom ? Il y a deux cas :
  • la case de la ligne 1 est cochée. Alors la chaîne de paramètres postée est Propriété=true&Propriété=false (lignes 1 et 2). Comme la propriété [Propriété] n'attend qu'une valeur, on peut penser que le framework affecte la valeur [true] à [Propriété]. Il lui suffirait de faire un OU logique entre les valeurs reçues pour y arriver ;
  • la case de la ligne 1 n'est pas cochée. Alors la chaîne de paramètres postée est Propriété=false (ligne 2 uniquement) et alors la propriété [Propriété] reçoit la valeur [false], ce qui est correct (la case n'a pas été cochée).

Champ de saisie monoligne

Le nouveau code est le suivant :


          <!-- le champ de saisie texte monoligne -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              @Html.TextBoxFor(m => m.TextField, new { size = "30" })
            </td>
</tr>

Le code HTML généré est le suivant :


          <!-- le champ de saisie texte monoligne -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input id="TextField" name="TextField" size="30" type="text" value="quelques mots" />
            </td>
</tr>

La méthode utilisée est la suivante :


@Html.TextBoxFor(m => m.TextField, new { size = "30" })
  • le premier paramètre précise la propriété du modèle associé au champ de saisie. Le nom de la propriété sera utilisé dans les attributs [name] et [id] de la balise <input> générée et sa valeur sera affectée à l'attribut [value] ;
  • le second paramètre est une classe anonyme précisant certains attributs de la balise HTML générée, ici l'attribut [size].

Champ de saisie d'un mot de passe

Le nouveau code est le suivant :


        <!-- le champ de saisie d'un mot de passe -->
        <tr>
          <td>Mot de passe</td>
          <td>
            @Html.PasswordFor(m => m.PasswordField, new { size = "15" })
          </td>
</tr>

Le code HTML généré est le suivant :


        <!-- le champ de saisie d'un mot de passe -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input id="PasswordField" name="PasswordField" size="15" type="password" />
          </td>
</tr>

La méthode utilisée est la suivante :


@Html.PasswordFor(m => m.PasswordField, new { size = "15" })

Le fonctionnement est analogue à celui de la méthode [Html.TexBoxFor].

Champ de saisie multi-lignes

Le nouveau code est le suivant :


        <!-- le champ de saisie texte multilignes -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            @Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
          </td>
</tr>

Le code HTML généré est le suivant :


        <!-- le champ de saisie texte multilignes -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea cols="30" id="TextAreaField" name="TextAreaField" rows="5">
ligne1
ligne2</textarea>
          </td>
</tr>

La méthode utilisée est la suivante :


@Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })

Le fonctionnement est analogue à celui de la méthode [Html.TexBoxFor].

Liste à choix unique

Le nouveau code est le suivant :


        <!-- la liste à choix unique -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
          @Html.DropDownListFor(m => m.SimpleChoiceListField, new SelectList(@Model.SimpleChoiceListFieldItems, "Value", "Label"), new { size = "3" })
</tr>

et le code HTML généré le suivant :


        <!-- la liste à choix unique -->
        <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>

Nous avons déjà étudié la méthode [Html.DropDownListFor]. La seule différence est ici le troisième paramètre qui sert à préciser un attribut [size] différent de 1. C'est cette caractéristique qui fait passer d'une liste déroulante [size=1] à une liste simple.

La liste à choix multiples

Le nouveau code est le suivant :


        <!-- la liste à choix multiple -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
          @Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
</tr>

et le code HTML généré le suivant :


        <!-- la liste à choix multiple -->
        <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>

La méthode


@Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })

fonctionne comme la méthode [Html.DropDownListFor] si ce n'est qu'elle génère une liste à sélection multiple. Les options sélectionnées sont celles qui ont leur valeur (attribut value) dans le tableau [MultipleChoiceListField].

La balise <form> peut être elle aussi, générée avec une méthode :


  @using (Html.BeginForm("Action09Post", "First"))
  {
...
  }

Le code HTML généré est le suivant :


<form action="/First/Action09Post" method="post">    
    ...
</form>

La méthode


Html.BeginForm("Action09Post", "First")

a pour premier paramètre le nom d'une action et pour second paramètre le nom d'un contrôleur.

5.7.2. Les actions et le modèle

Le formulaire sera délivré par l'action [Action09Get] suivante :


    // Action09-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 vue délivrée ligne 6 est [Formulaire2] associée au modèle [ViewModel09] suivant :


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;

namespace Exemple_03.Models
{
  public class ViewModel09
  {
    // les champs de saisie
    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; }

    // les collections à afficher dans le formulaire
    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; }

    // constructeurs
    public ViewModel09()
    {
    }

    public ViewModel09(ApplicationModel application)
    {
      // initialisation collections
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // initialisation champs
      RadioButtonField = "2";
      CheckBoxField2 = true;
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}

[ViewModel09] différe de [ViewModel08] par sa gestion des cases à cocher. Plutôt que d'avoir un tableau de trois cases à cocher, on a utilisé trois cases à cocher séparées (lignes 11-13).

Le formulaire sera traité par l'action [Action09Post] suivante :


    // Action09-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);
      // traitement des valeurs non postées
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // affichage formulaire
      return View("Formulaire2", modèle);
}

L'action [Action09Post] est identique à l'action [Action08Post] sauf sur deux points :

  • ligne 18 : la vue [Formulaire2] est utilisée à la place de la vue [Formulaire] ;
  • on n'a plus la gestion des cases à cocher qui ont été non cochées. C'est désormais géré correctement par la méthode [Html.CheckBoxFor].

5.8. Génération d'un formulaire à partir des métadonnées du modèle

Il existe d'autres méthodes que les précédentes pour générer un formulaire. L'une d'elles consiste à associer des informations à une rubrique du modèle qui vont permettre au framework MVC de savoir quelle balise de saisie il doit générer. On appelle ces informations des métadonnées.

Considérons le modèle de vue [ViewModel10] suivant :


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; }

    // constructeur
    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";
    }
  }
}

Les métadonnées sont formées des balises [Display, DataType, UIHint].

Ce modèle de vue sera construit par l'action [Action10Get] suivante :


    // Action10-GET
    [HttpGet]
    public ViewResult Action10Get()
    {
      return View(new ViewModel10());
}

Ci-dessus ligne 5, on demande à la vue par défaut de l'action [/First/Action10Get.cshtml ] d'afficher le modèle de vue de type [ViewModel10]. Cette vue est la suivante :


@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>

Pour chacune des propriétés du modèle, nous utilisons la méthode :

  • Html.LabelFor pour afficher la valeur de la métadonnée [DisplayName] de la propriété ;
  • Html.EditorFor pour générer la balise HTML de saisie de la valeur de la propriété. Cette méthode va utiliser les métadonnées [DataType] et [UIHint] de la propriété ;
  • Html.DisplayFor pour afficher la valeur de la propriété selon le format indiqué par la métadonnée [DataType].

Voici un exemple d'exécution avec le navigateur Chrome :

Image

Selon le navigateur utilisé, on peut avoir des pages différentes. En effet, la vue générée utilise les nouvelles balises amenées par la version 5 de HTML appelée HTML5. Tous les navigateurs ne supportent pas encore cette version. Ci-dessus, le navigateur Chrome la supporte en partie.

5.8.1. Le [POST] du formulaire

Le [POST] du formulaire est traité par l'action [Action10Post] suivante :


    // Action10-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);
}
  • ligne 3 : l'action [Action10Post] a pour modèle en entrée le formulaire posté ;
  • ligne 5 : on récupère les erreurs de validation de ce formulaire ;
  • ligne 6 : on prépare la réponse texte au client ;
  • ligne 7 : on l'envoie.

Examinons maintenant les propriétés du modèle [ViewModel10] une par une et voyons comment les métadonnées associées influencent le HTML généré et la validation des champs de saisie.

5.8.2. Propriété [Text]

Définition


    [Display(Name="Text")]
    [DataType(DataType.Text)]
    public string Text { get; set; }
...
Text = "tra la la";

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Text)</td>
        <td>@Html.EditorFor(m => m.Text)</td>
        <td>@Html.DisplayFor(m => m.Text)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.LabelFor] a généré la balise <label> de la ligne 2. La valeur de l'attribut [for] est le nom de la propriété paramètre de la méthode [Html.LabelFor]

public string Text { get; set; }

Le texte affiché entre le début et la fin de la balise est le texte de la métadonnée


[Display(Name="Text")]

La méthode [Html.LabelFor] procède toujours ainsi. Nous ne reviendrons pas dessus pour les autres propriétés.

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3. On notera qu'elle a un attribut [class] qui associe la classe CSS [text-box single-line] à la balise. Les attributs [id] et [name] ont pour valeur le nom [Text] de la propriété paramètre de la méthode [Html.EditorFor]. L'attribut [type] a eu la valeur [text] à cause de la métadonnée

[DataType(DataType.Text)]
  • la méthode [Html.DisplayFor] a généré le texte de la ligne 4. C'est la valeur de la propriété paramètre de la méthode [Html.DisplayFor ]. Cette méthode est influencée par la métadonnée

[DataType(DataType.Text)]

qui fait que la valeur est affichée comme un texte non formaté.

5.8.3. Propriété [MultiLineText]

Définition


    [Display(Name = "TextArea")]
    [DataType(DataType.MultilineText)]
public string MultiLineText { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.MultiLineText)</td>
        <td>@Html.EditorFor(m => m.MultiLineText)</td>
        <td>@Html.DisplayFor(m => m.MultiLineText)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <textarea> de la ligne 3. On notera qu'elle a un attribut [class] qui associe la classe CSS [text-box multi-line] à la balise. Les attributs [id] et [name] ont pour valeur le nom [MultiLineText] de la propriété paramètre de la méthode [Html.EditorFor]. C'est toujours ainsi. Nous ne le mentionnerons plus. La balise générée est <textarea> à cause de la métadonnée

[DataType(DataType.MultilineText)]

qui précisait que la propriété était un texte multi-ligne.

  • la méthode [Html.DisplayFor] a généré le texte des lignes 4-5. C'est la valeur de la propriété paramètre de la méthode [Html.DisplayFor ].

5.8.4. Propriété [Number]

Définition


    [Display(Name = "Number")]
public int Number { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Number)</td>
        <td>@Html.EditorFor(m => m.Number)</td>
        <td>@Html.DisplayFor(m => m.Number)</td>
</tr>

Visuel

 

HTML généré


<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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [number]. Apparemment simplement parce que la propriété a le type [int]. Les attributs [data-val], [data-val-number] et [data-val-required] sont des attributs non reconnus par HTML5. Ils sont utilisés par un framework Javascript de validation des données côté client ;
  • la méthode [Html.DisplayFor] a généré le texte de la ligne 4, la valeur de la propriété.

Validation

Les attributs [data-x] influencent la validation des données côté client. Voici deux exemples :

On saisit un nombre erroné et on valide :

 

Ci-dessus, la validation a eu lieu côté client. Le formulaire ne sera pas posté tant que l'erreur n'aura pas été corrigée.

Autre exemple, on ne saisit rien :

En [1] ci-dessus, [Action10Post] signale une erreur. On se souvient peut-être qu'on avait déjà obtenu ce comportement en utilisant l'attribut [Required] sur la propriété à contrôler (cf page 69), ici la propriété [Number]. Ici, on n'a pas eu à le faire.

5.8.5. Propriété [Decimal]

Définition


    [Display(Name = "Decimal")]
    [UIHint("Decimal")]
public double Decimal { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Decimal)</td>
        <td>@Html.EditorFor(m => m.Decimal)</td>
        <td>@Html.DisplayFor(m => m.Decimal)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [text]. Les autres attributs sont identiques à ceux générés pour la propriété [Number] précédente. La métadonnée :

[UIHint("Decimal")]

fait que la valeur de la propriété est affichée avec deux décimales pour les deux méthodes [Html.EditorFor] et [Html.DisplayFor]

Validation

Aucune erreur de validation n'est signalée côté client, contrairement au cas précédent. L'erreur n'est signalée que par l'action [Action10Post]. Là encore, le nombre décimal est requis sans qu'on ait besoin de lui mettre l'attribut [Required].

5.8.6. Propriété [Tel]

Définition


    [Display(Name = "Tel")]
    [DataType(DataType.PhoneNumber)]
public string Tel { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Tel)</td>
        <td>@Html.EditorFor(m => m.Tel)</td>
        <td>@Html.DisplayFor(m => m.Tel)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [tel]. Cette valeur a été générée à cause de la métadonnée :

[DataType(DataType.PhoneNumber)]

Le type [tel] pour une balise <input> est une nouveauté de HTML5. Le navigateur Chrome l'a traitée comme une balise <input> avec le type [text].

Validation

Aucune erreur de validation n'est signalée côté client ou côté serveur. On peut saisir n'importe quoi.

5.8.7. Propriété [Date]

Définition


    [Display(Name = "Date")]
    [DataType(DataType.Date)]
public DateTime Date { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Date)</td>
        <td>@Html.EditorFor(m => m.Date)</td>
        <td>@Html.DisplayFor(m => m.Date)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [date]. Cette valeur a été générée à cause de la métadonnée :

[DataType(DataType.Date)]

Le type [date] pour une balise <input> est une nouveauté HTML5. Le navigateur Chrome la reconnaît et permet de saisir la date avec un calendrier. Par ailleurs, la date saisie est présentée au format [jj/mm/aaaa], ç-à-d que Chrome adapte le format de date à la [locale] du navigateur.

  • la méthode [Html.DisplayFor] a écrit elle aussi la date sous la forme [jj/mm/aaaa] toujours à cause de la présence de la métadonnée [Date].

Validation

Une date invalide est signalée côté client [1] empêchant le POST du formulaire au serveur.

L'absence de date n'est pas signalée côté client mais elle l'est côté serveur [2].

5.8.8. Propriété [Time]

Définition


    [Display(Name = "Time")]
    [DataType(DataType.Time)]
public DateTime Time { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Time)</td>
        <td>@Html.EditorFor(m => m.Time)</td>
        <td>@Html.DisplayFor(m => m.Time)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [time]. Cette valeur a été générée à cause de la métadonnée :

[DataType(DataType.Time)]

Le type [time] pour une balise <input> est une nouveauté HTML5. Le navigateur Chrome la reconnaît et permet de saisir une heure sous la forme [hh:mm] ;

  • la méthode [Html.DisplayFor] a écrit elle aussi l'heure sous la forme [hh:mm] toujours à cause de la présence de la métadonnée [Time].

Validation

Il n'est techniquement pas possible de saisir une heure invalide. L'absence d'heure est signalée côté serveur :

 

5.8.9. Propriété [HiddenInput]

Définition


    [Display(Name = "HiddenInput")]
    [UIHint("HiddenInput")]
public string HiddenInput { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.HiddenInput)</td>
        <td>@Html.EditorFor(m => m.HiddenInput)</td>
        <td>@Html.DisplayFor(m => m.HiddenInput)</td>
</tr>

Visuel

 

HTML généré


      <tr>
        <td><label for="HiddenInput">HiddenInput</label></td>
        <td>cach&#233;<input id="HiddenInput" name="HiddenInput" type="hidden" value="caché" /></td>
        <td>cach&#233;</td>
</tr>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [hidden], ç-à-d un champ caché (mais néanmoins posté). Cette valeur a été générée à cause de la métadonnée :

[UIHint("HiddenInput")]
  • la méthode [Html.DisplayFor] a écrit elle la valeur du champ caché.

5.8.10. Propriété [Boolean]

Définition


    [Display(Name = "Boolean")]
public bool Boolean { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Boolean)</td>
        <td>@Html.EditorFor(m => m.Boolean)</td>
        <td>@Html.DisplayFor(m => m.Boolean)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [checkbox], ç-à-d une case à cocher. Cette valeur a été générée parce que la propriété est booléenne :

public bool Boolean { get; set; }
  • la méthode [Html.DisplayFor] a généré la ligne 4, également une case à cocher (attribut type) mais désactivée (attribut disabled).

5.8.11. Propriété [Email]

Définition


    [Display(Name = "Email")]
    [DataType(DataType.EmailAddress)]
public string Email{ get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Email)</td>
        <td>@Html.EditorFor(m => m.Email)</td>
        <td>@Html.DisplayFor(m => m.Email)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [email]. Ce type est nouveau dans HTML5. Ce type a été généré à cause de la métadonnée :

[DataType(DataType.EmailAddress)]

Chrome semble avoir traité ce type comme un type [text].

  • la méthode [Html.DisplayFor] a généré la ligne 4 : un lien vers l'adresse mail.

Validation

Une adresse invalide est signalée côté client [1] :

L'absence de saisie ne provoque aucune erreur.

5.8.12. Propriété [Url]

Définition


    [Display(Name = "Url")]
    [DataType(DataType.Url)]
public string Url { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Url)</td>
        <td>@Html.EditorFor(m => m.Url)</td>
        <td>@Html.DisplayFor(m => m.Url)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [url]. Ce type est nouveau dans HTML5. Il a été généré à cause de la métadonnée :

[DataType(DataType.Url)]

Chrome semble traiter ce type comme un type [text].

  • la méthode [Html.DisplayFor] a généré la ligne 4 : un lien vers l'URL.

Validation

Une URL invalide est signalée côté client [1] :

L'absence de saisie ne provoque aucune erreur.

5.8.13. Propriété [Password]

Définition


    [Display(Name = "Password")]
    [DataType(DataType.Password)]
public string Password { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Password)</td>
        <td>@Html.EditorFor(m => m.Password)</td>
        <td>@Html.DisplayFor(m => m.Password)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [password]. Ce type a été généré à cause de la métadonnée :

[DataType(DataType.Password)]
  • la méthode [Html.DisplayFor] a généré la ligne 4.

5.8.14. Propriété [Currency]

Définition


    [Display(Name = "Currency")]
    [DataType(DataType.Currency)]
public double Currency { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.Currency)</td>
        <td>@Html.EditorFor(m => m.Currency)</td>
        <td>@Html.DisplayFor(m => m.Currency)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [text] ;

  • la méthode [Html.DisplayFor] a généré la ligne 4, un nombre à deux décimales avec un symbole monétaire. Ce format a été utilisé à cause de la métadonnée :


[DataType(DataType.Currency)]

Validation

Une valeur invalide [1] ou une absence de valeur [2] est signalée côté serveur :

5.8.15. Propriété [CreditCard]

Définition


    [Display(Name = "CreditCard")]
    [DataType(DataType.CreditCard)]
public string CreditCard { get; set; }

Vue


      <tr>
        <td>@Html.LabelFor(m => m.CreditCard)</td>
        <td>@Html.EditorFor(m => m.CreditCard)</td>
        <td>@Html.DisplayFor(m => m.CreditCard)</td>
</tr>

Visuel

 

HTML généré


      <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>

Commentaires

  • la méthode [Html.EditorFor] a généré la balise <input> de la ligne 3 avec un attribut [type] de type [text]. La méthode [Html.DisplayFor] a généré la ligne 4. On ne voit pas ici ce qu'apporte la métadonnée :

[DataType(DataType.CreditCard)]

Validation

Aucune vérification n'est faite, ni côté client, ni côté serveur.

5.9. Validation d'un formulaire

Nous avons déjà rencontré le problème de la validation du modèle d'une action au paragraphe 4.5 et les paragraphes qui ont suivi. Nous revenons sur cette problématique dans le cadre d'un formulaire :

  • comment signaler les erreurs de saisie à l'utilisateur ;
  • faire les validations aussi bien côté client que côté serveur afin de signaler plus rapidement les erreurs à l'utilisateur.

5.9.1. Validation côté serveur

Considérons le modèle suivant :


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; }

    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Date 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // Email1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // on rend la liste des erreurs
      return résultats;
    }
  }
}

Ce modèle sera affiché par la vue suivante [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>
  • ligne 12 : on référence la feuille de style [Site.css]. Elle contient par défaut des classes utilisées pour mettre en relief les erreurs de saisie du formulaire ;
  • lignes 18-25 : un tableau à trois colonnes :
    • la colonne 1 affiche du texte avec la méthode [Html.LabelFor],
    • la colonne 2 affiche la saisie avec la méthode [Html.EditorFor],
    • la colonne 3 affiche l'éventuelle erreur de saisie avec la méthode [Html.ValidationMessageFor] ;

L'action [Action11Get] sert à afficher le formulaire :


    // Action11-GET
    [HttpGet]
    public ViewResult Action11Get()
    {
      return View("Action11Get", new ViewModel11());
}

L'action [Action11Post] sert à réafficher le formulaire avec les éventuelles erreurs de saisie :


    // Action11-POST
    [HttpPost]
    public ViewResult Action11Post(ViewModel11 modèle)
    {
      return View("Action11Get", modèle);
}
  • ligne 3 : le modèle [ViewModel11] est créé puis initialisé avec les valeurs postées. Il peut alors se produire des erreurs. A chaque propriété erronée P du modèle est associé un message d'erreur. C'est ce message que permet d'obtenir la méthode [Html.ValidationMessageFor] du formulaire.

Voici un exemple d'exécution :

Voici un autre exemple :

 

On notera que les deux dates sont erronées (on est le 11/10/2013) mais que les erreurs ne sont pas signalées. Ces erreurs sont détectées par la méthode [Validate] du modèle :


    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Date 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // Email1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // Regexp1
      try
      {
        DateTime.ParseExact(Regexp1, "dd/MM/yyyy", CultureInfo.CreateSpecificCulture("fr-FR"));
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Regexp1" }));
      }

      // on rend la liste des erreurs
      return résultats;
}

La méthode [Validate] n'est exécutée que lorsque toutes les validations par attributs sont passées. C'est ce que montre un dernier exemple :

 

5.9.2. Validation côté client

Toutes les validations précédentes se sont faites côté serveur. Il faut donc un aller-retour entre le client et le serveur pour que l'utilisateur s'aperçoive de ses erreurs. La validation côté client utilise du code Javascript pour signaler à l'utilisateur ses erreurs le plus tôt possible et en tout cas avant le POST. Celui-ci ne peut avoir lieu que lorsque toutes les erreurs détectées ont été corrigées.

Nous reprenons le modèle [ViewModel11] précédent mais nous l'affichons maintenant avec la vue [Action12Get.cshtml] suivante :


@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>

Note : ligne 13, adaptez la version de jQuery à celle que vous avez avec votre version de Visual Studio (voir ci-après).

La validation côté client nécessite la présence de la ligne 3 ci-dessous dans le fichier [Web.config] de l'application.


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>
  • lignes 1-4 : la section [appSettings] doit être un enfant direct de la section [configuration] du fichier [Web.config] ;

La vue [Action12Get] est identique à la vue [Action11Get] précédente aux lignes 13-15 près. Celles-ci incluent dans la vue, les scripts Javascript nécessaires pour la validation côté client. Ces scripts sont trouvés dans le dossier [Scripts] du projet :

Chaque script a une version normale [.js] et une version minifiée [min.js]. Cette dernière version est plus légère mais illisible. On l'utilise en production. La version lisible est utilisée en développement.

La vue [Action12Get.cshtml] sera affichée par l'action [Action12Get] suivante :


    // Action12-GET
    [HttpGet]
    public ViewResult Action12Get()
    {
      return View("Action12Get", new ViewModel11());
}

Le formulaire saisi sera traité par l'action [Action12Post] suivante :


    // Action12-POST
    [HttpPost]
    public ViewResult Action12Post(ViewModel11 modèle)
    {
      return View("Action12Get", modèle);
}

Voyons ce que ça change sur un exemple :

Dès qu'on tape un caractère en [1], le message en [2] s'affiche parce que la valeur attendue doit avoir au moins quatre caractères. Ainsi la validation est-elle faite à chaque nouveau caractère tapé. Le message d'erreur disparaît au quatrième caractère tapé. Ceci fait, validons le formulaire :

L'URL [3] nous montre que le [POST] n'a pas eu lieu. Mais le clic sur le bouton [Valider] a déclenché toutes les validations côté client et de nouveaux messages d'erreur sont apparus.

Regardons le HTML généré pour la première saisie par exemple :


        <tr>
          <td><label for="Chaine1">Cha&#238;ne d&#39;au moins quatre caract&#232;res</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>
  • ligne 3, on retrouve :
    • le message d'erreur pour le cas où la saisie est manquante [data-val-required],
    • le message d'erreur pour le cas où la saisie est erronée [data-val-regex],
    • l'expression régulière pour la chaîne saisie [data-val-regex-pattern] ;
  • ligne 4, d'autres attributs [data-x] utilisés pour afficher l'éventuel message d'erreur ;

Les attributs [data-x] des balises générées sont exploités par le Javascript que nous avons embarqué dans la vue. Si celui-ci est absent, ces attributs sont tout simplement ignorés et on n'a alors pas de validation côté client. On fonctionne comme dans l'exemple précédent. D'où le terme [unobtrusive] pour cette technique.

5.10. Gestion des liens de navigation et d'action

Nous allons créer les deux vues suivantes pour illustrer la gestion de liens dans une vue :

  • en [1] et [2], on a deux liens de navigation ;
  • en [3], on a un lien d'action qui poste le formulaire. Il ne sert pas à naviguer.

La page 1 est générée par la vue [Action16Get.cshtml] suivante :


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action16Get</title>
  <script>
    function postForm() {
      // on récupère le formulaire du document
      var form = document.forms[0];
      // soumission
      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>
  • ligne 22 : une information initialisée par l'action qui va délivrer la vue ;
  • lignes 23-28 : un formulaire ;
  • ligne 25 : un libellé pour le champ [data] ;
  • ligne 26 : une boîte de saisie nommée [data] ;
  • ligne 27 : un lien de type [submit]. Lors du clic dessus, la fonction Javascript [postForm] est exécutée (attribut href). Celle-ci est définie aux lignes 12-17 ;
  • ligne 14 : on récupère une référence sur le 1er formulaire du document, celui de la ligne 23 ;
  • ligne 16 : ce formulaire est posté. Au final, tout se passe comme si on avait cliqué sur un bouton de type [submit]. Le formulaire est posté au contrôleur et à l'action spécifiés ligne 23 ;
  • ligne 30 : un lien de navigation. Le code HTML généré est le suivant :

    <a href="/Second/Action17Get">Page 2</a>

La méthode utilisée est ActionLink(Texte, Action, Contrôleur).

La page 2 est générée par la vue [Action17Get.cshtml] suivante :


@{
  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>

Les actions qui génèrent ces vues sont les suivantes :


      // Action16-GET
      [HttpGet]
      public ViewResult Action16Get()
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
        return View("Action16Get");
      }

      // Action16-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");
      }

      // Action17-GET
      [HttpGet]
      public ViewResult Action17Get()
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
        return View();
}
  • ligne 6, l'action [Action16Get] génère la vue [Action16Get.cshtml], ç-à-d la page 1 de l'exemple. Cette vue a pour modèle le [ViewBag] (ligne 5) ;
  • ligne 19, l'action [Action17Get] génère la vue [Action17Get.cshtml], ç-à-d la page 2 de l'exemple. Cette vue a pour modèle le [ViewBag] (ligne 21) ;
  • ligne 11 : l'action [Action16Post] traite le POST du formulaire de la vue [Action16Get.cshtml]. Elle reçoit le paramètre nommé [data]. On se rappelle que c'est le nom du champ de saisie dans le formulaire ;
  • ligne 13 : une information est mise dans le [ViewBag] ;
  • ligne 14 : la vue [Action16Get.cshtml] est affichée.

Le lecteur est invité à tester cet exemple.