Skip to content

5. A visualização e seu modelo

5.1. Introduction

Voltemos à arquitetura de uma aplicação ASP.NET MVC:

No capítulo anterior, estudamos como ASP.NET MVC apresentava as informações da consulta [1] a uma ação [2a] na forma de um modelo que podia conter restrições de validação. Esse modelo era fornecido como entrada para a ação e nós o chamamos de modelo da ação. Agora, vamos nos concentrar no resultado mais comum de uma ação: o tipo [ViewResult], que corresponde a uma vista V [3] acompanhada de seu modelo M [2c]. Esse modelo será chamado de modelo da visualização V, que não deve ser confundido com o modelo da ação que acabamos de estudar. Um é a entrada da ação, o outro é a saída.

Vamos começar criando um novo projeto [Exemple-03] [1], ainda dentro da mesma solução, do tipo básico ASP.NET MVC:

Vamos criar um controlador chamado [First] [2]. O código gerado para esse controlador é o seguinte:


using System.Web.Mvc;

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

  }
}
  • linhas 7-10: foi criada uma ação [Index]. O tipo do resultado do método [Index] é o da classe [ActionResult], da qual derivam a maioria dos resultados possíveis de uma ação;
  • linha 9: o método [View] da classe [Controller] (linha 5) retorna um tipo [ViewResult], que deriva de [ActionResult]. Esse método admite várias sobrecargas. Veremos algumas delas. A principal é a seguinte:
 
  • o primeiro parâmetro é o nome da visualização. Se estiver ausente, a visualização utilizada será aquela que possui o mesmo nome da ação que gera o [ViewResult] e que será procurada na pasta [/Views/{controller}], onde {controller} é o nome do controlador;
  • o segundo é o modelo da vista. Se estiver ausente, a vista não possui modelo.

O método [Index] abaixo:


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

solicita que a vista [/Views/First/Index.cshtml] seja exibida. Ele não transmite nenhum modelo a ela. Vamos criar [1] na pasta [/Views/First]:

e, em seguida, vamos criar nela a visualização [Index] [2]:

Especifica-se o nome da visualização como [3]. Ela é criada como [4]. O código gerado é o seguinte:


@{
    Layout = null;
}

<!DOCTYPE html>

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

Trata-se de um HTML clássico, exceto pelas linhas 1 a 3, que são código C#. O programa que gerencia as visualizações é chamado de mecanismo de visualizações. Ele se encarrega de gerenciar tudo o que não é HTML para transformá-lo em HTML. No final das contas, é isso que será enviado ao cliente. O mecanismo de visualização, neste caso, é chamado de [Razor]. Ele permite incluir código C# em uma visualização. O [Razor] interpretará esse código C# e, a partir dele, produzirá código HTML. Aqui estão algumas regras básicas para a inclusão de código C# em uma visualização:

  • a transição do HTML para o C# ocorre ao encontrar o caractere @ (linha 1). Se esse caractere introduzir um bloco de código, colocar-se-ão as chaves (linhas 1 e 3). Se introduzir uma variável cujo valor se deseja recuperar, escrever-se-á simplesmente @variável;
  • a transição do C# para o HTML ocorre ao encontrar o caractere < (linha 5). Às vezes, é necessário forçar essa conversão, principalmente quando se inclui na página texto bruto sem a tag HTML. Nesse caso, utilizar-se-á a tag <text> para inserir o texto: <text>aqui vai o texto bruto</text>.

A linha 2 acima indica que a visualização [Index] não possui uma página mestre.

Vamos modificar a visualização da seguinte maneira:


@{
  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>
  • linha 3: define uma variável C#;
  • linha 15: exibe o valor dessa variável.

Vamos agora acessar a URL [/First/Index]:

 

O código HTML recebido é o seguinte:

<!DOCTYPE html>

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

Trata-se de um documento HTML puro. Todo o código C# desapareceu.

5.2. Usar o [ViewBag] para passar informações para a visualização

Criamos uma nova ação chamada [Action01] associada à visualização [Action01.cshtml]:

A ação [Action01] é a seguinte:


    // Ação01
    public ViewResult Action01()
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View();
}
  • linha 4: utiliza-se a propriedade [ViewBag] do controlador. Trata-se de um objeto dinâmico ao qual é possível adicionar propriedades, conforme feito na linha 4. Esse objeto tem a particularidade de também ser acessível à visualização. Portanto, essa é uma forma de transmitir informações a ele;
  • linha 5: a visualização padrão da ação é solicitada. Trata-se da visualização [/First/Action01.cshtml]. Nenhum modelo é transmitido a ela.

A visualização [Action01.cshtml] é a seguinte:


@{
  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>
  • linha 14: a propriedade [ViewBag.info] é exibida.

Vamos testar. Solicitamos o URL [/First/Action01]:

 

5.3. Usar um modelo fortemente tipado para passar informações para a visualização

O método anterior tem a desvantagem de não permitir a detecção de erros antes da execução. Assim, se a visualização [Action01.cshtml] utilizar o código


<h4>@ViewBag.Info</h4>

, ocorrerá um erro, pois a propriedade [Info] não existe. A propriedade criada pela ação [Action01] é chamada [info]. É possível, então, utilizar um modelo fortemente tipado para evitar esse inconveniente.

Em um dos exemplos analisados anteriormente, a ação era a seguinte:


    // Ação10
    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);
}

A ação [Action10] transmitia ao seu cliente seis informações (e-mail, dia, Info1, Info2, Info3, erros) na forma de uma sequência de caracteres. Vamos transmitir essas informações em um modelo de visualização [ViewModel01]. Como esse modelo herda informações de [ActionModel03], vamos derivá-lo dessa classe.

Começamos copiando o [ActionModel03] do projeto [Exemple-02] para o projeto [Exemple-03] atual:

e alteramos seu espaço de nomes para que seja o mesmo do projeto [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; }
  }
}
  • linha 2: o novo espaço de nomes;

Em seguida, criamos a classe [ViewModel01]:

O código de [ViewModel01] é o seguinte:


namespace Exemple_03.Models
{
  public class ViewModel01 : ActionModel03
  {
    public string Erreurs { get; set; }
  }
}
  • linha 3: a classe herda de [ActionModel03] e, portanto, das propriedades de [Email, Jour, Info1, Info2, Info3];
  • linha 5: adiciona-se a propriedade [Erreurs].

Agora, escrevemos a ação [Action02], que:

  • aceita como entrada o modelo de ação [ActionModel03];
  • e gera como saída o modelo de visualização [ViewModel01].

Seu código é o seguinte:


    // Ação02
    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});
}
  • linha 1: [Action02] recebe o modelo de ação [ActionModel03]. Ela retorna um resultado do tipo [ViewResult];
  • linha 4: os erros relacionados ao modelo de ação [ActionModel03] são agregados na sequência de caracteres [erreurs]. O método [getErrorMessagesFor] foi descrito na página 65 e foi incluído no controlador [First] do novo projeto;
  • linha 5: o método [View] é chamado com um parâmetro. Esse parâmetro é o modelo da visualização. A visualização não é especificada. Portanto, será utilizada a visualização padrão [/Views/First/Action02]. O modelo da vista [ViewModel01] é instanciado e inicializado com as cinco informações do modelo de ação [ActionModel03] e a informação [erreurs] construída na linha 4.

Agora, construímos a visualização [/First/Action02.cshtml]:

Seu código é o seguinte:


@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>
  • A novidade está na linha 1. A notação [@model] define o tipo do modelo da visualização. Esse modelo é, em seguida, referenciado pela notação [@Model] (linhas 16-21);
  • linhas 15 a 22: as informações do modelo são exibidas em uma lista.

Vejamos alguns exemplos de execução da ação [Action02].

Primeiro, sem parâmetros:

 

depois com parâmetros incorretos:

e, por fim, com parâmetros corretos:

Neste exemplo, o modelo da visualização [ViewModel01] herda as informações do modelo de ação [ActionModel03]. Isso ocorre com frequência. Assim, é possível utilizar um único modelo que servirá tanto como modelo de ação quanto como modelo de visualização. Criamos um novo modelo [ActionModel04]:

  

que terá a seguinte estrutura:


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

    // ---------------------- visualização --------------------------------
    public string Erreurs { get; set; }
  }
}
  • linhas 8-28: o modelo da ação com suas restrições de integridade. Esses campos também farão parte da visualização;
  • linha 31: uma propriedade específica do modelo da visualização. Ela foi excluída do modelo da ação pela anotação da linha 5.

Criamos a seguinte nova ação [Action03]:


    // Ação03
    public ViewResult Action03(ActionModel04 modèle)
    {
      modèle.Erreurs = getErrorMessagesFor(ModelState);
      return View(modèle);
}
  • linha 2: [Action03] recebe o modelo de ação do tipo [ActionModel04];
  • linha 5: e define como modelo de visualização esse mesmo modelo;
  • linha 4: complementada com a informação [Erreurs];

Resta-nos apenas criar a visualização [/First/Action03.cshtml]:

  • em [1]: clique com o botão direito do mouse no código de [Action03] e, em seguida, em [Ajouter une vue];
  • em [2]: o nome da visualização proposto por padrão;
  • em [3]: indicar que se está criando uma visualização fortemente tipada;
  • em [4]: selecione na lista suspensa a classe correta, neste caso a classe [ActionModel04];
  • em [5]: a visualização criada.

Atribuímos à visualização [Action03] o mesmo código da visualização [Action02]. Apenas o modelo da visualização (linha 1) e o título da página (linha 11) mudam:


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

