Skip to content

5. A vista e o seu modelo

5.1. Introdução

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

No capítulo anterior, analisámos como o ASP.NET MVC apresenta as informações do pedido [1] a uma ação [2a] sob a forma de um modelo que pode conter restrições de validação. Este modelo foi passado como entrada para a ação, e referimo-nos a ele como o modelo de ação. Vamos agora concentrar-nos no resultado mais comum de uma ação, o tipo ViewResult [ ], que corresponde a uma vista V [3] acompanhada pelo seu modelo M [2c]. Este modelo será chamado de modelo de vista V, não confundir com o modelo de ação que acabámos de estudar. Um é a entrada para a ação, o outro é a saída.

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

Vamos criar um controlador chamado [First] [2]. O código gerado para este 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 de retorno do método [Index] é o da classe [ActionResult], da qual derivam a maioria dos resultados de ação possíveis;
  • linha 9: o método [View] da classe [Controller] (linha 5) retorna um tipo [ViewResult], que deriva de [ActionResult]. Este método suporta várias sobrecargas. Vamos analisar algumas delas. A principal é a seguinte:

Image

  • o primeiro parâmetro é o nome da vista. Se for omitido, a vista utilizada é aquela com o mesmo nome da ação que produz o [ViewResult], e será procurada na pasta [/Views/{controller}], onde {controller} é o nome do controlador;
  • o segundo é o modelo da vista. Se for omitido, a vista não tem modelo.

O método [Index] abaixo:


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

solicita que a vista [ /Views/First/Index.cshtml ] seja apresentada. Não lhe passa nenhum modelo. Vamos criar [1] a pasta [/Views/First]:

e, em seguida, criemos a vista [Index] [2] dentro dela:

Especificamos o nome da vista em [3]. Ela é criada em [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>

Este é HTML padrão, exceto pelas linhas 1–3, que são código C#. O programa que gere as vistas é chamado de motor de vistas. Ele trata de tudo o que não é HTML e converte-o em HTML. Em última análise, é isto que será enviado para o navegador do cliente. O motor de vistas utilizado aqui é o [Razor]. Permite-lhe incluir código C# dentro de uma vista. O [Razor] interpreta este código C# e gera HTML a partir dele. Aqui estão algumas regras básicas para incluir código C# numa vista:

  • A transição de HTML para C# ocorre quando o caractere @ é encontrado (linha 1). Se este caractere introduzir um bloco de código, são utilizadas chaves (linhas 1 e 3). Se introduzir uma variável cujo valor pretenda recuperar, basta escrever @variable;
  • A transição de C# para HTML ocorre quando o caractere < é encontrado (linha 5). Por vezes, poderá ser necessário forçar esta transição, especialmente ao incluir texto simples sem tags HTML na página. Nesse caso, utilize a tag <text> para inserir o texto: <text>texto simples aqui</text>.

A linha 2 acima indica que a vista [Index] não tem página mestre.

Vamos modificar a vista da seguinte forma:


@{
  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 desta variável.

Agora vamos solicitar a URL [/First/Index]:

Image

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>

Este é um documento HTML puro. Todo o código C# foi removido.

5.2. Utilizar [ViewBag] para passar informações para a vista

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

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


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

  • Linha 4: Utilizamos a propriedade [ViewBag] do controlador. Trata-se de um objeto dinâmico ao qual podem ser adicionadas propriedades, conforme mostrado na linha 4. Uma característica fundamental deste objeto é que ele também é acessível à vista. É, portanto, uma forma de passar informações para a vista;
  • linha 5: é solicitada a vista padrão para a ação. Esta é a vista [/First/Action01.cshtml]. Não é passado nenhum modelo para ela.

A vista [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] é apresentada.

Vamos testá-la. Solicitamos a URL [/First/Action01]:

Image

5.3. Utilizar um modelo fortemente tipado para passar informações para a vista

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


<h4>@ViewBag.Info</h4>

Ocorrerá um erro porque a propriedade [Info] não existe. A propriedade criada pela ação [Action01] chama-se [info]. Podemos, portanto, utilizar um modelo fortemente tipado para evitar este problema.

Num dos exemplos discutidos anteriormente, a ação era a seguinte:


    // Action10
    public ContentResult Action10(ActionModel03 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3}, info3={4}, erreurs={5}",
        modèle.Email, modèle.Jour, modèle.Info1, modèle.Info2, modèle.Info3, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}

A ação [Action10] estava a passar seis elementos de informação (Email, Day, Info1, Info2, Info3, errors) ao seu cliente como uma string. Vamos passar esta informação para um modelo de visualização [ViewModel01]. Uma vez que este modelo utiliza informação do [ActionModel03], vamos derivá-lo dessa classe.

Começamos por copiar [ActionModel03] do projeto [Example-02] para o projeto atual [Example-03]:

e alteramos o seu namespace para corresponder ao do projeto [Example-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 namespace;

Em seguida, criamos a classe [ViewModel01]:

O código para [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 [Email, Day, Info1, Info2, Info3];
  • linha 5: adicionamos a propriedade [Errors] a ela.

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

  • recebe o modelo de ação [ActionModel03] como entrada;
  • e devolve o modelo de visualização [ViewModel01].

O seu código é o seguinte:


    // Action02
    public ViewResult Action02(ActionModel03 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      return View(new ViewModel01(){Email=modèle.Email, Jour=modèle.Jour, Info1=modèle.Info1, Info2=modèle.Info2, Info3=modèle.Info3, Erreurs=erreurs});
}

  • linha 1: [Action02] recebe o modelo de ação [ActionModel03]. Retorna um resultado do tipo [ViewResult];
  • linha 4: os erros relacionados com o modelo de ação [ActionModel03] são agregados na string [erros]. O método [getErrorMessagesFor] foi descrito na página 60 e foi incluído no controlador [First] do novo projeto;
  • linha 5: o método [View] é chamado com um parâmetro. Este é o modelo de visualização. A visualização em si não é especificada. Por conseguinte, será utilizada a visualização predefinida [/Views/First/Action02]. O modelo de visualização [ViewModel01] é instanciado e inicializado com as cinco informações do modelo de ação [ActionModel03] e as informações [errors] construídas na linha 4.

Vamos agora criar a vista [/First/Action02.cshtml]:

O 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 nova funcionalidade encontra-se na linha 1. A notação [@model] define o tipo do modelo de visualização. Este modelo é então referenciado pela notação [@Model] (linhas 16–21);
  • Linhas 15–22: As informações do modelo são apresentadas numa lista.

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

Primeiro, sem parâmetros:

Image

depois com parâmetros incorretos:

Image

depois com parâmetros corretos:

Image

Neste exemplo, o modelo de visualização [ViewModel01] utiliza as informações do modelo de ação [ActionModel03]. Isto acontece frequentemente. Podemos então utilizar um único modelo que sirva tanto de modelo de ação como de modelo de visualização. Criamos um novo modelo [ActionModel04]:

Image

que será o seguinte:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
  [Bind(Exclude="Erreurs")]
  public class ActionModel04
  {
    // ---------------------- Action --------------------------------
    [Required(ErrorMessage = "Le paramètre email est requis")]
    [EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
    public string Email { get; set; }
 
    [Required(ErrorMessage = "Le paramètre jour est requis")]
    [RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
    public string Jour { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    [MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
    public string Info1 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
    public string Info2 { get; set; }
 
    [Required(ErrorMessage = "Le paramètre info3 est requis")]
    [MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    [MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    public string Info3 { get; set; }
 
    // ---------------------- view --------------------------------
    public string Erreurs { get; set; }
  }
}

  • linhas 8–28: o modelo de ação com as suas restrições de integridade. Estes campos também farão parte da vista;
  • linha 31: uma propriedade específica do modelo de visualização. Foi excluída do modelo de ação pela anotação na linha 5.

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


    // Action03
    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 devolve este mesmo modelo como modelo de visualização;
  • linha 4: complementado com as informações [Errors];

Resta apenas criar a vista [/First/Action03.cshtml]:

  • em [1]: clique com o botão direito do rato no código [Action03] e, em seguida, selecione [Adicionar Vista];
  • em [2]: o nome da vista por predefinição;
  • em [3]: especifique que estamos a criar uma vista fortemente tipada;
  • em [4]: selecione a classe correta na lista suspensa, neste caso a classe [ActionModel04];
  • em [5]: a vista criada.

Atribuímos à vista [Action03] o mesmo código que à vista [Action02]. Apenas o modelo da vista (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 vamos chamar a ação [Action03] sem quaisquer parâmetros:

Image

Os resultados são os mesmos de antes. É comum utilizar o mesmo modelo tanto para a ação como para a vista, uma vez que o modelo da vista reutiliza frequentemente informações do modelo da ação. Por isso, utilizamos um modelo mais abrangente que pode ser utilizado tanto pela ação como pela vista que esta gera. Devemos 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 utilizador experiente poderia inicializar partes do modelo da vista sem o nosso conhecimento.

5.4. [Razor] – Introdução

Vamos agora apresentar alguns elementos das vistas [Razor], principalmente as instruções foreach e if.

Suponhamos que queremos apresentar uma lista de pessoas numa tabela HTML. O modelo de 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 tem uma matriz de pessoas do tipo [Person] definida nas linhas 12–16;
  • linhas 6–10: o construtor do modelo inicializa a propriedade [People] da linha 5 com uma matriz de duas pessoas.

A ação que produz este modelo como saída será a seguinte [Action04]:


    // Action04
    public ViewResult Action04()
    {
      return View(new ViewModel02());
}
  • linha 2: a ação não tem modelo de entrada;
  • linha 4: passa a sua vista padrão, uma instância do modelo [ViewModel02] que acabámos de definir.

A vista [Action04.cshtml] irá apresentar o modelo [ViewModel02]:

O código da vista [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 de visualização;
  • linha 2: importação do namespace para a classe [Person] utilizada na linha 24;
  • linhas 16–32: a tabela HTML que apresenta as pessoas do modelo;
  • Linha 24: O início do código C# é marcado pelo caractere @. A instrução [foreach] irá percorrer todas as pessoas no modelo;
  • linhas 26–27: o caractere < encerra o código C# e inicia o HTML. Em seguida, o caractere @ aparece novamente para voltar ao C# e escrever o nome da pessoa. Depois, o caractere < aparece novamente para voltar ao modo HTML;
  • Linha 28: A idade da pessoa é escrita.

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

Image

Outros elementos de uma vista podem ser preenchidos por uma coleção: listas (suspensas ou outras), botões de opção e caixas de seleção. Considere o seguinte exemplo novo, 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 [Person2] com três propriedades;
  • linha 3: o modelo [ViewModel05] para a vista;
  • linha 5: a lista de pessoas a apresentar na lista suspensa no formato [Nome Apelido];
  • linha 6: o [Id] da pessoa a selecionar na lista suspensa;
  • linhas 8–16: o construtor que cria uma matriz de três pessoas (linhas 10–13) e define o [Id] da pessoa que deve aparecer selecionada.

A vista [Action05.cshtml] exibirá este modelo:

O 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 na secção 2.5.2.6. Vamos revê-las:

Combo
<select size="1" name="cmbValues">
<option value="1">opção1</option>
<option selected="selected" value="2">opção 2</option>
<option value="3">Opção 3</option>
</select>

Image

Etiqueta HTML
<select size=".." name="..">
<option [selected="selected"] value=”v”>...</option>
...
</select>
exibe o texto entre as tags <option>...</option> numa lista
atributos
name="cmbValeurs": nome do controlo.
size="1": número de itens visíveis da lista. size="1" torna a lista equivalente a uma caixa combinada.
selected="selected": se esta palavra-chave estiver presente para um item da lista, esse item aparece selecionado na lista. No nosso exemplo acima, o item da lista choice2 aparece como o item selecionado na caixa combinada quando é exibido pela primeira vez.
value=”v”: se o item for selecionado pelo utilizador, este valor [v] é enviado para o servidor. Se este atributo estiver ausente, o texto exibido e selecionado é enviado para o servidor.

O código nas linhas 17–25 gera as tags <option> que são colocadas dentro da tag <select> na linha 16.

  • linha 17: percorremos a lista de pessoas no modelo;
  • linha 20: verificamos se a pessoa atual é a que deve ser selecionada. Se for, preparamos o texto selected="selected" para ser inserido na tag <option>;
  • Linha 24: a tag <option> é escrita.

Vamos chamar a ação de [Action05]:

  • em [1,2], as pessoas são apresentadas no formato [Nome Apelido];
  • em [1,2], a pessoa selecionada é aquela com um [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: a pessoa com [Id]=2 foi efetivamente selecionada.

Os dois exemplos acima são suficientes. Ao escrever uma vista [Razor], deve resistir à tentação de colocar lógica dentro dela. O código C# permitir-nos-ia fazê-lo. No entanto, no modelo MVC, a lógica deve estar na ação ou nas camadas inferiores [Lógica de Negócio, DAO], mas não na vista. Mesmo ao aderir ao padrão MVC, pode acabar por ter muita lógica na vista para calcular valores intermédios. Isto pode significar que o modelo utilizado não é suficientemente detalhado. O modelo deve conter os valores finais de que a vista necessita, para que esta não tenha de os calcular ela própria. Uma boa vista é aquela com lógica mínima e uma estrutura HTML clara. Se for inserido demasiado código C#, a estrutura HTML pode tornar-se ilegível.

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

5.5. Formulários – Introdução

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

Image

O modelo de visualização será o modelo [ViewModel05] utilizado anteriormente. A ação que irá apresentar esta visualização será a seguinte:


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

  • Linha 2: A ação só pode ser solicitada através de uma solicitação HTTP GET;
  • linha 5: a vista [/First/Action06Get.cshtml] será apresentada utilizando uma instância do tipo [ViewModel05] como modelo.

A vista [/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 transmita as informações introduzidas por um utilizador, precisamos de um formulário. Este é delimitado pela tag <form> nas linhas 18 e 31.

A tag HTML <form> foi apresentada na secção 2.5.2.1. Vamos rever as suas características:

formulário

<form method="post" action="FormulairePost.aspx">
Etiqueta HTML
<form name="..." method="..." action="...">...</form>
atributos
name="frmexample": nome do formulário - opcional
method="..." : método utilizado pelo navegador para enviar os valores recolhidos no formulário para o servidor web
action="..." : URL para a qual os valores recolhidos no formulário serão enviados.
Um formulário web é delimitado pelas tags <form>...</form>. O formulário pode ter um nome (name="xx"). Isto aplica-se a todos os controlos encontrados dentro de um formulário. O objetivo de um formulário é recolher informações introduzidas pelo utilizador através do teclado ou do rato e enviá-las para um URL de servidor web. Qual? Aquele referenciado no atributo action="URL". Se este atributo estiver em falta, as informações serão enviadas para o URL do documento que contém o formulário. Um cliente web pode utilizar dois métodos diferentes, denominados POST e GET, para enviar dados a um servidor web. O atributo method="método", em que método é definido como GET ou POST, na tag <form> indica ao navegador qual o método a utilizar para enviar as informações recolhidas no formulário para o URL especificado pelo atributo action="URL". Quando o atributo method não é especificado, o método GET é utilizado por predefinição.
  • linha 18: vemos que os valores do formulário serão enviados para o URL [/First/Action06] através de um pedido HTTP POST;
  • Linha 30: Um formulário deve ter um botão [submit]. Este botão aciona o envio dos valores introduzidos para o URL especificado pelo atributo [action] da tag <form>.

O que é que o navegador enviará exatamente quando o utilizador clicar no botão [Submit]? Isto foi explicado na secção 2.5.3.1. Vamos recapitular o que foi dito:


Controlo HTML


visual


valor(es) devolvido(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 selecionado pelo utilizador.
<input type="checkbox" name="C1" value="one"/>1
<input type="checkbox" name="C2" value="dois" checked="checked"/>2
<input type="checkbox" name="C3" value="three"/>3
C1=um
C2=dois
- valores dos atributos value das caixas de seleção selecionadas pelo utilizador
<input type="text" name="txtInput" size="20" value="algumas palavras"/>
txtInput=Programação+Web
- texto digitado pelo utilizador no campo de entrada. Os espaços foram substituídos pelo sinal +
<input type="password" name="txtMdp" size="20" value="aPassword"/>
txtPassword=istoÉSecreto
- texto digitado pelo utilizador no campo de entrada
<textarea rows="2" name="inputArea" cols="20">
linha1
linha2
linha3
</textarea>
campo de entrada=noções+básicas+de+programação+Web%0D%0A
programação+Web
- texto digitado pelo utilizador no campo de entrada. %OD%OA é o marcador de fim de linha. Os espaços foram substituídos pelo sinal +
<select size="1" name="cmbValues">
<option value='1'>opção1</option>
<option selected="selected" value='2'>opção2</option>
<option value='3'>opção3</option>
</select>
cmbValues=3
- Atributo [value] do elemento selecionado pelo utilizador
<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 utilizador
<select size="3" name="lst2" multiple="multiple">
<option selected="selected" value='1'>list1</option>
<option value='2'>list2</option>
<option selected="selected" value='3'>list3</option>
<option value='4'>list4</option>
<option value='5'>lista5</option>
</select>
lst2=1
lst2=3
- Atributos [value] dos elementos selecionados pelo utilizador
<input type="submit" value="Enviar" name="cmdSubmit"/>
 
cmdRenvoyer=Submit
- atributos name e value do botão utilizado para enviar os dados do formulário para o servidor
<input type="hidden" name="secret" value="aValue"/>
 
secret=aValue
- atributo value do campo oculto

No nosso formulário, temos duas tags que podem enviar um valor:


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

e


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

Se o utilizador selecionar a pessoa n.º 2, os valores serão enviados no seguinte formato:

personneId=2&valider=Valider

Os nomes dos parâmetros são os atributos [name] das tags envolvidas no POST. Sem este atributo, as tags não enviam um 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. Aqui, esta informação não é relevante para nós. Por vezes, os formulários têm vários botões [submit]. Nesses casos, é importante saber qual o botão que foi clicado. Por isso, iremos atribuir o atributo [name] aos diferentes botões.

A tag <select> consiste numa série 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 é enviado. Se este atributo estiver em falta, o texto apresentado pela opção — por exemplo, [Pierre Martino] — é enviado.

A cadeia

personneId=2&valider=Valider

será enviada para o seguinte URL [/First/Action06]:


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

Talvez se lembre que já tínhamos uma ação [Action06]:


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

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

  • [Action06] na linha 3 trata de um POST (linha 2);
  • [Action06] na linha c trata de um GET (linha b).

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

personneId=2&valider=Valider

Precisamos de um modelo de ação para encapsular estes valores. Este 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 este modelo e passa-o tal como está para a seguinte vista [Action06Post] (linha 5 da ação):


@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 é apresentado nas linhas 18 e 19.

Vejamos um exemplo:

Em [1], selecionamos a terceira pessoa com um [Id] igual a 3. Em [2], enviamos o formulário. Em [3], os valores recebidos. Em [4,5], vemos que foi chamada a mesma URL, uma via GET [4] e a outra via POST [5]. Isto não é visível na URL.

Na vista apresentada após o POST, podemos querer o nome e apelido da pessoa selecionada em vez do seu número. Por isso, temos de atualizar a vista POST e o seu modelo.

Criamos uma ação [Action07] para tratar deste caso. Esta ação terá de utilizar a sessão do utilizador para armazenar a lista de pessoas. Seguiremos o padrão abordado na Secção 4.10, que nos permite incluir dados dos âmbitos [Application] e [Session] no modelo da ação.

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


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

Precisamos de associar o tipo anterior [SessionModel] a um binder a que daremos o nome de [SessionModelBinder]. Este será idêntico ao descrito na página 74:

Image


using System.Web.Mvc;
 
namespace Exemple_03.Infrastructure
{
  public class SessionModelBinder : IModelBinder
  {
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
      // render scope data [Session]
      return controllerContext.HttpContext.Session["data"];
    }
  }
}

A ligação entre o modelo [SessionModel] e o seu [SessionModelBinder] é definida em [Global.asax]:


public class MvcApplication : System.Web.HttpApplication
  {
    protected void Application_Start()
    {
      ...
 
      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
    }
    // Session
    public void Session_Start()
    {
      Session["data"] = new SessionModel();
    }
  }
  • Linha 8: O modelo é associado ao seu binder em [Application_Start];
  • linha 13: uma instância do tipo [SessionModel] é adicionada à sessão associada à chave [data].

Uma vez feito isto, a ação [Action07] é a seguinte:


    // Action07-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, os dados do âmbito [Session] associados à chave [data];
  • linha 5: criamos o modelo de visualização;
  • linha 6: a matriz de pessoas é colocada na sessão. Vamos precisar dela no próximo pedido, o pedido POST. O protocolo HTTP é um protocolo sem estado. É necessário utilizar uma sessão para manter o estado entre pedidos. Uma sessão é específica para um utilizador e é gerida pelo servidor web;
  • linha 7: a vista [Action07Get.cshtml] é apresentada. É 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>

É idêntico à vista [Action06Get.cshtml] que já analisámos. A principal diferença está na linha 7: o URL para o qual os valores do formulário serão enviados. Estes serão processados pela seguinte ação [Action07]:


    // Action07-POST
    [HttpPost]
    public ViewResult Action07(SessionModel session, ActionModel06 modèle)
    {
      Personne2 personne = session.Personnes.Where(p => p.Id == modèle.PersonneId).First<Personne2>();
      string strPersonne = string.Format("{0} {1}", personne.Prénom, personne.Nom);
      return View("Action07Post", (object)strPersonne);
}
  • 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 são os dados do âmbito [Session] associados à chave [data];
  • linha 5: uma consulta LINQ recupera a pessoa com o [Id] que foi enviado;
  • linha 6: construímos a string a ser exibida pela vista [Action07Post] (linha 8);
  • linha 7: para chamar o construtor [View] correto, o tipo [string] deve ser convertido para [object].

A vista [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 string é apresentada.

Aqui está um exemplo de execução:

Image

Image

5.6. Formulário – um exemplo completo

Na secção 2.5.2.1, analisámos o seguinte formulário HTML:

Image

Vamos analisar uma ação [Action08Get] que apresenta (GET) este formulário e uma ação [Action08Post] que processa (POST) os valores introduzidos pelo utilizador. Uma configuração clássica.

O modelo de visualização [1] acima será uma instância da classe [ViewModel08]. Esta classe servirá tanto como:

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

5.6.1. O modelo de âmbito [Application]

Vamos assumir que os elementos apresentados pelos botões de rádio, caixas de seleção e várias listas são dados do âmbito [Application]. Um cenário comum. Esta informação provém de um ficheiro de configuração ou de uma base de dados acedida quando a aplicação é iniciada no método [Application_Start] do [Global.asax]. Este método evolui da seguinte forma:


    protected void Application_Start()
    {
....
 
      // model binders
      ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
      ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
 
      // scope data [Application]
      Application["data"] = new ApplicationModel();
}

  • linha 7: o tipo [ApplicationModel], que descreveremos em breve, está associado ao ligador de dados [ApplicationModelBinder] que já apresentámos na página 74;
  • linha 10: uma instância do tipo [ApplicationModel] é armazenada no dicionário da aplicação, associada à chave [data].

A classe [ApplicationModel] é utilizada para encapsular todos os dados dentro do âmbito [Application]. Aqui, irá encapsular os dados que o formulário precisa de apresentar:


namespace Exemple_03.Models
{
  public class ApplicationModel
  {
    // the collections to be displayed in the form
    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; }
 
    // initializing fields and collections
    public ApplicationModel()
    {
      RadioButtonFieldItems = new Item[]{
        new Item {Value="1",Label="oui"},
        new Item {Value="2", Label="non"}
      };
      CheckBoxesFieldItems = new Item[]{
        new Item {Value="1",Label="1"},
        new Item {Value="2", Label="2"},
        new Item {Value="3", Label="3"}
      };
      DropDownListFieldItems = new Item[]{
        new Item {Value="1",Label="choix1"},
        new Item {Value="2", Label="choix2"},
        new Item {Value="3", Label="choix3"}
      };
      SimpleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
      MultipleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
    }
    // the collections item
    public class Item
    {
      public string Label { get; set; }
      public string Value { get; set; }
    }
 
  }
}

  • linhas 45–49: o controlo do formulário que representa as várias coleções. [Label] é o texto apresentado pelo controlo do formulário, [Value] é o valor enviado por este controlo quando é 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: estas coleções são inicializadas pelo construtor sem parâmetros da classe.

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

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

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


    // Action08-GET
    [HttpGet]
    public ViewResult Action08Get(ApplicationModel application)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View("Formulaire", new ViewModel08(application));
}
  • Linha 2: [Action08Get] só responderá a um pedido [GET];
  • linha 3: recebe como parâmetro o modelo de aplicação que acabámos de descrever;
  • linha 5: inicializa os dados no contentor dinâmico [ViewBag];
  • linha 6: renderiza a vista [/First/Formulaire.cshtml] utilizando o modelo [ViewModel08]. Este modelo é o do formulário apresentado anteriormente. Para tal, passamos o modelo da aplicação — que define os elementos a serem exibidos — ao construtor.

5.6.3. O modelo de visualização [Form]

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


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
 
namespace Exemple_03.Models
{
  public class ViewModel08
  {
    // input fields
    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; }
 
    // the collections to be displayed in the form
    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; }
 
    // manufacturers
    public ViewModel08()
    {
    }
 
    public ViewModel08(ApplicationModel application)
    {
      // initialization collections
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // field initialization
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}

  • Num formulário, existem dois tipos de elementos: os que são apresentados e os que são introduzidos;
  • As linhas 20–24 definem os elementos a serem apresentados. Trata-se das várias coleções do formulário. Estas encontram-se no modelo da aplicação (linhas 34–38);
  • Linhas 10–17: definem os campos de entrada do formulário;
  • linha 10: [RadioButtonField] irá recuperar o valor enviado pelas linhas seguintes do formulário:


        <!-- radio buttons -->
        <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 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á uma string no formato:


param1=val1&RadioButtonField=2&param2=val2

se o utilizador tiver marcado a opção rotulada [no]. Na verdade, é o atributo [value] da opção marcada que é enviado.

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


        <!-- checkboxes -->
        <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 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á uma cadeia de caracteres no seguinte formato:


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

se o utilizador tiver marcado as caixas de seleção rotuladas [2] e [3]. É o atributo [value] das opções marcadas que é enviado. Como podem ser enviados vários parâmetros com o mesmo nome, [CheckBoxesField] é uma matriz de valores em vez de um único valor. Se nenhuma caixa de seleção for marcada, o parâmetro [CheckBoxesField] estará ausente da string enviada, e a propriedade do modelo com o mesmo nome não será inicializada. Isto pode ser problemático, como veremos.

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


          <!-- single-line text input field -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="quelques mots" size="30" />
            </td>
</tr>

Linha 5: O atributo [name] do campo de entrada é o nome da propriedade que será inicializada. Nos dados enviados, encontrará uma cadeia de caracteres no seguinte formato:


param1=val1&TextField=abcdef&param2=val2

se o utilizador tiver introduzido [abcdef] no campo de entrada.

  • Linha 13: [PasswordField] irá recuperar o valor enviado pelas seguintes linhas do formulário:


        <!-- password entry field -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="secret" size="30" />
          </td>
</tr>

Linha 5: O atributo [name] do campo de entrada é o nome da propriedade que será inicializada. Nos dados enviados, encontrará uma cadeia de caracteres no seguinte formato:


param1=val1&PasswordField=abcdef&param2=val2

se o utilizador tiver introduzido [abcdef] no campo de entrada.

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


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

Linha 5: O atributo [name] do campo de entrada é o nome da propriedade que será inicializada. Nos dados enviados, encontrará uma cadeia de caracteres no seguinte formato:


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

se o utilizador tiver introduzido [abcdef] seguido de uma quebra de linha e [ijk] no campo de entrada.

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


        <!-- the drop-down list -->
        <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>

Linha 5: O atributo [name] da tag <select> é o nome da propriedade que será inicializada. Nos dados enviados, encontrará uma cadeia de caracteres no seguinte formato:


param1=val1&DropDownListField=1&param2=val2

se o utilizador tiver selecionado a opção [choice1]. É o atributo [value] da opção selecionada que é enviado.

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


        <!-- single-choice list -->
        <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>

Linha 5: O atributo [name] da tag <select> é o nome da propriedade que será inicializada. O atributo [size="3"] garante que não tenhamos uma lista suspensa. Nos dados enviados, encontraremos uma string no seguinte formato:


param1=val1&SimpleChoiceListField=3&param2=val2

se o utilizador 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 item tiver sido selecionado.

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


        <!-- multiple choice list -->
        <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>

Linha 5: O atributo [name] da tag <select> é o nome da propriedade que será inicializada. O atributo [size="3"] garante que não haja uma lista suspensa, e o atributo [multiple] permite que o utilizador selecione vários itens mantendo premida a tecla [Ctrl]. Nos dados enviados, encontrará uma cadeia de caracteres no seguinte formato:


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

se o utilizador tiver selecionado as opções [list1] e [list3]. É o atributo [value] das opções selecionadas que é enviado. Como podem ser enviados vários parâmetros com o mesmo nome, [MultipleChoiceListField] é uma matriz de valores em vez de um único valor. Se nenhuma caixa de seleção estiver marcada, o parâmetro [MultipleChoiceListField] estará ausente da string enviada e a propriedade do modelo com o mesmo nome não será inicializada.

Os vários campos de entrada apresentados anteriormente receberão os valores enviados pelo formulário. Também podem ser inicializados antes de enviar o formulário. Foi isso que foi feito aqui:


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

Se estes valores fossem obtidos após um pedido POST do formulário, isso significaria que o utilizador:

  • linha 2: selecionado a opção [não] nos botões de opção;
  • linha 3: selecionado a opção [2] nas caixas de seleção;
  • linha 4: digitou [algumas palavras] no campo de entrada;
  • linha 5: introduzido [secret] como palavra-passe;
  • linha 6: digitou [linha1\nlinha2] no campo de entrada multilinha;
  • linha 7: selecionou a opção [option2] na lista suspensa;
  • linha 8: selecionou a opção [list3] da lista de escolha única;
  • linha 9: selecionou as opções [list1] e [list3] da lista de seleção múltipla;

Vamos proceder como se tivesse sido feita uma solicitação POST e quiséssemos devolver o formulário exatamente como foi preenchido. É exatamente isso que acontece quando um formulário incorreto é devolvido ao utilizador. O formulário é devolvido exatamente como foi preenchido.

5.6.4. A vista [Form]

A vista [/First/Formulaire.cshtml] apresenta 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>
        <!-- radio buttons -->
        <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> para o formulário. Este formulário será enviado utilizando o método [POST] (atributo method) para o URL [/First/Action08Post] (atributo action);
  • linha 33: o botão [submit] utilizado para enviar o formulário;
  • linhas 22–27: exibem os botões de opção:

Image

  • linha 22: percorremos a coleção apresentada pelo botão de opção;
  • linha 24: o botão cujo atributo [value] corresponde ao valor da propriedade [RadioButtonField] deve ser marcado. Para isso, deve ter o atributo [checked="checked"];
  • linha 25: gera a tag <input type="radio"> com o valor [@item.Value] e o rótulo [@item.Label];
  • linha 26: a tag <text/> não é uma tag HTML reconhecida. Está lá para o [Razor]. Quando encontrada, o [Razor] irá gerar uma quebra de linha. Isto não tem efeito no formulário apresentado, mas afeta o código HTML gerado. As tags <input type="radio"> ficam então em duas linhas diferentes, em vez de estarem na mesma linha. Isto torna o código mais legível quando visualiza o código-fonte da página apresentada no navegador;

Vamos rever os outros elementos da vista:


        <!-- checkboxes -->
        <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 caixa de seleção cujo atributo [value] seja um dos valores da propriedade [CheckBoxesField] deve estar marcada. Para isso, deve ter o atributo [checked="checked"]. Utilizamos uma expressão LINQ para determinar se um valor está contido numa matriz;
  • linha 25: gera a tag <input type="checkbox"> com o valor [@item.Value] e o rótulo [@item.Label];

<!-- single-line text input field -->
          <tr>
            <td>Champ de saisie</td>
            <td>
              <input type="text" name="TextField" value="@Model.TextField" size="30" />
            </td>
          </tr>
        <!-- password entry field -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="@Model.PasswordField" size="30" />
          </td>
        </tr>
        <!-- multiline text input field -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">@Model.TextAreaField</textarea>
          </td>
        </tr>
  • linhas 5, 12: atribuímos o valor do modelo ao atributo [value] da tag;
  • linha 19: igual ao anterior, mas com sintaxe diferente.

        <!-- the drop-down list -->
        <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: percorremos a coleção apresentada pela lista suspensa;
  • linha 9: deve então ser selecionada uma opção cujo atributo [value] corresponda ao valor da propriedade [DropDownListField]. Para tal, deve ter o atributo [selected="selected"]
  • linha 25: gera a tag <option value="value">label</option> com o valor [@item.Value] e o rótulo [@item.Label];

        <!-- single-choice list -->
        <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.


        <!-- multiple choice list -->
        <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: percorremos a coleção apresentada pela lista;
  • linha 9: deve ser selecionada uma opção cujo atributo [value] seja um dos valores da propriedade [MultipleChoiceListField]. Para tal, deve ter o atributo [selected="selected"]. Utilizamos uma expressão LINQ para determinar se um valor está contido numa matriz;
  • linha 10: geração da tag <option value="value">label</option> com [@item.Value] como valor e [@item.Label] como rótulo;

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:


    // Action08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      return View("Formulaire", modèle);
}
  • Linha 3: O modelo da aplicação é passado como parâmetro, juntamente com os valores enviados. Estes estão disponíveis num tipo [FormCollection]. O valor do parâmetro [RadioButtonField] enviado é obtido utilizando a expressão posted["RadioButtonField"]. Isto devolve uma cadeia de caracteres ou o ponteiro nulo. Se escrever posted["CheckBoxesField"], obterá uma matriz de cadeias de caracteres ou o ponteiro nulo;
  • então por que não escrever:

public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)

Existem duas razões:

  • a primeira é que o framework irá instanciar o modelo [ViewModel08] utilizando o construtor sem parâmetros, o que resultará na não inicialização das coleções do modelo;
  • a segunda é que queremos controlar o que entra no modelo. Sabemos que existem quatro fontes possíveis para o modelo: os parâmetros de um pedido GET ou POST, a rota utilizada e os dados de um ficheiro carregado. Aqui, queremos inicializar o modelo apenas com os valores enviados.
  • Linha 6: Instanciamos o modelo utilizando o construtor correto;
  • linha 7: inicializamo-lo com os valores enviados. Após esta operação, o modelo corresponde à entrada do utilizador;
  • Linha 8: Exibimos o formulário novamente. O utilizador irá vê-lo exatamente como foi preenchido.

Vejamos um exemplo:

Em [2], o resultado do [POST] reflete com precisão o que foi introduzido em [1].

5.6.6. Tratamento de erros POST

Mencionámos que, se nenhum valor fosse marcado ou selecionado para os campos [CheckBoxesField, SimpleChoiceListField, MultipleChoiceListField], os parâmetros correspondentes não faziam parte da string enviada e, portanto, as propriedades do modelo com os mesmos nomes não eram inicializadas.

Vejamos o seguinte exemplo:

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

A explicação é a seguinte:

  • como nenhuma caixa de seleção está marcada, o parâmetro [CheckBoxesField] não é incluído nos valores enviados;
  • a ação [Action08Post] prossegue da seguinte forma:

    [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 enviados são guardados no modelo. Uma vez que o parâmetro [CheckBoxesField] não faz parte dos valores enviados, não é atribuído um valor à propriedade com o mesmo nome. Por conseguinte, esta mantém o seu valor ["2"], o que significa que, quando o formulário é apresentado, a caixa de seleção n.º 2 fica marcada quando não deveria estar.

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


// Action08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      // processing of non-posted values
      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[] { };
      }
      // form display
      return View("Formulaire", modèle);
    }
  • Linhas 9–20: Verificamos se determinados parâmetros foram enviados ou não. Caso contrário, inicializamo-los com o valor correspondente ao facto de o utilizador não ter introduzido nada. Esta verificação não foi realizada para a lista suspensa, que tem sempre um item selecionado, ao contrário das outras listas.

Convidamos os leitores a testarem esta nova versão.

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

5.7.1. O novo formulário

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

Vamos rever o código utilizado para gerar a lista suspensa do formulário:


        <!-- the drop-down list -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
              @{
                foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
                {
                  string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>

Este código tem duas desvantagens:

  • a mais significativa é que a natureza do componente — neste caso, uma lista suspensa — perde-se devido à complexidade do código;
  • Linha 5: Se cometer um erro no nome da propriedade do modelo a ser utilizada como atributo [name], só o irá notar na altura da execução.

O ASP.NET MVC fornece métodos especializados chamados [HTML Helpers] que, como o próprio nome sugere, foram concebidos para facilitar a geração de HTML, particularmente para formulários. Utilizando estas classes, a lista suspensa apresentada acima é escrita da seguinte forma:


        <!-- the drop-down list -->
        <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–5. O código é significativamente menos complexo. O código HTML gerado para a lista suspensa é o seguinte:


        <!-- the drop-down list -->
        <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 são geradas corretamente e a opção correta foi selecionada.

Voltemos ao código que gerou estas linhas HTML:


@Html.DropDownListFor(m => m.DropDownListField, new SelectList(@Model.DropDownListFieldItems, "Value", "Label"))
  • O primeiro parâmetro é uma função lambda (é assim que se chama), em que `m` representa o modelo de visualização e `m.DropDownListField` é uma propriedade desse modelo. O gerador de código HTML utilizará o nome desta propriedade para gerar os atributos [id] e [name] do elemento [select] que será gerado. Se for utilizada uma propriedade inexistente, ocorrerá um erro em tempo de compilação, em vez de em tempo de execução. Trata-se de uma melhoria em relação à solução anterior, em que os erros de nomenclatura só eram detetados em tempo de execução;
  • o segundo parâmetro é utilizado para especificar a coleção de elementos que irá preencher a lista suspensa. A classe [SelectList] permite-lhe construir esta coleção:
    • o seu primeiro parâmetro é qualquer coleção de elementos. Aqui temos uma coleção do tipo [Item];
    • o seu segundo parâmetro é a propriedade dos elementos que fornecerá o valor da tag <option>. Aqui, é a propriedade [Value] da classe [Item];
    • o seu terceiro parâmetro é a propriedade dos elementos que fornecerá o rótulo para a tag <option>. Aqui, é a propriedade [Label] da classe [Item];
  • Para determinar qual a opção que deve ser selecionada (o atributo selected), o framework faz o que nós fazemos: compara o valor da opção com o valor atual da propriedade [DropDownListField].

Agora, vamos ver os outros métodos que podemos utilizar:

Botões de opção

O novo código é o seguinte:


        <!-- les boutons radio -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @{
    foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
    {
              @Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)@item.Label
              <text/>
    }
            }
          </td>
</tr>

O código HTML gerado é o seguinte:


        <!-- radio buttons -->
        <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 (o atributo [name]);
  • o segundo parâmetro é o valor a atribuir ao botão de opção (o atributo [value]).

Caixas de seleção

O código altera-se da seguinte forma:


        <!-- les cases à cocher -->
        <tr>
          <td>Cases à cocher</td>
          <td>
            @{
              @Html.CheckBoxFor(m=>m.CheckBoxField1) @Model.CheckBoxesFieldItems[0].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField2) @Model.CheckBoxesFieldItems[1].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField3) @Model.CheckBoxesFieldItems[2].Label
            }
</td>

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 [Propriedade=true], a caixa de seleção ficará marcada. Se [Propriedade=false], a caixa de seleção não ficará marcada. Em todos os casos, o atributo [value] é definido como 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 [Property] que a caixa de seleção, com o atributo [value="false"]. Por que existem duas tags [input] com o mesmo nome? Existem dois casos:
  • a caixa de seleção na linha 1 está marcada. Nesse caso, a sequência de parâmetros enviada é Property=true&Property=false (linhas 1 e 2). Como a propriedade [Property] espera apenas um valor, pode-se pensar que o framework atribui o valor [true] a [Property]. Para isso, bastaria realizar uma operação lógica OR entre os valores recebidos;
  • a caixa de seleção na linha 1 não está marcada. Neste caso, a cadeia de parâmetros enviada é Property=false (apenas na linha 2), e à propriedade [Property] é atribuído o valor [false], o que está correto (a caixa de seleção não estava marcada).

Campo de entrada de linha única

O novo código é o seguinte:


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

O código HTML gerado é o seguinte:


          <!-- single-line text input field -->
          <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 associada ao campo de entrada. O nome da propriedade será utilizado nos atributos [name] e [id] da tag <input> gerada, e o 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 introdução da palavra-passe

O novo código é o seguinte:


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

O código HTML gerado é o seguinte:


        <!-- password entry field -->
        <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 comportamento é semelhante ao do método [Html.TextBoxFor].

Campo de entrada multilinha

O novo código é o seguinte:


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

O código HTML gerado é o seguinte:


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

O método utilizado é o seguinte:


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

O comportamento é semelhante ao do método [Html.TextBoxFor].

Lista de seleção única

O novo código é o seguinte:


        <!-- single-choice list -->
        <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:


        <!-- single-choice list -->
        <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á abordámos o método [Html.DropDownListFor]. A única diferença aqui é o terceiro parâmetro, que é utilizado para especificar um atributo [size] diferente de 1. É esta funcionalidade que transforma uma lista suspensa [size=1] numa lista simples.

A lista de seleção múltipla

O novo código é o seguinte:


        <!-- multiple choice list -->
        <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:


        <!-- multiple choice list -->
        <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], exceto que gera uma lista de seleção múltipla. As opções selecionadas são aquelas cujo valor (atributo value) está na matriz [MultipleChoiceListField].

A tag <form> também pode ser gerada utilizando 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")

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

5.7.2. As ações e o modelo

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


    // Action09-GET
    [HttpGet]
    public ViewResult Action09Get(ApplicationModel application)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View("Formulaire2", new ViewModel09(application));
}

A vista renderizada na linha 6 é [Form2], associada ao seguinte modelo [ViewModel09]:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
using Exemple_03.Models;
 
namespace Exemple_03.Models
{
  public class ViewModel09
  {
    // input fields
    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; }
 
    // collections to be displayed in the form
    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; }
 
    // manufacturers
    public ViewModel09()
    {
    }
 
    public ViewModel09(ApplicationModel application)
    {
      // initialization collections
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // field initialization
      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] na forma como lida com as caixas de seleção. Em vez de ter uma matriz com três caixas de seleção, foram utilizadas três caixas de seleção separadas (linhas 11–13).

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


    // Action09-POST
    [HttpPost]
    public ViewResult Action09Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel09 modèle = new ViewModel09(application);
      TryUpdateModel(modèle, posted);
      // processing of non-posted values
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // form display
      return View("Formulaire2", modèle);
}

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

  • linha 18: a vista [Form2] é utilizada em vez da vista [Form];
  • já não há qualquer tratamento de caixas de seleção não marcadas. Isto é agora tratado corretamente pelo método [Html.CheckBoxFor].

5.8. Gerar um formulário a partir dos metadados do modelo

Existem outros métodos, além dos mencionados acima, para gerar um formulário. Um deles envolve associar informações a uma propriedade do modelo que indica à estrutura MVC qual a tag de entrada deve ser gerada. Esta informação é designada por metadados.

Considere 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; }
 
    // manufacturer
    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 consistem nas tags [Display, DataType, UIHint].

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


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

Na linha 5 acima, instruímos a vista padrão para a ação [/First/Action10Get.cshtml] a apresentar o modelo de vista do tipo [ViewModel10]. Esta vista é 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 propriedade do modelo, utilizamos o método:

  • Html.LabelFor para apresentar o valor dos metadados [DisplayName] da propriedade;
  • Html.EditorFor para gerar a tag de entrada HTML para o valor da propriedade. Este método utiliza os metadados [DataType] e [UIHint] da propriedade;
  • Html.DisplayFor para exibir o valor da propriedade no formato especificado pelos metadados [DataType].

Eis um exemplo de como isto funciona no navegador Chrome:

Image

Dependendo do navegador utilizado, as páginas podem ter um aspeto diferente. Isto deve-se ao facto de a vista gerada utilizar as novas tags introduzidas pela versão 5 do HTML, conhecida como HTML5. Nem todos os navegadores suportam ainda esta versão. No exemplo acima, o navegador Chrome suporta-a parcialmente.

5.8.1. O [POST] do formulário

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


    // Action10-POST
    [HttpPost]
    public ContentResult Action10Post(ViewModel10 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      string texte = string.Format("Contrôleur={0}, Action={1}, valide={2}, erreurs={3}", RouteData.Values["controller"], RouteData.Values["action"], ModelState.IsValid, erreurs);
      return Content(texte, "text/plain", Encoding.UTF8);
}
  • linha 3: a ação [Action10Post] utiliza o formulário enviado como seu modelo de entrada;
  • linha 5: recuperamos os erros de validação deste formulário;
  • linha 6: a resposta de texto para o cliente é preparada;
  • linha 7: enviamo-la.

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

5.8.2. Propriedade [Text]

Definição


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

View


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

Visual

Image

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> na linha 2. O valor do atributo [for] é o nome da propriedade do parâmetro do método [Html.LabelFor]


public string Text { get; set; }

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


[Display(Name="Text")]

O método [Html.LabelFor] funciona sempre desta forma. Não voltaremos a abordar este assunto para as outras propriedades.

  • O método [Html.EditorFor] gerou a tag <input> na linha 3. Note que ela possui um atributo [class] que associa a classe CSS [text-box single-line] à tag. Os atributos [id] e [name] têm o valor [Text], que é o nome da propriedade do parâmetro do método [Html.EditorFor]. O atributo [type] tem o valor [text] devido aos metadados


[DataType(DataType.Text)]

  • o método [Html.DisplayFor] gerou o texto na linha 4. Este é o valor da propriedade do parâmetro do método [Html.DisplayFor]. Este método é influenciado pelos metadados


[DataType(DataType.Text)]

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

5.8.3. Propriedade [MultiLineText]

Definição


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

Exibir


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

Visual

Image

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> na linha 3. Note-se que esta possui um atributo [class] que associa a classe CSS [text-box multi-line] à tag. Os atributos [id] e [name] têm o valor [MultiLineText], que é o nome da propriedade do parâmetro do método [Html.EditorFor]. Isto acontece sempre. Não voltaremos a referi-lo. A tag gerada é <textarea> devido aos metadados


[DataType(DataType.MultilineText)]

que especificou que a propriedade era texto multilinha.

  • O método [Html.DisplayFor] gerou o texto para as linhas 4–5. Este é o valor da propriedade parameter do método [Html.DisplayFor].

5.8.4. Propriedade [Number]

Definição


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

Visualização


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

Visual

Image

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> na linha 3 com um atributo [type] definido como [number]. Aparentemente, isto deve-se simplesmente ao facto de a propriedade ter o tipo [int]. Os atributos [data-val], [data-val-number] e [data-val-required] não são reconhecidos pelo HTML5. São utilizados por uma estrutura de validação de dados JavaScript do lado do cliente;
  • O método [Html.DisplayFor] gerou o texto na linha 4, que é o valor da propriedade.

Validação

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

Introduzimos um número incorreto e submetemos:

Image

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

Outro exemplo: não introduzimos nada:

Em [1] acima, [Action10Post] reporta um erro. Talvez se lembre que anteriormente conseguíamos este comportamento utilizando o atributo [Required] na propriedade a ser validada (ver 63), neste caso a propriedade [Number]. Aqui, não foi necessário fazê-lo.

5.8.5. Propriedade [Decimal]

Definição


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

Ver


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [text]. Os outros atributos são idênticos aos gerados para a propriedade [Number] anterior. Os metadados:


[UIHint("Decimal")]

faz com que o valor da propriedade seja apresentado com duas casas decimais, tanto para o método [Html.EditorFor] como para o método [Html.DisplayFor]

Validação

Não são reportados erros de validação no lado do cliente, ao contrário do caso anterior. O erro é reportado apenas pela ação [Action10Post]. Mais uma vez, o número decimal é obrigatório sem necessidade de definir o atributo [Required].

5.8.6. Propriedade [Tel]

Definição


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

Ver


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [tel]. Este valor foi gerado com base nos metadados:


[DataType(DataType.PhoneNumber)]

O tipo [tel] para uma tag <input> é uma nova funcionalidade do HTML5. O navegador Chrome tratou-a como uma tag <input> com o tipo [text].

Validação

Não são reportados erros de validação nem no lado do cliente nem no lado do servidor. Pode introduzir qualquer coisa.

5.8.7. Propriedade [Data]

Definição


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

Exibir


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [date]. Este valor foi gerado com base nos metadados:


[DataType(DataType.Date)]

O tipo [date] para uma tag <input> é uma nova funcionalidade do HTML5. O navegador Chrome reconhece-o e permite que a data seja introduzida através de um calendário. Além disso, a data introduzida é apresentada no formato [dd/mm/aaaa], o que significa que o Chrome adapta o formato da data à [locale] do navegador.

  • O método [Html.DisplayFor] também escreveu a data no formato [dd/mm/aaaa], mais uma vez devido à presença dos metadados [Date].

Validação

Uma data inválida é sinalizada no lado do cliente [1], impedindo que o formulário seja enviado para o servidor.

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

5.8.8. Propriedade [Time]

Definição


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

Exibir


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [time]. Este valor foi gerado com base nos metadados:


[DataType(DataType.Time)]

O tipo [time] para uma tag <input> é uma nova funcionalidade do HTML5. O navegador Chrome reconhece-o e permite introduzir uma hora no formato [hh:mm];

  • O método [Html.DisplayFor] também apresentou a hora no formato [hh:mm], mais uma vez devido à presença dos metadados [Time].

Validação

É tecnicamente impossível introduzir uma hora inválida. A ausência de uma hora é sinalizada no lado do servidor:

Image

5.8.9. Propriedade [HiddenInput]

Definição


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

Exibir


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

Visual

Image

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a tag <input> na linha 3 com um atributo [type] definido como [hidden], ou seja, um campo oculto (mas que ainda é enviado). Este 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; }

Exibir


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

Visual

Image

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> na linha 3 com um atributo [type] definido como [checkbox], ou seja, uma caixa de seleção. Este 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; }

Exibir


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [email]. Este tipo é novo no HTML5. Este tipo foi gerado devido aos metadados:


[DataType(DataType.EmailAddress)]

O Chrome parece ter tratado este 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

É reportado um endereço inválido no lado do cliente [1]:

Deixar o campo em branco não causa um erro.

5.8.12. Propriedade [Url]

Definição


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

Exibir


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [url]. Este tipo é novo no HTML5. Foi gerado devido aos metadados:


[DataType(DataType.Url)]

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

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

Validação

É reportado um URL inválido no lado do cliente [1]:

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

5.8.13. Propriedade [Password]

Definição


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

Exibir


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [password]. Este tipo foi gerado com base nos 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; }