Agora, chamemos a ação [Action03] sem parâmetros:

 

Os resultados são os mesmos de antes. É comum usar o mesmo modelo para a ação e para a visualização, pois o modelo da visualização frequentemente retoma informações do modelo da ação. Assim, utiliza-se um modelo mais abrangente, que pode ser usado tanto pela ação quanto pela visualização gerada por ela. É preciso ter o cuidado de excluir da ligação de dados as informações que não pertencem ao modelo da ação. Caso contrário, um usuário bem informado poderia inicializar partes do modelo da visualização sem o nosso conhecimento.

5.4. [Razor] – primeiros passos

Apresentaremos agora alguns elementos das visualizações [Razor], principalmente as instruções foreach e if.

Suponhamos que queiramos apresentar uma lista de pessoas em uma tabela HTML. O modelo da visualização poderia ser o seguinte [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; }
  }
}
  • a vista do modelo é a classe [ViewModel02], linhas 3-10;
  • linha 5: o modelo possui um array de pessoas do tipo [Personne], definido nas linhas 12 a 16;
  • linhas 6 a 10: o construtor do modelo inicializa a propriedade [Personnes] da linha 5 com uma matriz de duas pessoas.

A ação que gera esse modelo na saída será a seguinte: [Action04]:


    // Ação04
    public ViewResult Action04()
    {
      return View(new ViewModel02());
}
  • linha 2: a ação não possui modelo de entrada;
  • linha 4: ela passa para sua visualização padrão, uma instância do modelo [ViewModel02] que acabamos de definir.

A visualização [Action04.cshtml] exibirá o modelo [ViewModel02]:

O código da visualização [Action04.cshtml] é o seguinte:


@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>
  • linha 1: o modelo da visualização;
  • linha 2: a importação do espaço de nomes da classe [Personne] utilizada na linha 24;
  • linhas 16-32: a tabela HTML que exibe as pessoas do modelo;
  • linha 24: o início do código C# é sinalizado pelo caractere @. A instrução [foreach] irá percorrer todas as pessoas do modelo;
  • linhas 26-27: o caractere < encerra o C# e inicia o HTML. Em seguida, novamente, o caractere @ para alternar para o C# e escrever o nome da pessoa. Depois, novamente o caractere <, que alterna para o modo HTML;
  • linha 28: escreve-se a idade da pessoa.

A execução da ação [Action04] produz o seguinte resultado:

 

Outros elementos de uma visualização podem ser preenchidos por uma coleção: listas, com ou sem menu suspenso, botões de opção e caixas de seleção. Consideremos o seguinte exemplo, que exibe uma lista suspensa.

O modelo [ViewModel05] será o seguinte:


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; }
  }
}
  • linha 18: uma classe [Personne2] com três propriedades;
  • linha 3: o modelo [ViewModel05] da visualização;
  • linha 5: a lista de pessoas a serem exibidas na lista suspensa na forma [Prénom Nom];
  • linha 6: o [Id] da pessoa a ser selecionada na lista suspensa;
  • linhas 8-16: o construtor que cria uma tabela com três pessoas (linhas 10-13) e define o [Id] da pessoa que deve aparecer selecionada.

A visualização [Action05.cshtml] exibirá este modelo:

Seu código é o seguinte:


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

As características da lista suspensa HTML foram apresentadas no parágrafo 2.5.2.6. Vamos relembrá-las:

Lista suspensa
<select size="1" name="cmbValeurs">
<option value="1">opção1</option>
<option selected="selected" value="2">opção 2</option>
<option value="3">opção 3</option>
</select>
 
tag HTML
<select size=".." name="..">
<option [selected="selected"] value=”v”>...</option>
...
</select>
exibe em uma lista os textos contidos entre as tags <option>...</option>
atributos
name="cmbValeurs": nome do controle.
size="1": número de itens visíveis na lista. size="1" transforma a lista no equivalente a uma caixa de combinação.
selected="selected": se essa palavra-chave estiver presente para um elemento da lista, este aparecerá selecionado na lista. No nosso exemplo acima, o elemento da lista choix2 aparece como o elemento selecionado da caixa de combinação quando esta é exibida pela primeira vez.
value=”v”: se o elemento for selecionado pelo usuário, é esse valor [v] que é enviado ao servidor. Na ausência desse atributo, é o texto exibido e selecionado que é enviado ao servidor.

O código das linhas 17 a 25 gera as tags <option>, que são inseridas na tag <select> da linha 16.

  • linha 17: percorre-se a lista de pessoas do modelo;
  • linha 20: verifica-se se a pessoa atual é aquela que deve ser selecionada. Se for, prepara-se o texto selected="selected", que deve ser inserido na tag <option>;
  • linha 24: a tag <option> é gravada.

Vamos solicitar a ação [Action05]:

  • em [1,2], as pessoas são exibidas na forma [Prénom Nom];
  • No [1,2], a pessoa selecionada é aquela cujo [Id] é igual a 2.

Vamos agora examinar o código-fonte HTML da página acima:


<!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>
  • linhas 10-12: as três tags <option> geradas pelo código [Razor];
  • linha 11: foi mesmo a pessoa de [Id]=2 que foi selecionada.

Os dois exemplos acima são suficientes. Ao escrever uma visualização [Razor], é preciso resistir à tentação de incluir lógica nela. O código C# nos permitiria fazer isso. No entanto, no modelo MVC, a lógica deve estar na ação ou nas camadas inferiores [Metier, DAO], mas não na visualização. Mesmo respeitando o modelo MVC, pode-se acabar com muita lógica na visualização para calcular valores intermediários. Isso pode significar que o modelo utilizado não é detalhado o suficiente. Ele deve conter os valores finais de que a visualização precisa, para que ela não precise calculá-los por conta própria. Uma boa visualização é aquela em que há um mínimo de lógica e em que a estrutura HTML da visualização permanece clara. Se for inserido código C# em excesso, a estrutura HTML pode se tornar ilegível.

No exemplo acima, a lista suspensa poderia ser usada por um usuário e, nesse caso, gostaríamos de saber qual pessoa ele selecionou. Para isso, precisamos de um formulário.

5.5. Formulário – primeiros passos

O formulário apresentado ao usuário será o seguinte:

 

O modelo da visualização será o modelo [ViewModel05], já utilizado anteriormente. A ação que exibirá essa visualização será a seguinte:


    // Ação06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}
  • linha 2: a ação só pode ser solicitada por um comando HTTP GET;
  • linha 5: a visualização [/First/Action06Get.cshtml] será exibida com uma instância do tipo [ViewModel05] como modelo.

A visualização [/First/Action06Get.cshtml] será a seguinte:


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

As principais novidades são as seguintes:

  • linha 18: para que o navegador possa transmitir as informações inseridas por um usuário, precisamos de um formulário. É a tag <form> nas linhas 18 e 31 que o delimita.

A tag HTML <form> foi apresentada no parágrafo 2.5.2.1. Vamos relembrar suas características:

formulário

<form method="post" action="FormulairePost.aspx">
tag HTML
<form name="..." method="..." action="...">...</form>
atributos
name="frmexemple": nome do formulário — opcional
method="...": método utilizado pelo navegador para enviar ao servidor web os valores coletados no formulário
action="...": URL para onde serão enviados os valores coletados no formulário.
Um formulário da Web é delimitado pelas tags <form>...</form>. O formulário pode ter um nome (name="xx"). Esse é o caso de todos os controles que podem ser encontrados em um formulário. O objetivo de um formulário é coletar informações fornecidas pelo usuário por meio do teclado/mouse e enviá-las para uma URL de servidor web. Qual? Aquela referenciada no atributo action="URL". Se esse atributo estiver ausente, as informações serão enviadas para o URL do documento no qual o formulário está localizado. Um cliente da Web pode utilizar dois métodos diferentes, chamados POST e GET, para enviar dados a um servidor web. O atributo method="méthode", com method igual a GET ou POST, da tag <form> indica ao navegador o método a ser utilizado para enviar as informações coletadas no formulário para o URL especificado pelo atributo action="URL". Quando o atributo method não é especificado, o método GET é utilizado por padrão.
  • linha 18: vemos que os valores do formulário serão enviados para o URL [/First/Action06] por meio de um comando HTTP POST;
  • linha 30: um formulário deve ter um botão do tipo [submit]. É ele que aciona o envio dos valores inseridos para o URL, especificado pelo atributo [action] da tag <form>.

O que exatamente o navegador transmitirá quando o usuário clicar no botão [Valider]? Isso foi explicado no parágrafo 2.5.3.1. Vamos relembrar o que foi dito:


controle HTML


visual


valor(es) retornado(s)

<input type="radio" value="Sim" name="R1"/>Sim
<input type="radio" name="R1" value="não" checked="checked"/>Não
R1=Sim
- o valor do atributo value do botão de opção marcado pelo usuário.
<input type="checkbox" name="C1" value="um"/>1
<input type="checkbox" name="C2" value="dois" checked="checked"/>2
<input type="checkbox" name="C3" value="três"/>3
C1=um
C2=dois
- valores dos atributos value das caixas de seleção marcadas pelo usuário
<input type="text" name="txtSaisie" size="20" value="algumas palavras"/>
txtSaisida=programação+Web
- texto digitado pelo usuário no campo de entrada. Os espaços foram substituídos pelo sinal +
<input type="password" name="txtMdp" size="20" value="unMotDePasse"/>
txtMdp=issoésegredo
- texto digitado pelo usuário no campo de entrada
<textarea rows="2" name="areaSaisie" cols="20">
linha1
linha 2
linha3
</textarea>
areaSaisie=os+fundamentos+da%0D%0A
programação+Web
- texto digitado pelo usuário no campo de entrada. %OD%OA é o marcador de fim de linha. Os espaços foram substituídos pelo sinal +
<select size="1" name="cmbValeurs">
<option value='1'>opção1</option>
<option selected="selected" value='2'>opção2</option>
<option value='3'>opção3</option>
</select>
cmbValores=3
- atributo [value] do elemento selecionado pelo usuário
<select size="3" name="lst1">
<option selected="selected" value='1'>lista1</option>
<option value='2'>lista2</option>
<option value='3'>lista3</option>
<option value='4'>lista4</option>
<option value='5'>lista5</option>
</select>
lst1=3
- atributo [value] do elemento selecionado pelo usuário
<select size="3" name="lst2" multiple="multiple">
<option selected="selected" value='1'>lista1</option>
<option value='2'>lista2</option>
<option selected="selected" value='3'>lista3</option>
<option value='4'>lista4</option>
<option value='5'>lista5</option>
</select>
lst2=1
lst2=3
- atributos [value] dos elementos selecionados pelo usuário
<input type="submit" value="Enviar" name="cmdRenvoyer"/>
 
cmdRenvoyer=Enviar
- nome e atributo value do botão utilizado para enviar os dados do formulário ao servidor
<input type="hidden" name="secret" value="uneValeur"/>
 
secret=umValor
- atributo value do campo oculto

Em nosso formulário, temos duas tags capazes de enviar um valor:


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

e


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

Se o usuário selecionar a pessoa nº 2, os valores serão enviados da seguinte forma:

personneId=2&valider=Valider

Os nomes dos parâmetros correspondem aos atributos [name] das tags relacionadas ao POST. Sem esse atributo, as tags não enviam nenhum valor. Assim, no exemplo acima, poderíamos omitir o atributo name="valider" do botão [submit]. O valor enviado é o atributo [value] do botão. Nesse caso, essa informação não nos interessa. Às vezes, os formulários têm vários botões do tipo [submit]. Nesse caso, é importante saber qual botão foi clicado. Portanto, atribuiremos o atributo [name] aos diferentes botões.

A tag <select> é composta por uma sequência de tags <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>

É o valor do atributo [value] da opção selecionada que é enviado. Na ausência desse atributo, é o texto exibido pela opção, por exemplo, [Pierre Martino], que é enviado.

A string

personneId=2&valider=Valider

será publicada na próxima URL [/First/Action06]:


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

Talvez nos lembremos de que já tínhamos uma ação [Action06]:


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

É possível ter duas ações com o mesmo nome, desde que elas não processem os mesmos comandos HTTP:

  • [Action06] da linha 3 gerencia um POST (linha 2);
  • [Action06] da linha c processa um GET (linha b).

A ação [Action06], que gerencia o POST, receberá a seguinte cadeia de parâmetros:

personneId=2&valider=Valider

Precisamos de um modelo de ação para encapsular esses valores. Será o seguinte modelo [ActionModel06]:


using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
  public class ActionModel06
  {
    [Required(ErrorMessage = "Le paramètre [personneId] est requis")]
    public int PersonneId { get; set; }

    [Required(ErrorMessage = "Le paramètre [valider] est requis")]
    public string Valider { get; set; }
  }
}

A ação [Action06] recebe esse modelo e o transmite tal como está para a visualização [Action06Post] (linha 5 da ação), conforme segue:


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

O modelo é exibido nas linhas 18 e 19.

Vejamos um exemplo:

Em [1], seleciona-se a terceira pessoa de [Id], cujo valor é 3. Em [2], o formulário é enviado. Em [3], os valores recebidos. Em [4,5], percebe-se que o mesmo URL foi chamado, um por um GET [4], e a outra por um POST e um [5]. Isso não aparece no URL.

Na visualização exibida após o POST, talvez seja preferível exibir os nom e prénom da pessoa selecionada, em vez de seu número. É necessário, portanto, atualizar a visualização do POST e seu modelo.

Criamos uma ação [Action07] para lidar com esse caso. Essa ação precisará utilizar a sessão do usuário para armazenar nela a lista de pessoas. Seguiremos o modelo estudado no parágrafo 4.10, que permite incluir os dados de escopo [Application] e [Session] no modelo da ação.

O modelo da sessão será a seguinte classe [SessionModel]:


namespace Exemple_03.Models
{
  public class SessionModel
  {
    public Personne2[] Personnes { get; set; }
  }
}
  • linha 2: a sessão armazenará a lista de pessoas exibidas na lista suspensa;

Precisamos vincular o tipo anterior [SessionModel] a um binder que chamaremos de [SessionModelBinder]. Este será o mesmo descrito na página 82:

  

using System.Web.Mvc;

namespace Exemple_03.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // retornamos os dados do escopo [Session]
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

A ligação entre o modelo [SessionModel] e seus modelos binder e [SessionModelBinder] é feita no [Global.asax]:


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

      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
    }
    // Sessão
    public void Session_Start()
    {
      Session["data"] = new SessionModel();
    }
  }
  • linha 8: a ligação do modelo ao seu binder é feita no [Application_Start];
  • linha 13: uma instância do tipo [SessionModel] é inserida na sessão associada à chave [data].

Feito isso, a ação [Action07] é a seguinte:


    // Ação07-GET
    [HttpGet]
    public ViewResult Action07(SessionModel session)
    {
      ViewModel05 modèleVue = new ViewModel05();
      session.Personnes= modèleVue.Personnes;
      return View("Action07Get", modèleVue);
}
  • linha 3: a ação recupera um tipo [SessionModel], ou seja, o dado de escopo [Session] associado à chave [data];
  • linha 5: cria-se o modelo da visualização;
  • linha 6: insere-se na sessão a tabela de pessoas. Ela será necessária na consulta seguinte, a do POST. O protocolo HTTP é um protocolo sem estado. É necessário utilizar uma sessão para manter a memória entre as consultas. Uma sessão é específica para um usuário e é gerenciada pelo servidor web;
  • linha 7: a visualização [Action07Get.cshtml] é exibida. É a seguinte:

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

Ela é idêntica à visualização [Action06Get.cshtml] já analisada. A principal diferença está na linha 7: a URL, para a qual serão enviadas as entradas do formulário. Essas entradas serão processadas pela ação [Action07] a seguir:


    // Ação07-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);
}
  • linha 3: os valores enviados são encapsulados no modelo de ação [ActionModel06], já utilizado anteriormente (abaixo):

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; }
  }
}
  • linha 3: o primeiro parâmetro é o dado de escopo [Session] associado à chave [data];
  • linha 5: uma consulta LINQ recupera a pessoa com o [Id] que foi publicado;
  • linha 6: constrói-se a sequência de caracteres que deve ser exibida pela visualização [Action07Post] (linha 8);
  • linha 7: para chamar o construtor correto [View], é necessário converter o tipo [string] para [object].

A visão [Action07Post.cshtml] é a seguinte:


@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>
  • linha 1: o modelo é do tipo [string];
  • linha 16: a sequência de caracteres é exibida.

Veja um exemplo de execução:

5.6. Formulário – um exemplo completo

No parágrafo 2.5.2.1, analisamos o seguinte formulário HTML:

1
 

Vamos analisar uma ação [Action08Get] que exibe (GET) esse formulário e uma ação [Action08Post] que processa (POST) os valores inseridos pelo usuário. Um esquema clássico.

O modelo da visualização [1] acima será uma instância da classe [ViewModel08]. Essa classe será, ao mesmo tempo:

  • o modelo da visualização gerada por um GET na ação [Action08Get];
  • o modelo da ação [Action08Post] para uma solicitação POST.

5.6.1. O modelo de escopo [Application]

Vamos supor que os elementos exibidos pelos botões de opção, caixas de seleção e pelas diversas listas sejam dados do escopo [Application]. Um caso frequente. Essas informações provêm de um arquivo de configuração ou de um banco de dados processados na inicialização do aplicativo no método [Application_Start] de [Global.asax]. Esse método é executado da seguinte maneira:


    protected void Application_Start()
    {
....

      // ligadores de modelo
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());

      // dados de escopo [Application]
      Application["data"] = new ApplicationModel();
}
  • linha 7: o tipo [ApplicationModel], que descreveremos em breve, está associado ao ligador de dados [ApplicationModelBinder], que já apresentamos na página 82;
  • linha 10: uma instância do tipo [ApplicationModel] é registrada no dicionário da aplicação, associada à chave [data].

A classe [ApplicationModel] serve para encapsular todos os dados do escopo [Application]. Aqui, ela encapsulará os dados que o formulário deve exibir:


namespace Exemple_03.Models
{
  public class ApplicationModel
  {
    // as coleções a serem exibidas no formulário
    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; }

    // inicialização de campos e coleções
    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"}
      };
    }
    // elemento das coleções
    public class Item
    {
      public string Label { get; set; }
      public string Value { get; set; }
    }

  }
}
  • linhas 45-49: o elemento das diferentes coleções do formulário. [Label] é o texto exibido pelo elemento do formulário, [Value] é o valor enviado por esse elemento quando ele é selecionado;
  • linha 6: a coleção exibida pelo botão de opção;
  • linha 7: a coleção exibida pelas caixas de seleção;
  • linha 8: a coleção exibida pela lista suspensa;
  • linha 9: a coleção exibida pela lista de seleção única;
  • linha 10: a coleção exibida pela lista de seleção múltipla;
  • linhas 13-43: essas coleções são inicializadas pelo construtor sem parâmetros da classe.