Exibir


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

Visual

Image

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> na linha 3 com um atributo [type] definido como [text];

  • O método [Html.DisplayFor] gerou a linha 4, um número com duas casas decimais e um símbolo monetário. Este formato foi utilizado devido aos metadados:


[DataType(DataType.Currency)]

Validação

Um valor inválido [1] ou um valor em falta [2] é reportado no lado do servidor:

5.8.15. Propriedade [CreditCard]

Definição


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

Ver


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

Visual

Image

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> na linha 3 com um atributo [type] do tipo [text]. O método [Html.DisplayFor] gerou a linha 4. Não é claro aqui qual é a contribuição dos metadados:


[DataType(DataType.CreditCard)]

Validação

Não é realizada qualquer validação, nem no lado do cliente nem no lado do servidor.

5.9. Validação do formulário

Já abordámos a questão da validação do modelo de uma ação na Secção 4.5 e nas secções seguintes. Voltamos a abordar esta questão no contexto de um formulário:

  • como notificar o utilizador de erros de introdução de dados;
  • realizar validações tanto no lado do cliente como no lado do servidor para comunicar os erros ao utilizador mais rapidamente.

5.9.1. Validação do lado do servidor

Considere 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; }
 
    // validation
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Date 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // Email1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // return the list of errors
      return résultats;
    }
  }
}