As diferentes coleções irão preencher o seguinte formulário:

5.6.2. O modelo da ação [Action08Get]

O formulário anterior será exibido pela seguinte ação [Action08Get]:


    // Ação08-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));
}
  • linha 2: [Action08Get] responderá apenas a um comando [GET];
  • linha 3: ela recebe como parâmetro o modelo do aplicativo que acabamos de descrever;
  • linha 5: ela inicializa uma informação no contêiner dinâmico [ViewBag];
  • linha 6: ela exibe a vista [/First/Formulaire.cshtml] com o modelo [ViewModel08]. Esse modelo será o do formulário apresentado anteriormente. Para isso, passamos ao construtor o modelo da aplicação que define os elementos a serem exibidos.

5.6.3. O modelo da visualização [Formulaire]

A classe [ViewModel08] será o modelo do formulário. Essa classe é a seguinte:


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

namespace Exemple_03.Models
{
  public class ViewModel08
  {
    // os campos de preenchimento
    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; }

    // as coleções a serem exibidas no formulário
    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; }

    // construtores
    public ViewModel08()
    {
    }

    public ViewModel08(ApplicationModel application)
    {
      // inicialização das coleções
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // inicialização dos campos
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}
  • em um formulário, há dois tipos de elementos: aqueles que são exibidos e aqueles que são preenchidos;
  • as linhas 20-24 definem os elementos a serem exibidos. São as diferentes coleções do formulário. Elas se encontram no modelo do aplicativo (linhas 34-38);
  • linhas 10 a 17: definem os campos de preenchimento do formulário;
  • linha 10: [RadioButtonField] recuperará o valor enviado pelas linhas seguintes do formulário:

        <!-- os botões de opção -->
        <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>

Observe-se, nas linhas 5 e 6, que o atributo [name] dos dois botões de opção é o nome da propriedade que será inicializada. Nos dados enviados, encontrará-se uma string no formato:


param1=val1&RadioButtonField=2&param2=val2

se o usuário tiver marcado a opção denominada [non]. Na verdade, é o atributo [value] da opção marcada que é enviado.

  • linha 11: [CheckBoxesField] recuperará os valores enviados pelas seguintes linhas do formulário:

        <!-- as caixas de seleção -->
        <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>

Observe-se, nas linhas 5 e 6, que o atributo [name] das caixas de seleção é o nome da propriedade que será inicializada. Nos dados enviados, encontrar-se-á uma string no formato:


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

se o usuário tiver marcado as caixas de seleção denominadas [2] e [3]. É o atributo [value] das opções marcadas que é enviado. Como vários parâmetros com o mesmo nome podem ser enviados, [CheckBoxesField] é uma matriz de valores e não um valor único. Se nenhuma opção estiver marcada, o parâmetro [CheckBoxesField] estará ausente da string enviada e a propriedade com o mesmo nome no modelo não será inicializada. Isso pode ser um problema, como veremos.

  • linha 12: [TextField] recuperará o valor enviado pelas seguintes linhas do formulário:

          <!-- campo de entrada de texto de linha única -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="quelques mots" size="30" />
            </td>
</tr>

Na linha 5, o atributo [name] do campo de entrada é o nome da propriedade que será inicializada. Nos dados enviados, encontraremos uma string no formato:


param1=val1&TextField=abcdef&param2=val2

se o usuário tiver digitado [abcdef] no campo de entrada.

  • linha 13: [PasswordField] recuperará o valor inserido nas linhas seguintes do formulário:

        <!-- o campo de entrada de senha -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="secret" size="30" />
          </td>
</tr>

Na linha 5, o atributo [name] do campo de entrada é o nome da propriedade que será inicializada. Nos dados enviados, encontrará-se uma sequência no formato:


param1=val1&PasswordField=abcdef&param2=val2

se o usuário tiver inserido [abcdef] no campo de entrada.

  • linha 14: [TextAreaField] recuperará o valor enviado pelas seguintes linhas do formulário:

        <!-- o campo de entrada de texto de várias linhas -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">ligne1
ligne2</textarea>
          </td>
</tr>

Na linha 5, o atributo [name] do campo de entrada é o nome da propriedade que será inicializada. Nos dados enviados, encontrará-se uma sequência no formato:


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

se o usuário tiver digitado [abcdef] seguido de um salto de linha e de [ijk] no campo de entrada.

  • linha 15: [DropDownListField] recuperará o valor enviado pelas linhas seguintes do formulário:

        <!-- a lista suspensa -->
        <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>

Na linha 5, o atributo [name] da tag <select> é o nome da propriedade que será inicializada. Nos dados enviados, encontraremos uma sequência no formato:


param1=val1&DropDownListField=1&param2=val2

se o usuário tiver selecionado a opção [choix1]. É o atributo [value] da opção selecionada que é enviado.

  • linha 16: [SingleChoiceListField] recuperará o valor enviado pelas seguintes linhas do formulário:

        <!-- a lista de seleção única -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
            <select name="SimpleChoiceListField" size="3">
<option value="1" >liste1</option>
<option value="2" >liste2</option>
<option value="3" selected=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>

            </select>
</tr>

Na linha 5, o atributo [name] da tag <select> é o nome da propriedade que será inicializada. É o atributo [size="3"] que faz com que não haja uma lista suspensa. Nos dados enviados, encontraremos uma string no formato:


param1=val1&SimpleChoiceListField=3&param2=val2

se o usuário tiver selecionado a opção [liste3]. É o atributo [value] da opção selecionada que é enviado. O parâmetro [SingleChoiceListField] pode estar ausente da string enviada se nenhum elemento tiver sido selecionado.

  • linha 17: [MultipleChoiceListField] recuperará os valores enviados pelas seguintes linhas do formulário:

        <!-- a lista de múltipla escolha -->
        <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>

Na linha 5, o atributo [name] da tag <select> é o nome da propriedade que será inicializada. É o atributo [size="3"] que faz com que não haja uma lista suspensa, e o atributo [multiple] que permite que o usuário selecione vários elementos mantendo pressionada a tecla [Ctrl]. Nos dados enviados, encontraremos uma string no formato:


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

se o usuário tiver selecionado as opções [liste1] e [liste3]. É o atributo [value] das opções selecionadas que é enviado. Como vários parâmetros com o mesmo nome podem ser enviados, [MultipleChoiceListField] é uma matriz de valores e não um valor simples. Se nenhuma opção estiver marcada, o parâmetro [MultipleChoiceListField] estará ausente da sequência enviada e a propriedade com o mesmo nome no modelo não será inicializada.

Os diversos campos de entrada apresentados anteriormente receberão os valores enviados pelo formulário. Também é possível inicializá-los antes de enviar o formulário. Foi isso que foi feito aqui:


      // inicialização de campos
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
MultipleChoiceListField = new string[] { "1", "3" };

Se esses valores tivessem sido obtidos após o envio do formulário com o código POST, isso significaria que o usuário:

  • linha 2: marcado a opção [non] do botão de opção;
  • linha 3: marcado a opção [2] nas caixas de seleção;
  • linha 4: digitou [quelques mots] no campo de entrada;
  • linha 5: digitou [secret] como senha;
  • linha 6: digitou [ligne1\nligne2] no campo de entrada multilinha;
  • linha 7: selecione a opção [choix2] na lista suspensa;
  • linha 8: selecionei a opção [liste3] da lista de escolha única;
  • linha 9: selecionei as opções [liste1] e [liste3] da lista de seleção múltipla;

Vamos supor que uma ação POST tenha ocorrido e que desejemos reenviar o formulário exatamente como foi preenchido. Isso é o que ocorre, por exemplo, quando se reenvia ao usuário um formulário com erros. O formulário é reenviado exatamente como foi preenchido.

5.6.4. A visualização [Formulaire]

A visualização [/First/Formulaire.cshtml] exibe o formulário:


@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>
        <!-- botões de opção -->
        <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>
  • linha 1: [ViewModel08] é o modelo do formulário;
  • linha 12: a tag <form> do formulário. Este será enviado pelo método [POST] (atributo method) para o URL [/First/Action08Post] (atributo action);
  • linha 33: o botão do tipo [submit], que serve para enviar o formulário;
  • linhas 22-27: exibem os botões de opção:
  
  • linha 22: percorre-se a coleção exibida pelo botão de opção;
  • linha 24: o botão cujo atributo [value] tem o valor da propriedade [RadioButtonField] deve estar marcado. Para isso, ele deve ter o atributo [checked="checked"];
  • linha 25: geração da tag <input type="radio"> com o valor [@item.Value] e o texto [@item.Label];
  • linha 26: a tag <text/> não é uma tag HTML reconhecida. Ela está presente para [Razor]. Ao encontrá-la, [Razor] irá gerar um salto de linha. Isso não afeta o formulário exibido, mas afeta o código HTML gerado. As tags <input type="radio"> ficam, então, em duas linhas diferentes, em vez de ficarem na mesma linha. Isso torna o código mais legível quando, no navegador, solicitamos a visualização do código-fonte da página exibida;

Vamos examinar os demais elementos da visualização:


        <!-- caixas de seleção -->
        <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>
  • linha 6: percorremos a coleção exibida pelas caixas de seleção;
  • linha 8: uma célula que tenha como atributo [value] um dos valores da propriedade [CheckBoxesField] deve estar marcada. Para isso, ela deve ter o atributo [checked="checked"]. Utiliza-se uma expressão LINQ que permite verificar se um valor está contido em uma tabela;
  • linha 25: geração da tag <input type="checkbox"> com o valor [@item.Value] e o texto [@item.Label];

<!-- o campo de entrada de texto de linha única -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="@Model.TextField" size="30" />
            </td>
          </tr>
        <!-- o campo de digitação de senha -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
          </td>
        </tr>
        <!-- o campo de entrada de texto de várias linhas -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
          </td>
        </tr>
  • linhas 5, 12: atribui-se ao atributo [value] da tag o valor do modelo;
  • linha 19: o mesmo, mas com uma sintaxe diferente.

        <!-- a lista suspensa -->
        <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>
  • linha 7: percorre-se a coleção exibida pela lista suspensa;
  • linha 9: deve ser selecionada uma opção cujo atributo [value] tenha o valor da propriedade [DropDownListField]. Para isso, ela deve ter o atributo [selected="selected"];
  • linha 25: geração da tag <option value="valeur">libellé</option> com o valor [@item.Value] e o texto [@item.Label];

        <!-- a lista de seleção única -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
            <select name="SimpleChoiceListField" size="3">
              @{
                foreach (ApplicationModel.Item item in @Model.SimpleChoiceListFieldItems)
                {
                  string strChecked = item.Value == @Model.SimpleChoiceListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>

A explicação é a mesma que para a lista suspensa.


        <!-- a lista de seleção múltipla -->
        <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>
  • linha 7: percorre-se a coleção exibida pela lista;
  • linha 9: deve ser selecionada uma opção cujo atributo [value] tenha um dos valores da propriedade [MultipleChoiceListField]. Para isso, ela deve ter o atributo [selected="selected"]. Utiliza-se uma expressão LINQ que permite verificar se um valor está contido em uma matriz;
  • linha 10: geração da tag libellé/option com o valor [@item.Value] e o rótulo [@item.Label];

5.6.5. Processamento do POST do formulário

Vimos que o formulário seria enviado para a ação [Action08Post]:


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

A ação [Action08Post] é a seguinte:


    // Ação08-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);
}
  • linha 3: o modelo da aplicação é passado como parâmetro, assim como os valores enviados. Estes estão disponíveis em um tipo [FormCollection]. O valor do parâmetro [RadioButtonField] enviado é obtido pela expressão posted[" RadioButtonField"]. O resultado é uma cadeia de caracteres ou o ponteiro null. Se escrevermos posted[" CheckBoxesField"], teremos uma matriz de cadeias de caracteres ou o ponteiro null;
  • por que não escrever:

public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)

Há duas razões:

  • a primeira é que o framework instanciará o modelo [ViewModel08] com o construtor sem parâmetros, o que fará com que as coleções do modelo não sejam inicializadas;
  • a segunda é que queremos controlar o que vai para o modelo. Sabemos que há quatro fontes possíveis para o modelo: os parâmetros de um GET, de um POST, da rota utilizada e os de um arquivo uploadé. Aqui, queremos inicializar o modelo apenas com os valores inseridos.
  • linha 6: instanciamos o modelo usando o construtor correto;
  • linha 7: inicializamos o modelo com os valores enviados. Após essa operação, o modelo corresponde aos dados inseridos pelo usuário;
  • linha 8: exibimos o formulário novamente. O usuário o encontrará exatamente como foi preenchido.

Vejamos um exemplo:

No [2], o resultado do [POST] reflete corretamente o que foi inserido no [1].

5.6.6. Tratamento de anomalias no POST

Mencionamos que, se nenhum valor fosse marcado ou selecionado nos campos do [CheckBoxesField, SimpleChoiceListField, MultipleChoiceListField], os parâmetros correspondentes não fariam parte da string enviada e, portanto, as propriedades com os mesmos nomes no modelo não seriam inicializadas.

Vejamos o seguinte exemplo:

  • no [1], nenhuma caixa de seleção foi marcada;
  • em [2], o [POST] retorna uma caixa marcada.

A explicação é a seguinte:

  • como não há nenhuma caixa marcada, o parâmetro [CheckBoxesField] não faz parte dos valores enviados;
  • a ação [Action08Post] procede da seguinte maneira:

    [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);
}
  • linha 5: o modelo do formulário é instanciado. No entanto, o construtor utilizado atribui a matriz ["2"] à propriedade [CheckBoxesField];
  • linha 6: os valores lançados são registrados no modelo. Como o parâmetro [CheckBoxesField] não faz parte dos valores postados, a propriedade com o mesmo nome não é atribuída. Ela permanece, portanto, com seu valor ["2"], o que faz com que, na exibição, a caixa nº 2 esteja marcada, embora não devesse estar.

Esse problema pode ser resolvido de várias maneiras. Optamos por resolvê-lo no código da ação [Action08Post]:


// Ação08-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);
      // processamento de valores não lançados
      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[] { };
      }
      // exibição do formulário
      return View("Formulaire", modèle);
    }
  • linhas 9-20: verifica-se se determinados parâmetros foram ou não enviados. Caso contrário, eles são inicializados com o valor que corresponde à ausência de entrada feita pelo usuário. O teste não foi realizado para a lista suspensa, que sempre tem um item selecionado, o que não ocorre nas outras listas.

O leitor é convidado a testar esta nova versão.

5.7. Uso de métodos especializados na geração de formulários

5.7.1. O novo formulário

Criamos um novo formulário [Formulaire2.cshtml] que gerará um formulário idêntico ao anterior:

Vamos revisar o código usado para gerar a lista suspensa do formulário:


        <!-- a lista suspensa -->
        <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>

Esse código apresenta duas desvantagens:

  • o mais importante é que se perde de vista a natureza do componente — neste caso, uma lista suspensa — devido à complexidade do código;
  • linha 5: se houver um erro no nome da propriedade do modelo a ser usada como atributo [name], só perceberemos isso na hora da execução.

ASP.NET MVC oferece métodos especializados chamados [HTML Helpers] que, como o próprio nome indica, visam facilitar a geração do HTML, especialmente para formulários. Com essas classes, a lista suspensa anterior é escrita da seguinte forma:


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

A lista suspensa é gerada pelas linhas 4 e 5. O código é significativamente menos complexo. O código HTML gerado para a lista suspensa é o seguinte:


        <!-- a lista suspensa -->
        <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>
  • linha 4: o atributo [name] está correto;
  • linhas 4-6: as opções foram geradas corretamente e a opção correta foi selecionada.

Voltemos ao código que gerou essas linhas HTML:


@Html.DropDownListFor(m => m.DropDownListField, new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
  • o primeiro parâmetro é uma função lambda (esse é o nome dela), em que m representa o modelo da visualização e m.DropDowListField é uma propriedade desse modelo. O gerador de código HTML utilizará o nome dessa propriedade para gerar os atributos [id] e [name] do [select] que será gerado. Se for utilizada uma propriedade inexistente, ocorrerá um erro na compilação e não mais na execução. Trata-se de uma melhoria em relação à solução anterior, na qual os erros de nomenclatura eram detectados apenas na execução;
  • o segundo parâmetro serve para designar a coleção de elementos que alimentará a lista suspensa. A classe [SelectList] permite construir essa coleção:
    • seu primeiro parâmetro é uma coleção qualquer de elementos. Aqui, temos uma coleção do tipo [Item];
    • seu segundo parâmetro é a propriedade dos elementos que fornecerá o valor da tag <option>. Aqui, trata-se da propriedade [Value] da classe [Item];
    • seu terceiro parâmetro é a propriedade dos elementos que fornecerá o texto da tag <option>. Aqui, trata-se da propriedade [Label] da classe [Item];
  • para saber qual opção deve ser selecionada (atributo selected), o framework faz o mesmo que nós: compara o valor da opção com o valor atual da propriedade [DropDownListField].

Vamos ver agora os outros métodos que podemos usar:

Botões de opção

O novo código é o seguinte:


        <!-- botões de opção -->
        <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>

O código HTML gerado é o seguinte:


        <!-- botões de opção -->
        <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>

O método utilizado é [Html.RadioButtonFor]:

@Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)
  • o primeiro parâmetro é a propriedade do modelo que será associada ao botão de opção (atributo [name]);
  • o segundo parâmetro é o valor a ser atribuído ao botão de opção (atributo [value]).

Caixas de seleção

O código é alterado da seguinte forma:


        <!-- as caixas de seleção -->
        <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>

O método utilizado para gerar caixas de seleção é [Html.CheckBoxFor]:

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

O parâmetro é a propriedade booleana do modelo que será associada à caixa de seleção. Se for [Propriété=true], a caixa estará marcada. Se for [Propriété=false], a caixa não estará marcada. Em todos os casos, o atributo [value] tem o valor true. O código HTML gerado é o seguinte:


<input id="Propriété" name="Propriété" type="checkbox" value="true" />
<input name="Propriété" type="hidden" value="false" />
  • linha 1: a caixa de seleção com o atributo [value="true"];
  • linha 2: um campo oculto (type=hidden) com o mesmo nome [Propriété] que a caixa de seleção com o atributo [value="false"]. Por que duas tags [input] com o mesmo nome? Há dois casos:
  • a caixa de seleção da linha 1 está marcada. Nesse caso, a sequência de parâmetros enviada é Propriedade=true&Propriedade=false (linhas 1 e 2). Como a propriedade [Propriété] espera apenas um valor, pode-se supor que o framework atribua o valor [true] a [Propriété]. Bastaria realizar uma operação lógica OU entre os valores recebidos para chegar a esse resultado;
  • a caixa de seleção da linha 1 não está marcada. Portanto, a sequência de parâmetros enviada é Propriedade=false (apenas na linha 2) e, assim, a propriedade [Propriété] recebe o valor [false], o que está correto (a caixa de seleção não foi marcada).