Este modelo será apresentado pela seguinte vista [Action11Get.cshtml]:


@model Exemple_03.Models.ViewModel11
@{
  Layout = null;
}
 
<!DOCTYPE html>
 
<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action11Get</title>
  <link rel="stylesheet" href="~/Content/Site.css" />
</head>
<body>
  <h3>Formulaire ASP.NET MVC – Validation 1</h3>
  @using (Html.BeginForm("Action11Post", "First"))
  {
    <table>
      <thead>
        <tr>
          <th>Type attendu</th>
          <th>Valeur saisie</th>
          <th>Message d'erreur</th>
        </tr>
      </thead>
      <tbody>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine1)</td>
          <td>@Html.EditorFor(m => m.Chaine1)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine2)</td>
          <td>@Html.EditorFor(m => m.Chaine2)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Chaine3)</td>
          <td>@Html.EditorFor(m => m.Chaine3)</td>
          <td>@Html.ValidationMessageFor(m => m.Chaine3)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Entier1)</td>
          <td>@Html.EditorFor(m => m.Entier1)</td>
          <td>@Html.ValidationMessageFor(m => m.Entier1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Entier2)</td>
          <td>@Html.EditorFor(m => m.Entier2)</td>
          <td>@Html.ValidationMessageFor(m => m.Entier2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Reel1)</td>
          <td>@Html.EditorFor(m => m.Reel1)</td>
          <td>@Html.ValidationMessageFor(m => m.Reel1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Reel2)</td>
          <td>@Html.EditorFor(m => m.Reel2)</td>
          <td>@Html.ValidationMessageFor(m => m.Reel2)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Email1)</td>
          <td>@Html.EditorFor(m => m.Email1)</td>
          <td>@Html.ValidationMessageFor(m => m.Email1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Regexp1)</td>
          <td>@Html.EditorFor(m => m.Regexp1)</td>
          <td>@Html.ValidationMessageFor(m => m.Regexp1)</td>
        </tr>
        <tr>
          <td>@Html.LabelFor(m => m.Date1)</td>
          <td>@Html.EditorFor(m => m.Date1)</td>
          <td>@Html.ValidationMessageFor(m => m.Date1)</td>
        </tr>
      </tbody>
    </table>
    <p>
      <input type="submit" value="Valider" />
    </p>
  }