Campo de entrada de linha única

O novo código é o seguinte:


          <!-- o campo de entrada de texto de linha única -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              @Html.TextBoxFor(m => m.TextField, new { size = "30" })
            </td>
</tr>

O código HTML gerado é o seguinte:


          <!-- o campo de entrada de texto de uma linha -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input id="TextField" name="TextField" size="30" type="text" value="quelques mots" />
            </td>
</tr>

O método utilizado é o seguinte:


@Html.TextBoxFor(m => m.TextField, new { size = "30" })
  • o primeiro parâmetro especifica a propriedade do modelo associado ao campo de entrada. O nome da propriedade será utilizado nos atributos [name] e [id] da tag <input> gerada, e seu valor será atribuído ao atributo [value];
  • o segundo parâmetro é uma classe anônima que especifica determinados atributos da tag HTML gerada, neste caso, o atributo [size].

Campo de digitação de senha

O novo código é o seguinte:


        <!-- o campo de digitação de senha -->
        <tr>
          <td>Mot de passe</td>
          <td>
            @Html.PasswordFor(m => m.PasswordField, new { size = "15" })
          </td>
</tr>

O código HTML gerado é o seguinte:


        <!-- o campo de entrada de senha -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input id="PasswordField" name="PasswordField" size="15" type="password" />
          </td>
</tr>

O método utilizado é o seguinte:


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

O funcionamento é semelhante ao do método [Html.TexBoxFor].

Campo de entrada com várias linhas

O novo código é o seguinte:


        <!-- o campo de entrada de texto de várias linhas -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            @Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
          </td>
</tr>

O código HTML gerado é o seguinte:


        <!-- o campo de entrada de texto de várias linhas -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea cols="30" id="TextAreaField" name="TextAreaField" rows="5">
ligne1
ligne2</textarea>
          </td>
</tr>

O método utilizado é o seguinte:


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

O funcionamento é semelhante ao do método [Html.TexBoxFor].

Lista de escolha única

O novo código é o seguinte:


        <!-- a lista de seleção única -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
          @Html.DropDownListFor(m => m.SimpleChoiceListField, new SelectList(@Model.SimpleChoiceListFieldItems, "Value", "Label"), new { size = "3" })
</tr>

e o código HTML gerado é o seguinte:


        <!-- a lista de seleção única -->
        <tr>
          <td>Liste à choix unique</td>
          <td>
          <select id="SimpleChoiceListField" name="SimpleChoiceListField" size="3">
<option value="1">liste1</option>
<option value="2">liste2</option>
<option selected="selected" value="3">liste3</option>
<option value="4">liste4</option>
<option value="5">liste5</option>
</select>
</tr>

Já analisamos o método [Html.DropDownListFor]. A única diferença aqui é o terceiro parâmetro, que serve para especificar um atributo [size] diferente de 1. É essa característica que faz a transição de uma lista suspensa [size=1] para uma lista simples.

A lista de múltiplas opções

O novo código é o seguinte:


        <!-- a lista de seleção múltipla -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
          @Html.ListBoxFor(m => m.MultipleChoiceListField, new SelectList(@Model.MultipleChoiceListFieldItems, "Value", "Label"), new { size = "5" })
</tr>

e o código HTML gerado é o seguinte:


        <!-- a lista de seleção múltipla -->
        <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>

O método


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

funciona como o método [Html.DropDownListFor], com a diferença de que gera uma lista de seleção múltipla. As opções selecionadas são aquelas cujo valor (atributo value) consta na tabela [MultipleChoiceListField].

A tag <form> também pode ser gerada por meio de um método:


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

O código HTML gerado é o seguinte:


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

O método


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

tem como primeiro parâmetro o nome de uma ação e, como segundo parâmetro, o nome de um controlador.

5.7.2. As ações e o modelo

O formulário será gerado pela seguinte ação [Action09Get]:


    // Ação09-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));
}

A visualização gerada na linha 6 é [Formulaire2], associada ao seguinte modelo [ViewModel09]:


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

namespace Exemple_03.Models
{
  public class ViewModel09
  {
    // os campos de preenchimento
    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; }

    // as coleções a serem exibidas no formulário
    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; }

    // construtores
    public ViewModel09()
    {
    }

    public ViewModel09(ApplicationModel application)
    {
      // inicialização de coleções
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // inicialização dos campos
      RadioButtonField = "2";
      CheckBoxField2 = true;
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}

[ViewModel09] difere de [ViewModel08] no gerenciamento das caixas de seleção. Em vez de uma tabela com três caixas de seleção, foram utilizadas três caixas de seleção separadas (linhas 11 a 13).

O formulário será processado pela ação [Action09Post] a seguir:


    // Ação09-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);
      // processamento de valores não enviados
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // exibição do formulário
      return View("Formulaire2", modèle);
}

A ação [Action09Post] é idêntica à ação [Action08Post], exceto em dois pontos:

  • linha 18: a visualização [Formulaire2] é utilizada em vez da visualização [Formulaire];
  • não há mais o gerenciamento das caixas de seleção que foram desmarcadas. Isso agora é gerenciado corretamente pelo método [Html.CheckBoxFor].

5.8. Geração de um formulário a partir dos metadados do modelo

Existem outros métodos, além dos anteriores, para gerar um formulário. Um deles consiste em associar informações a um campo do modelo, o que permitirá que o framework MVC saiba qual tag de entrada deve gerar. Essas informações são chamadas de metadados.

Consideremos o seguinte modelo de visualização [ViewModel10]:


using System;
using System.ComponentModel.DataAnnotations;
using System.Drawing;

namespace Exemple_03.Models
{
  public class ViewModel10
  {
    [Display(Name="Text")]
    [DataType(DataType.Text)]
    public string Text { get; set; }

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Os metadados são formados pelas tags [Display, DataType, UIHint].

Esse modelo de visualização será construído pela seguinte ação [Action10Get]:


    // Ação10-GET
    [HttpGet]
    public ViewResult Action10Get()
    {
      return View(new ViewModel10());
}

Na linha 5 acima, solicita-se à visualização padrão da ação [/First/Action10Get.cshtml ] que exiba o modelo de visualização do tipo [ViewModel10]. Essa visualização é a seguinte:


@model Exemple_03.Models.ViewModel10

@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action10Get</title>
</head>
<body>
  <h3>Formulaire ASP.NET MVC - 2</h3>
  @using (Html.BeginForm("Action10Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>LabelFor</th>
          <th>EditorFor</th>
          <th>DisplayFor</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Text)</td>
          <td>@Html.EditorFor(m => m.Text)</td>
          <td>@Html.DisplayFor(m => m.Text)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.MultiLineText)</td>
          <td>@Html.EditorFor(m => m.MultiLineText)</td>
          <td>@Html.DisplayFor(m => m.MultiLineText)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Number)</td>
          <td>@Html.EditorFor(m => m.Number)</td>
          <td>@Html.DisplayFor(m => m.Number)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Decimal)</td>
          <td>@Html.EditorFor(m => m.Decimal)</td>
          <td>@Html.DisplayFor(m => m.Decimal)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Tel)</td>
          <td>@Html.EditorFor(m => m.Tel)</td>
          <td>@Html.DisplayFor(m => m.Tel)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Date)</td>
          <td>@Html.EditorFor(m => m.Date)</td>
          <td>@Html.DisplayFor(m => m.Date)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Time)</td>
          <td>@Html.EditorFor(m => m.Time)</td>
          <td>@Html.DisplayFor(m => m.Time)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.HiddenInput)</td>
          <td>@Html.EditorFor(m => m.HiddenInput)</td>
          <td>@Html.DisplayFor(m => m.HiddenInput)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Boolean)</td>
          <td>@Html.EditorFor(m => m.Boolean)</td>
          <td>@Html.DisplayFor(m => m.Boolean)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Email)</td>
          <td>@Html.EditorFor(m => m.Email)</td>
          <td>@Html.DisplayFor(m => m.Email)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Url)</td>
          <td>@Html.EditorFor(m => m.Url)</td>
          <td>@Html.DisplayFor(m => m.Url)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Password)</td>
          <td>@Html.EditorFor(m => m.Password)</td>
          <td>@Html.DisplayFor(m => m.Password)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Currency)</td>
          <td>@Html.EditorFor(m => m.Currency)</td>
          <td>@Html.DisplayFor(m => m.Currency)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.CreditCard)</td>
          <td>@Html.EditorFor(m => m.CreditCard)</td>
          <td>@Html.DisplayFor(m => m.CreditCard)</td>
        </tr>
      </tbody>
    </table>
    <input type="submit" value="Valider" />
  }
</body>
</html>

Para cada uma das propriedades do modelo, utilizamos o método:

  • Html.LabelFor para exibir o valor do metadado [DisplayName] da propriedade;
  • Html.EditorFor para gerar a tag HTML de inserção do valor da propriedade. Esse método utilizará os metadados [DataType] e [UIHint] da propriedade;
  • Html.DisplayFor para exibir o valor da propriedade de acordo com o formato indicado pelo metadado [DataType].

Veja um exemplo de execução no navegador Chrome:

Image

Dependendo do navegador utilizado, podem ser exibidas páginas diferentes. De fato, a visualização gerada utiliza as novas tags introduzidas pela versão 5 do HTML, denominada HTML5. Nem todos os navegadores suportam essa versão ainda. No exemplo acima, o navegador Chrome a suporta parcialmente.

5.8.1. O [POST] do formulário

O [POST] do formulário é processado pela ação [Action10Post] a seguir:


    // Ação10-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);
}
  • linha 3: a ação [Action10Post] tem como modelo de entrada o formulário enviado;
  • linha 5: recuperam-se os erros de validação desse formulário;
  • linha 6: prepara-se a resposta em texto para o cliente;
  • linha 7: ela é enviada.

Vamos agora examinar as propriedades do modelo [ViewModel10], uma a uma, e ver como os metadados associados influenciam o HTML gerado e a validação dos campos de preenchimento.

5.8.2. Propriedade [Text]

Definição


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

Visão


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

Visual

 

HTML gerado


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

Comentários

  • O método [Html.LabelFor] gerou a tag <label> da linha 2. O valor do atributo [for] é o nome da propriedade de parâmetro do método [Html.LabelFor]

public string Text { get; set; }

O texto exibido entre o início e o fim da tag é o texto dos metadados


[Display(Name="Text")]

O método [Html.LabelFor] sempre procede dessa forma. Não voltaremos a abordar isso para as outras propriedades.

  • O método [Html.EditorFor] gerou a tag <input> da linha 3. Observe-se que ela possui um atributo [class] que associa a classe CSS [text-box single-line] à tag. Os atributos [id] e [name] têm como valor o nome [Text] da propriedade “parâmetro” do método [Html.EditorFor]. O atributo [type] recebeu o valor [text] devido ao metadado

[DataType(DataType.Text)]
  • o método [Html.DisplayFor] gerou o texto da linha 4. Esse é o valor da propriedade de parâmetro do método [Html.DisplayFor ]. Esse método é influenciado pelos metadados

[DataType(DataType.Text)]

, o que faz com que o valor seja exibido como texto sem formatação.

5.8.3. Propriedade [MultiLineText]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <textarea> da linha 3. Observe-se que ela possui um atributo [class] que associa a classe CSS [text-box multi-line] à tag. Os atributos [id] e [name] têm como valor o nome [MultiLineText] da propriedade “parâmetro” do método [Html.EditorFor]. É sempre assim. Não mencionaremos mais isso. A tag gerada é <textarea> devido ao metadado

[DataType(DataType.MultilineText)]

, que especificava que a propriedade era um texto de várias linhas.

  • O método [Html.DisplayFor] gerou o texto das linhas 4 e 5. Esse é o valor da propriedade de parâmetro do método [Html.DisplayFor ].

5.8.4. Propriedade [Number]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [number]. Aparentemente, simplesmente porque a propriedade tem o tipo [int]. Os atributos [data-val], [data-val-number] e [data-val-required] são atributos não reconhecidos pelo HTML5. Eles são utilizados por um framework JavaScript de validação de dados no lado do cliente;
  • o método [Html.DisplayFor] gerou o texto da linha 4, o valor da propriedade.

Validação

Os atributos [data-x] influenciam a validação de dados no lado do cliente. Aqui estão dois exemplos:

Digita-se um número incorreto e valida-se:

 

No exemplo acima, a validação ocorreu no lado do cliente. O formulário não será enviado até que o erro seja corrigido.

Outro exemplo: não se insere nada:

No [1] acima, o [Action10Post] sinaliza um erro. Talvez nos lembremos de que já havíamos obtido esse comportamento ao usar o atributo [Required] na propriedade a ser controlada (ver página 69), neste caso, a propriedade [Number]. Aqui, não foi necessário fazer isso.

5.8.5. Propriedade [Decimal]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [text]. Os demais atributos são idênticos aos gerados para a propriedade [Number] anterior. Os metadados:

[UIHint("Decimal")]

faz com que o valor da propriedade seja exibido com duas casas decimais nos dois métodos [Html.EditorFor] e [Html.DisplayFor]

Validação

Nenhum erro de validação é sinalizado no lado do cliente, ao contrário do caso anterior. O erro é sinalizado apenas pela ação [Action10Post]. Mais uma vez, o número decimal é obrigatório, sem que seja necessário definir o atributo [Required].

5.8.6. Propriedade [Tel]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [tel]. Esse valor foi gerado devido aos metadados:

[DataType(DataType.PhoneNumber)]

O tipo [tel] para uma tag <input> é uma novidade em relação ao HTML5. O navegador Chrome a tratou como uma tag <input> com o tipo [text].

Validação

Não há erros de validação relatados nem no lado do cliente nem no lado do servidor. É possível inserir qualquer coisa.

5.8.7. Propriedade [Date]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [date]. Esse valor foi gerado devido aos metadados:

[DataType(DataType.Date)]

O tipo [date] para uma tag <input> é uma novidade em relação ao HTML5. O navegador Chrome o reconhece e permite inserir a data por meio de um calendário. Além disso, a data inserida é apresentada no formato [jj/mm/aaaa], ou seja, o Chrome adapta o formato da data ao formato [locale] do navegador.

  • O método [Html.DisplayFor] também escreveu a data no formato [jj/mm/aaaa], sempre devido à presença do metadado [Date].

Validação

Uma data inválida é sinalizada no lado do cliente ([1]), impedindo o envio do POST do formulário para o servidor.

A ausência de data não é sinalizada no lado do cliente, mas é no lado do servidor: [2].

5.8.8. Propriedade [Time]

Definição


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

Visão


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

Visual

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [time]. Esse valor foi gerado devido aos metadados:

[DataType(DataType.Time)]

O tipo [time] para uma tag <input> é uma novidade em relação ao HTML5. O navegador Chrome o reconhece e permite inserir uma hora no formato [hh:mm];

  • o método [Html.DisplayFor] também escreveu a hora no formato [hh:mm], sempre devido à presença do metadado [Time].

Validação

Tecnicamente, não é possível inserir uma hora inválida. A ausência de hora é sinalizada no servidor:

 

5.8.9. Propriedade [HiddenInput]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [hidden], ou seja, um campo oculto (mas, mesmo assim, enviado). Esse valor foi gerado devido aos metadados:

[UIHint("HiddenInput")]
  • o método [Html.DisplayFor] escreveu o valor do campo oculto.

5.8.10. Propriedade [Boolean]

Definição


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

Visualização


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

Imagem

 

HTML gerado


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

Comentários

  • o método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [checkbox], ou seja, uma caixa de seleção. Esse valor foi gerado porque a propriedade é booleana:

public bool Boolean { get; set; }
  • o método [Html.DisplayFor] gerou a linha 4, também uma caixa de seleção (atributo type), mas desativada (atributo disabled).

5.8.11. Propriedade [Email]

Definição


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

Exibição


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [email]. Esse tipo é novo no HTML5. Esse tipo foi gerado devido aos metadados:

[DataType(DataType.EmailAddress)]

O Chrome parece ter tratado esse tipo como um tipo [text].

  • O método [Html.DisplayFor] gerou a linha 4: um link para o endereço de e-mail.

Validação

Um endereço inválido é sinalizado no lado do cliente [1]:

A ausência de entrada não causa nenhum erro.

5.8.12. Propriedade [Url]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [url]. Esse tipo é novo no HTML5. Ele foi gerado devido aos metadados:

[DataType(DataType.Url)]

O Chrome parece tratar esse tipo como um tipo [text].

  • O método [Html.DisplayFor] gerou a linha 4: um link para o URL.

Validação

Um URL inválido é sinalizado no lado do cliente [1]:

A ausência de entrada não causa nenhum erro.

5.8.13. Propriedade [Password]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [password]. Esse tipo foi gerado devido aos metadados:

[DataType(DataType.Password)]
  • o método [Html.DisplayFor] gerou a linha 4.

5.8.14. Propriedade [Currency]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • o método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [text];
  • o método [Html.DisplayFor] gerou a linha 4, um número com duas casas decimais e um símbolo monetário. Esse formato foi utilizado devido aos metadados:

[DataType(DataType.Currency)]

Validação

Um valor inválido [1] ou a ausência de valor [2] é sinalizada no servidor:

5.8.15. Propriedade [CreditCard]

Definição


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

Visão


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> da linha 3 com um atributo [type] do tipo [text]. O método [Html.DisplayFor] gerou a linha 4. Não fica claro aqui o que os metadados trazem:

[DataType(DataType.CreditCard)]

Validação

Não é realizada nenhuma verificação, nem no lado do cliente, nem no lado do servidor.

5.9. Validação de um formulário

Já abordamos o problema da validação do modelo de uma ação no parágrafo 4.5 e nos parágrafos seguintes. Voltamos a tratar dessa questão no contexto de um formulário:

  • como sinalizar os erros de preenchimento ao usuário;
  • realizar as validações tanto no lado do cliente quanto no lado do servidor, a fim de sinalizar os erros mais rapidamente ao usuário.

5.9.1. Validação no lado do servidor

Consideremos o seguinte modelo:


using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Net.Mail;

namespace Exemple_03.Models
{
  public class ViewModel11 : IValidatableObject
  {

    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Chaîne d'au moins quatre caractères")]
    [RegularExpression(@"^.{4,}$", ErrorMessage = "Information incorrecte")]
    public string Chaine1 { get; set; }

    [Display(Name = "Chaîne d'au plus quatre caractères")]
    [Required(ErrorMessage = "Information requise")]
    [RegularExpression(@"^.{1,4}$", ErrorMessage = "Information incorrecte")]
    public string Chaine2 { get; set; }

    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Chaîne de quatre caractères exactement")]
    [RegularExpression(@"^.{4,4}$", ErrorMessage = "Information incorrecte")]
    public string Chaine3 { get; set; }