</body>
</html>

  • linha 12: faz referência à folha de estilos [Site.css]. Por predefinição, contém classes utilizadas para destacar erros de preenchimento de formulários;
  • linhas 18–25: uma tabela de três colunas:
    • A coluna 1 exibe texto utilizando o método [Html.LabelFor];
    • A coluna 2 apresenta a entrada utilizando o método [Html.EditorFor],
    • A coluna 3 apresenta quaisquer erros de entrada utilizando o método [Html.ValidationMessageFor];

A ação [Action11Get] é utilizada para apresentar o formulário:


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

A ação [Action11Post] é utilizada para voltar a apresentar o formulário com quaisquer erros de introdução de dados:


    // Action11-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. Podem ocorrer erros. Cada propriedade inválida P do modelo está associada a uma mensagem de erro. Esta é a mensagem devolvida pelo método [Html.ValidationMessageFor] do formulário.

Eis um exemplo de execução:

Image

Image

Eis outro exemplo:

Image

Note que ambas as datas estão incorretas (a data atual é 11/10/2013), mas os erros não são reportados. Estes erros são detetados pelo método [Validate] do modelo:


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

O método [Validate] só é executado depois de todas as validações de atributos terem sido aprovadas. Isto é demonstrado num exemplo final:

Image

5.9.2. Validação do lado do cliente