    [Required(ErrorMessage = "Information requise")]
    [Display(Name = "Nombre entier")]
    public int Entier1 { get; set; }

    [Display(Name = "Nombre entier dans l'intervalle [1,100]")]
    [Required(ErrorMessage = "Information requise")]
    [Range(1, 100, ErrorMessage = "Information incorrecte")]
    public int Entier2 { get; set; }

    [Display(Name = "Nombre réel")]
    [Required(ErrorMessage = "Information requise")]
    public double Reel1 { get; set; }

    [Display(Name = "Nombre réel dans l'intervalle [10.2, 11.3]")]
    [Required(ErrorMessage = "Information requise")]
    [Range(10.2, 11.3, ErrorMessage = "Information incorrecte")]
    public double Reel2 { get; set; }

    [Display(Name = "Adresse mail")]
    [Required(ErrorMessage = "Information requise")]
    public string Email1 { get; set; }

    [Display(Name = "Date sous la forme dd/jj/aaaa")]
    [RegularExpression(@"\s*\d{2}/\d{2}/\d{4}\s*", ErrorMessage = "Information incorrecte")]
    [Required(ErrorMessage = "Information requise")]
    public string Regexp1 { get; set; }

    [Display(Name = "Date postérieure à celle d'aujourd'hui")]
    [Required(ErrorMessage = "Information requise")]
    [DataType(DataType.Date)]
    public DateTime Date1 { get; set; }

    // validação
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Data 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // E-mail 1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // retorna a lista de erros
      return résultats;
    }
  }
}

Esse modelo será exibido pela seguinte visualização [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>
  • linha 12: é feita a referência à folha de estilo [Site.css]. Ela contém, por padrão, classes utilizadas para destacar os erros de preenchimento do formulário;
  • linhas 18-25: uma tabela com três colunas:
    • a coluna 1 exibe texto com o método [Html.LabelFor],
    • a coluna 2 exibe o dado inserido com o método [Html.EditorFor],
    • a coluna 3 exibe o eventual erro de preenchimento com o método [Html.ValidationMessageFor];

A ação [Action11Get] serve para exibir o formulário:


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

A ação [Action11Post] serve para exibir novamente o formulário com os eventuais erros de preenchimento:


    // Ação11-POST
    [HttpPost]
    public ViewResult Action11Post(ViewModel11 modèle)
    {
      return View("Action11Get", modèle);
}
  • linha 3: o modelo [ViewModel11] é criado e, em seguida, inicializado com os valores enviados. Nesse momento, podem ocorrer erros. A cada propriedade P incorreta do modelo está associada uma mensagem de erro. É essa mensagem que o método [Html.ValidationMessageFor] do formulário permite obter.

Veja um exemplo de execução:

Veja outro exemplo:

 

Observe que ambas as datas estão incorretas (hoje é 11/10/2013), mas os erros não são sinalizados. Esses erros são detectados pelo método [Validate] do modelo:


    // validação
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Data 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // E-mail 1
      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" }));
      }

      // exibe a lista de erros
      return résultats;
}

O método [Validate] só é executado quando todas as validações por atributos forem aprovadas. É o que mostra um último exemplo:

 

5.9.2. Validação no lado do cliente

Todas as validações anteriores foram realizadas no lado do servidor. Portanto, é necessária uma troca de dados entre o cliente e o servidor para que o usuário perceba seus erros. A validação do lado do cliente utiliza código JavaScript para sinalizar ao usuário seus erros o mais cedo possível e, em qualquer caso, antes do POST. Este só pode ocorrer quando todos os erros detectados tiverem sido corrigidos.

Retomamos o modelo [ViewModel11] anterior, mas agora o exibimos com a seguinte visualização [Action12Get.cshtml]:


@model Exemple_03.Models.ViewModel11
@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action12Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
  <script type="text/javascript" src="~/Scripts/jquery-1.8.2.min.js" ></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.min.js" ></script>
  <script type="text/javascript" src="~/Scripts/jquery.validate.unobtrusive.min.js" ></script>
</head>
<body>
  <h3>Formulaire ASP.NET MVC - Validation 1</h3>
  @using (Html.BeginForm("Action11Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>Type attendu</th>
          <th>Valeur saisie</th>
          <th>Message d'erreur</th>
        </tr>
      </thead>
      <tbody>
...
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>

Observação: linha 13, adapte a versão do jQuery à que você possui com a sua versão do Visual Studio (veja a seguir).

A validação do lado do cliente requer a presença da linha 3 abaixo no arquivo [Web.config] do aplicativo.


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>
  • linhas 1-4: a seção [appSettings] deve ser um elemento filho direto da seção [configuration] do arquivo [Web.config];

A visualização [Action12Get] é idêntica à visualização [Action11Get] anterior, com exceção das linhas 13 a 15. Essas linhas incluem na visualização os scripts JavaScript necessários para a validação do lado do cliente. Esses scripts estão localizados na pasta [Scripts] do projeto:

Cada script possui uma versão normal ([.js]) e uma versão minificada ([min.js]). Esta última versão é mais leve, mas ilegível. Ela é utilizada em produção. A versão legível é utilizada em desenvolvimento.

A visualização [Action12Get.cshtml] será exibida pela ação [Action12Get] a seguir:


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

O formulário preenchido será processado pela ação [Action12Post] a seguir:


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

Vamos ver como isso funciona com um exemplo:

Assim que digitamos um caractere em [1], a mensagem em [2] é exibida porque o valor esperado deve ter pelo menos quatro caracteres. Assim, a validação é feita a cada novo caractere digitado. A mensagem de erro desaparece ao digitar o quarto caractere. Feito isso, vamos validar o formulário:

O URL [3] nos mostra que o [POST] não ocorreu. Mas ao clicar no botão [Valider], todas as validações do lado do cliente foram acionadas e novas mensagens de erro apareceram.

Vejamos, por exemplo, o código HTML gerado para a primeira entrada:


        <tr>
          <td><label for="Chaine1">Cha&#238;de pelo menos quatro caracteres</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-regex="Information incorrecte" data-val-regex-pattern="^.{4,}$" data-val-required="Information requise" id="Chaine1" name="Chaine1" type="text" value="" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Chaine1" data-valmsg-replace="true"></span></td>
</tr>
  • na linha 3, encontramos:
    • a mensagem de erro para o caso em que a entrada está faltando [data-val-required],
    • a mensagem de erro para o caso em que a entrada está incorreta [data-val-regex],
    • a expressão regular para a sequência digitada [data-val-regex-pattern];
  • linha 4, outros atributos [data-x] utilizados para exibir a eventual mensagem de erro;

Os atributos [data-x] das tags geradas são utilizados pelo JavaScript que incorporamos na visualização. Se ele estiver ausente, esses atributos são simplesmente ignorados e, nesse caso, não há validação do lado do cliente. O funcionamento é semelhante ao do exemplo anterior. Daí a denominação [unobtrusive] para essa técnica.

Vamos criar as duas visualizações a seguir para ilustrar o gerenciamento de links em uma visualização:

  • em [1] e [2], temos dois links de navegação;
  • na [3], há um link de ação que envia o formulário. Ele não serve para navegar.

A página 1 é gerada pela seguinte vista [Action16Get.cshtml]:


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action16Get</title>
  <script>
    function postForm() {
      // recupera-se o formulário do documento
      var form = document.forms[0];
      // envio
      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>
  • linha 22: uma informação inicializada pela ação que irá gerar a visualização;
  • linhas 23-28: um formulário;
  • linha 25: um rótulo para o campo [data];
  • linha 26: um campo de entrada denominado [data];
  • linha 27: um link do tipo [submit]. Ao clicar nele, a função JavaScript [postForm] é executada (atributo href). Essa função está definida nas linhas 12 a 17;
  • linha 14: obtém-se uma referência ao primeiro formulário do documento, o da linha 23;
  • linha 16: esse formulário é enviado. No final, tudo ocorre como se tivéssemos clicado em um botão do tipo [submit]. O formulário é enviado ao controlador e à ação especificados na linha 23;
  • linha 30: um link de navegação. O código HTML gerado é o seguinte:

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

O método utilizado é ActionLink(Texto, Ação, Controlador).

A página 2 é gerada pela seguinte vista [Action17Get.cshtml]:


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action17Get</title>
</head>
<body>
  <h3>Navigation - Page 2</h3>
  <h4>@ViewBag.info</h4>
  <p>
    @Html.ActionLink("Page 1", "Action16Get", "Second")
  </p>
</body>
</html>

As ações que geram essas visualizações são as seguintes:


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

      // Ação16-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");
      }

      // Ação17-GET
      [HttpGet]
      public ViewResult Action17Get()
      {
        ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
        return View();
}
  • na linha 6, a ação [Action16Get] gera a visualização [Action16Get.cshtml], ou seja, a página 1 do exemplo. Essa visualização tem como modelo a [ViewBag] (linha 5);
  • linha 19, a ação [Action17Get] gera a visualização [Action17Get.cshtml], ou seja, a página 2 do exemplo. Essa visualização tem como modelo a [ViewBag] (linha 21);
  • linha 11: a ação [Action16Post] processa o POST do formulário da visualização [Action16Get.cshtml]. Ela recebe o parâmetro denominado [data]. Lembramos que esse é o nome do campo de entrada no formulário;
  • linha 13: uma informação é inserida no [ViewBag];
  • linha 14: a visualização [Action16Get.cshtml] é exibida.

O leitor é convidado a testar este exemplo.