Todas as validações anteriores foram realizadas no lado do servidor. Isto requer uma ida e volta entre o cliente e o servidor para que o utilizador veja os seus erros. A validação do lado do cliente utiliza código JavaScript para notificar o utilizador dos erros o mais cedo possível e, em qualquer caso, antes do pedido POST. O pedido POST só pode ocorrer depois de todos os erros detetados terem sido corrigidos.

Vamos utilizar o modelo [ViewModel11] anterior, mas agora vamos exibi-lo utilizando a seguinte vista [Action12Get.cshtml]:


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

Nota: Linha 13 — ajuste a versão do jQuery para corresponder à que tem na sua versão do Visual Studio (ver abaixo).

A validação do lado do cliente requer que a linha 3 abaixo esteja presente no ficheiro [Web.config] da aplicação.


  <appSettings>
    ...
    <add key="ClientValidationEnabled" value="true" />
</appSettings>

  • Linhas 1–4: A secção [appSettings] deve ser um elemento filho direto da secção [configuration] no ficheiro [Web.config];

A vista [Action12Get] é idêntica à vista [Action11Get] anterior, exceto pelas linhas 13–15. Estas linhas incluem os scripts JavaScript necessários para a validação do lado do cliente na vista. Estes scripts estão localizados na pasta [Scripts] do projeto:

Cada script tem uma versão normal [.js] e uma versão minimizada [min.js]. A última versão é mais pequena, mas ilegível. É utilizada em produção. A versão legível é utilizada em desenvolvimento.

A vista [Action12Get.cshtml] será apresentada pela seguinte ação [Action12Get]:


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

O formulário enviado será processado pela seguinte ação [Action12Post]:


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

Vamos ver como isto funciona com um exemplo:

Assim que digitar um caractere em [1], a mensagem em [2] aparece porque o valor esperado deve ter pelo menos quatro caracteres. Assim, a validação ocorre a cada novo caractere digitado. A mensagem de erro desaparece após o quarto caractere ser digitado. Feito isso, vamos enviar o formulário:

A URL [3] mostra-nos que o [POST] não foi efetuado. Mas clicar no botão [Validate] acionou todas as validações do lado do cliente, e surgiram novas mensagens de erro.

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


        <tr>
          <td><label for="Chaine1">Cha&#238;ne d&#39;au moins quatre caract&#232;res</label></td>
          <td><input class="text-box single-line" data-val="true" data-val-regex="Information incorrecte" data-val-regex-pattern="^.{4,}$" data-val-required="Information requise" id="Chaine1" name="Chaine1" type="text" value="" /></td>
          <td><span class="field-validation-valid" data-valmsg-for="Chaine1" data-valmsg-replace="true"></span></td>
</tr>

  • A linha 3 contém:
    • a mensagem de erro para quando falta uma entrada [data-val-required],
    • a mensagem de erro para quando a entrada está incorreta [data-val-regex],
    • a expressão regular para a cadeia de caracteres introduzida [data-val-regex-pattern];
  • linha 4, outros atributos [data-x] utilizados para apresentar eventuais mensagens de erro;

Os atributos [data-x] das tags geradas são utilizados pelo JavaScript que incorporámos na vista. Se este JavaScript estiver em falta, estes atributos são simplesmente ignorados e não há validação do lado do cliente. Funciona da mesma forma que no exemplo anterior. Daí o termo [discreto] para esta técnica.

Iremos criar as duas vistas seguintes para ilustrar a gestão de links dentro de uma vista:

  • Em [1] e [2], temos dois links de navegação;
  • em [3], há um link de ação que envia o formulário. Não é utilizado para navegação.

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() {
      // on récupère le formulaire du document
      var form = document.forms[0];
      // soumission
      form.submit();
    }
  </script>
</head>
<body>
  <h3>Navigation - page 1</h3>
  <h4>@ViewBag.info</h4>
  @using (Html.BeginForm("Action16Post", "Second"))
  {
    @Html.Label("data", "Tapez un texte")
    @Html.TextBox("data")
    <a href="javascript:postForm()">Valider</a>
  }
  <p>
    @Html.ActionLink("Page 2", "Action17Get", "Second")
  </p>
</body>
</html>

  • linha 22: informações inicializadas pela ação que irá renderizar a vista;
  • 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 [submit]. Quando clicado, a função JavaScript [postForm] é executada (atributo href). Esta função está definida nas linhas 12–17;
  • linha 14: é recuperada uma referência para o primeiro formulário no documento, o que se encontra na linha 23;
  • linha 16: este formulário é enviado. Em última análise, comporta-se como se um botão [submit] tivesse sido clicado. O formulário é enviado para o controlador e a 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(Text, Action, Controller).

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 estas visualizações são as seguintes:


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

  • linha 6, a ação [Action16Get] renderiza a vista [Action16Get.cshtml], ou seja, a página 1 do exemplo. Esta vista baseia-se no [ViewBag] (linha 5);
  • linha 19: a ação [Action17Get] gera a vista [Action17Get.cshtml], ou seja, a página 2 do exemplo. Esta vista baseia-se no [ViewBag] (linha 21);
  • linha 11: a ação [Action16Post] processa o pedido POST proveniente do formulário na vista [Action16Get.cshtml]. Recebe o parâmetro denominado [data]. Recorde-se que este é o nome do campo de entrada no formulário;
  • linha 13: as informações são colocadas no [ViewBag];
  • linha 14: a vista [Action16Get.cshtml] é apresentada.

Os leitores são encorajados a testar este exemplo.