Skip to content

5. A vista e o seu modelo

5.1. Introduction

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

No capítulo anterior, analisámos como o ASP.NET MVC apresentava as informações do pedido [1] a uma ação [2a] sob a forma de um modelo que podia conter restrições de validação. Este modelo era fornecido como entrada para a ação e denominámo-lo «modelo da ação». Vamos agora centrar-nos no resultado mais comum de uma ação, o tipo [ViewResult], que corresponde a uma vista V [3] acompanhada do seu modelo M [2c]. Este modelo será designado por «modelo da vista V», não devendo ser confundido com o modelo da ação que acabámos de analisar. Um é uma entrada da ação, o outro é uma saída.

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

Vamos criar um controlador denominado [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 do resultado do método [Index] é o da classe [ActionResult], da qual derivam a maioria dos resultados possíveis de uma ação;
  • linha 9: o método [View] da classe [Controller] (linha 5) devolve um tipo [ViewResult] que deriva de [ActionResult]. Este método admite várias sobrecargas. Vamos ver algumas delas. A principal é a seguinte:
 
  • o primeiro parâmetro é o nome da vista. Se estiver ausente, a vista utilizada é aquela que tem o mesmo nome que a ação que produz o [ViewResult] e que será procurada na pasta [/Views/{controller}], onde {controller} é o nome do controlador;
  • o segundo é o modelo da vista. Se estiver ausente, a vista não 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 transmite qualquer modelo. Vamos criar [1] na pasta [/Views/First]:

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

Indica-se o nome da vista como [3]. Esta é criada como [4]. O código gerado é o seguinte:


@{
    Layout = null;
}

<!DOCTYPE html>

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

Trata-se de código HTML clássico, exceto pelas linhas 1 a 3, que são código C#. O programa que gere as vistas é denominado motor de vistas. Este encarrega-se de gerir tudo o que não é HTML para o converter em HTML. No final, de facto, é isso que será enviado ao cliente. O motor de vistas chama-se aqui [Razor]. Permite incluir código C# numa vista. O [Razor] irá interpretar esse código C# e, a partir dele, produzir código HTML. Aqui estão algumas regras básicas para a inclusão de código C# numa vista:

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

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

Alteremos 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: apresenta o valor dessa variável.

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

 

O código HTML recebido é o seguinte:

<!DOCTYPE html>

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

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

5.2. Utilizar o [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:


    // Ação01
    public ViewResult Action01()
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      return View();
}
  • linha 4: utiliza-se a propriedade [ViewBag] do controlador. Trata-se de um objeto dinâmico ao qual é possível adicionar propriedades, tal como se faz na linha 4. Este objeto tem a particularidade de também ser acessível à vista. É, portanto, uma forma de lhe transmitir informação;
  • linha 5: é solicitada a vista predefinida da ação. Trata-se da vista [/First/Action01.cshtml]. Não lhe é transmitido nenhum modelo.

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 testar. Solicitamos o URL [/First/Action01]:

 

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, pois a propriedade [Info] não existe. A propriedade criada pela ação [Action01] chama-se [info]. É possível, então, utilizar um modelo fortemente tipado para evitar este inconveniente.

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


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

A ação [Action10] transmitia ao seu cliente seis informações (e-mail, dia, Info1, Info2, Info3, erros) sob a forma de uma cadeia de caracteres. Vamos transmitir estas informações num modelo de vista [ViewModel01]. Como este modelo retoma informações de [ActionModel03], vamos derivá-lo desta classe.

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

e alteramos o seu espaço de nomes para que corresponda ao do projeto [Exemple-03]:


using System.ComponentModel.DataAnnotations;
namespace Exemple_03.Models
{
  public class ActionModel03
  {
    [Required(ErrorMessage = "Le paramètre email est requis")]
    [EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Le paramètre jour est requis")]
    [RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
    public string Jour { get; set; }

    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    [MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
    public string Info1 { get; set; }

    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
    public string Info2 { get; set; }

    [Required(ErrorMessage = "Le paramètre info3 est requis")]
    [MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    [MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    public string Info3 { get; set; }
  }
}
  • linha 2: o novo espaço de nomes;

Em seguida, criamos a classe [ViewModel01]:

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


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

Escrevemos agora a ação [Action02] que:

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

O seu código é o seguinte:


    // Ação 02
    public ViewResult Action02(ActionModel03 modèle)
    {
      string erreurs = getErrorMessagesFor(ModelState);
      return View(new ViewModel01(){Email=modèle.Email, Jour=modèle.Jour, Info1=modèle.Info1, Info2=modèle.Info2, Info3=modèle.Info3, Erreurs=erreurs});
}
  • linha 1: [Action02] recebe o modelo de ação [ActionModel03]. Devolve um resultado do tipo [ViewResult];
  • linha 4: os erros relacionados com o modelo de ação [ActionModel03] são agregados na cadeia de caracteres [erreurs]. O método [getErrorMessagesFor] foi descrito na página 65 e foi incluído no controlador [First] do novo projeto;
  • linha 5: o método [View] é chamado com um parâmetro. Este é o modelo da vista. A vista não é especificada. Por conseguinte, será utilizada a vista por predefinição [/Views/First/Action02]. O modelo da vista [ViewModel01] é instanciado e inicializado com as cinco informações do modelo de ação [ActionModel03] e a informação [erreurs] construída na linha 4.

Construímos agora 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 novidade reside na linha 1. A notação [@model] define o tipo do modelo da vista. Este modelo é, em seguida, 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:

 

depois com parâmetros incorretos:

e, por fim, com parâmetros corretos:

Neste exemplo, o modelo da vista [ViewModel01] retoma as informações do modelo de ação [ActionModel03]. Isto acontece frequentemente. É então possível utilizar um único modelo que servirá tanto de modelo de ação como de vista. Criamos um novo modelo [ActionModel04]:

  

que terá o seguinte aspeto:


using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_03.Models
{
  [Bind(Exclude="Erreurs")]
  public class ActionModel04
  {
    // ---------------------- Ação --------------------------------
    [Required(ErrorMessage = "Le paramètre email est requis")]
    [EmailAddress(ErrorMessage = "Le paramètre email n'a pas un format valide")]
    public string Email { get; set; }

    [Required(ErrorMessage = "Le paramètre jour est requis")]
    [RegularExpression(@"^\d{1,2}$", ErrorMessage = "Le paramètre jour doit avoir 1 ou 2 chiffres")]
    public string Jour { get; set; }

    [Required(ErrorMessage = "Le paramètre info1 est requis")]
    [MaxLength(4, ErrorMessage = "Le paramètre info1 ne peut avoir plus de 4 caractères")]
    public string Info1 { get; set; }

    [Required(ErrorMessage = "Le paramètre info2 est requis")]
    [MinLength(2, ErrorMessage = "Le paramètre info2 ne peut avoir moins de 2 caractères")]
    public string Info2 { get; set; }

    [Required(ErrorMessage = "Le paramètre info3 est requis")]
    [MinLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    [MaxLength(4, ErrorMessage = "Le paramètre info3 doit avoir 4 caractères exactement")]
    public string Info3 { get; set; }

    // ---------------------- vista --------------------------------
    public string Erreurs { get; set; }
  }
}
  • linhas 8-28: o modelo da 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 da vista. Foi excluída do modelo da ação pela anotação da linha 5.

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


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

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

  • em [1]: clique com o botão direito do rato no código de [Action03] e, em seguida, em [Ajouter une vue];
  • em [2]: o nome da vista proposto por predefinição;
  • em [3]: indicar que se está a criar uma vista fortemente tipada;
  • em [4]: selecionar 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, executemos a ação [Action03] sem parâmetros:

 

Os resultados são os mesmos de antes. É frequente utilizar o mesmo modelo para a ação e para a vista, uma vez que o modelo da vista retoma frequentemente informações do modelo da ação. Utiliza-se, assim, um modelo mais abrangente, que pode ser utilizado tanto pela ação como pela vista que esta gera. Deve-se 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 bem informado poderia inicializar partes do modelo da vista sem o nosso conhecimento.

5.4. [Razor] – primeiros passos

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

Suponhamos que se pretenda apresentar uma lista de pessoas numa tabela HTML. O modelo da vista poderia ser o seguinte [ViewModel02]:


namespace Exemple_03.Models
{
  public class ViewModel02
  {
    public Personne[] Personnes { get; set; }
    public ViewModel02()
    {
      Personnes = new Personne[] { new Personne { Nom = "Pierre", Age = 44 }, new Personne { Nom = "Pauline", Age = 12 } };
    }
  }

  public class Personne
  {
    public string Nom { get; set; }
    public int Age { get; set; }
  }
}
  • a vista do modelo é a classe [ViewModel02], linhas 3-10;
  • linha 5: o modelo possui um array de pessoas do tipo [Personne], definido nas linhas 12-16;
  • linhas 6-10: o construtor do modelo inicializa a propriedade [Personnes] da linha 5 com um tabuleiro de duas pessoas.

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


    // Ação04
    public ViewResult Action04()
    {
      return View(new ViewModel02());
}
  • linha 2: a ação não tem nenhum modelo de entrada;
  • linha 4: passa para a sua vista por predefiniçã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 da vista;
  • linha 2: a importação do espaço de nomes da classe [Personne] utilizada na linha 24;
  • linhas 16-32: o array HTML que apresenta as pessoas do modelo;
  • linha 24: o início do código C# é assinalado pelo caractere @. A instrução [foreach] irá percorrer todas as pessoas do modelo;
  • linhas 26-27: o carácter < interrompe o C# e dá início ao HTML. Em seguida, volta a aparecer o carácter @ para alternar para o C# e escrever o nome da pessoa. Depois, volta a aparecer o carácter <, que faz a alternância para o modo HTML;
  • linha 28: escreve-se a idade da pessoa.

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

 

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

O modelo [ViewModel05] será o seguinte:


namespace Exemple_03.Models
{
  public class ViewModel05
  {
    public Personne2[] Personnes { get; set; }
    public int SelectedId { get; set; }

    public ViewModel05()
    {
      Personnes = new Personne2[] { 
        new Personne2 { Id = 1, Prénom = "Pierre", Nom = "Martino" }, 
        new Personne2 { Id = 2, Prénom = "Pauline", Nom = "Pereiro" }, 
        new Personne2 { Id = 3, Prénom = "Jacques", Nom = "Alfonso" } };
      SelectedId = 2;
    }
  }

  public class Personne2
  {
    public int Id { get; set; }
    public string Nom { get; set; }
    public string Prénom { get; set; }
  }
}
  • linha 18: uma classe [Personne2] com três propriedades;
  • linha 3: o modelo [ViewModel05] da vista;
  • linha 5: a lista de pessoas a apresentar na lista suspensa, na forma [Prénom Nom];
  • linha 6: o [Id] da pessoa a selecionar na lista suspensa;
  • linhas 8-16: o construtor que cria uma tabela com três pessoas (linhas 10-13) e define o [Id] da pessoa que deve aparecer selecionada.

A vista [Action05.cshtml] irá apresentar 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 no parágrafo 2.5.2.6. Recorde-se que são as seguintes:

Lista suspensa
<select size="1" name="cmbValeurs">
<option value="1">opção1</option>
<option selected="selected" value="2">opção 2</option>
<option value="3">opção 3</option>
</select>
 
tag HTML
<select size=".." name="..">
<option [selected="selected"] value=”v”>...</option>
...
</select>
exibe numa lista os textos contidos entre as balizas <option>...</option>
atributos
name="cmbValeurs": nome do controlo.
size="1": número de elementos da lista visíveis. size="1" transforma a lista no equivalente a uma caixa de seleção.
selected="selected": se esta palavra-chave estiver presente para um elemento da lista, este aparece selecionado na lista. No nosso exemplo acima, o elemento da lista choix2 aparece como o elemento selecionado da caixa de seleção quando esta é apresentada pela primeira vez.
value=”v”: se o elemento for selecionado pelo utilizador, é este valor [v] que é enviado para o servidor. Na ausência deste atributo, é o texto apresentado e selecionado que é enviado para o servidor.

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

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

Solicitemos a ação [Action05]:

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

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


<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action05</title>
</head>
<body>
  <select>
      <option value="1" >Pierre Martino</option>
      <option value="2" selected=&quot;selected&quot;>Pauline Pereiro</option>
      <option value="3" >Jacques Alfonso</option>
  </select>
</body>
</html>
  • linhas 10-12: as três tags <option> geradas pelo código [Razor];
  • linha 11: foi efetivamente a pessoa de [Id]=2 que foi selecionada.

Os dois exemplos acima são suficientes. Ao escrever uma vista [Razor], é preciso resistir à tentação de incluir lógica nela. 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 [Metier, DAO], mas não na vista. Mesmo respeitando o modelo MVC, podemos acabar por ter muita lógica na vista para calcular valores intermédios. Isso pode significar que o modelo utilizado não é suficientemente detalhado. Este deve conter os valores finais de que a vista necessita, para que esta não tenha de os calcular por si própria. Uma boa vista é aquela em que há um mínimo de lógica e em que a estrutura HTML da vista permanece clara. Se se inserir 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 que pessoa ele selecionou. Para isso, precisamos de um formulário.

5.5. Formulário – primeiros passos

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

 

O modelo da vista será o modelo [ViewModel05], já utilizado anteriormente. A ação que irá apresentar esta vista será a seguinte:


    // Ação06-GET
    [HttpGet]
    public ViewResult Action06()
    {
      return View("Action06Get",new ViewModel05());
}
  • linha 2: a ação só pode ser solicitada por um comando HTTP GET;
  • linha 5: a vista [/First/Action06Get.cshtml] será apresentada com 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 possa transmitir as informações introduzidas por um utilizador, é necessário um formulário. É a baliza <form> nas linhas 18 e 31 que o delimita.

A baliza HTML <form> foi apresentada no parágrafo 2.5.2.1. Recorde-se as suas características:

formulário

<form method="post" action="FormulairePost.aspx">
tag HTML
<form name="..." method="..." action="...">...</form>
atributos
name="frmexemple": nome do formulário - opcional
method="..." : método utilizado pelo navegador para enviar ao servidor Web os valores recolhidos no formulário
action="..." : URL para onde serão enviados os valores recolhidos no formulário.
Um formulário Web é delimitado pelas balizas <form>...</form>. O formulário pode ter um nome (name="xx"). É o caso de todos os controlos que se podem encontrar num formulário. O objetivo de um formulário é recolher as informações fornecidas pelo utilizador através do teclado/rato e enviá-las para um URL de servidor Web. Qual? Aquele referenciado no atributo action="URL". Se este atributo estiver ausente, as informações serão enviadas para o URL do documento no qual o formulário se encontra. Um cliente Web pode utilizar dois métodos diferentes, denominados POST e GET, para enviar dados para um servidor Web. O atributo method="méthode", com method igual a GET ou POST, da baliza <form> indica ao navegador 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: verifica-se que os valores do formulário serão enviados para o URL [/First/Action06] através de um comando HTTP POST;
  • linha 30: um formulário deve ter um botão do tipo [submit]. É este botão que aciona o envio dos valores introduzidos para o URL, especificado pelo atributo [action] da baliza <form>.

O que é que o navegador irá transmitir exatamente quando o utilizador clicar no botão [Valider]? Isto foi explicado no parágrafo 2.5.3.1. Recorde-se 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="um"/>1
<input type="checkbox" name="C2" value="dois" checked="checked"/>2
<input type="checkbox" name="C3" value="três"/>3
C1=um
C2=dois
- valores dos atributos value das caixas de seleção marcadas pelo utilizador
<input type="text" name="txtSaisie" size="20" value="algumas palavras"/>
txtSaisida=programação+Web
- texto digitado pelo utilizador no campo de introdução. Os espaços foram substituídos pelo sinal +
<input type="password" name="txtMdp" size="20" value="unMotDePasse"/>
txtMdp=istoé-secreto
- texto digitado pelo utilizador no campo de introdução
<textarea rows="2" name="areaSaisie" cols="20">
linha1
linha 2
linha 3
</textarea>
areaSaisie=os+fundamentos+da%0D%0A
programação+Web
- texto digitado pelo utilizador no campo de introdução. %OD%OA é o marcador de fim de linha. Os espaços foram substituídos pelo sinal +
<select size="1" name="cmbValeurs">
<option value='1'>opção1</option>
<option selected="selected" value='2'>opção2</option>
<option value='3'>opção 3</option>
</select>
cmbValores=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'>lista1</option>
<option value='2'>lista2</option>
<option selected="selected" value='3'>lista3</option>
<option value='4'>lista4</option>
<option value='5'>lista5</option>
</select>
lst2=1
lst2=3
- atributos [value] dos elementos selecionados pelo utilizador
<input type="submit" value="Enviar" name="cmdRenvoyer"/>
 
cmdRenvoyer=Enviar
- nome e atributo value do botão utilizado para enviar os dados do formulário para o servidor
<input type="hidden" name="secret" value="uneValeur"/>
 
secret=umValor
- 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 da seguinte forma:

personneId=2&valider=Valider

Os nomes dos parâmetros correspondem aos atributos [name] das balizas abrangidas pelo POST. Sem este atributo, as balizas não enviam qualquer 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. Neste caso, esta informação não nos interessa. Por vezes, os formulários têm vários botões do tipo [submit]. Nesse caso, é importante saber qual o botão em que se clicou. Por isso, atribuir-se-á o atributo [name] aos diferentes botões.

A baliza <select> é composta por uma sequência de balizas <option>:


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

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

A cadeia

personneId=2&valider=Valider

será publicada na seguinte URL [/First/Action06]:


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

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


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

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

  • A ação [Action06] da linha 3 gere uma ação POST (linha 2);
  • A ação [Action06] da linha c gere uma ação GET (linha b).

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

personneId=2&valider=Valider

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


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

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

A ação [Action06] recebe este modelo e transmite-o tal como está para a vista [Action06Post] (linha 5 da ação) seguinte:


@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], seleciona-se a terceira pessoa de [Id], igual a 3. Em [2], envia-se o formulário. Em [3], os valores recebidos. Em [4,5], verifica-se que o mesmo URL foi chamado, um por um GET [4], e a outra por um POST e um [5]. Isto não é visível no URL.

Na vista apresentada a seguir ao POST, poder-se-ia preferir os nom e prénom da pessoa selecionada, em vez do seu número. É, portanto, necessário atualizar a visualização do 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. Vamos seguir o modelo analisado no parágrafo 4.10, que permite incluir os dados de âmbito [Application] e [Session] no modelo da ação.

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


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

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

  

using System.Web.Mvc;

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

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


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

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

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


    // Ação07-GET
    [HttpGet]
    public ViewResult Action07(SessionModel session)
    {
      ViewModel05 modèleVue = new ViewModel05();
      session.Personnes= modèleVue.Personnes;
      return View("Action07Get", modèleVue);
}
  • linha 3: a ação recupera um tipo [SessionModel], ou seja, o dado de âmbito [Session] associado à chave [data];
  • linha 5: cria-se o modelo da vista;
  • linha 6: insere-se na sessão a tabela de pessoas. Iremos precisar dela na consulta seguinte, a do POST. O protocolo HTTP é um protocolo sem estado. É necessário utilizar uma sessão para dispor de memória entre as consultas. Uma sessão é específica de um utilizador e é gerida pelo servidor web;
  • linha 7: é apresentada a vista [Action07Get.cshtml]. É 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êntica à vista [Action06Get.cshtml] já analisada. A principal diferença está na linha 7: a URL para a qual serão enviados os valores do formulário. Estes serão processados pela ação [Action07] seguinte:


    // Ação07-POST
    [HttpPost]
    public ViewResult Action07(SessionModel session, ActionModel06 modèle)
    {
      Personne2 personne = session.Personnes.Where(p => p.Id == modèle.PersonneId).First<Personne2>();
      string strPersonne = string.Format("{0} {1}", personne.Prénom, personne.Nom);
      return View("Action07Post", (object)strPersonne);
}
  • linha 3: os valores enviados são encapsulados no modelo de ação [ActionModel06], já utilizado anteriormente (abaixo):

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

    [Required(ErrorMessage = "Le paramètre [valider] est requis")]
    public string Valider { get; set; }
  }
}
  • linha 3: o primeiro parâmetro é o dado de âmbito [Session] associado à chave [data];
  • linha 5: uma consulta LINQ recupera a pessoa com o [Id] que foi publicado;
  • linha 6: constrói-se a cadeia de caracteres que deve ser apresentada pela vista [Action07Post] (linha 8);
  • linha 7: para chamar o construtor correto [View], é necessário converter o tipo [string] 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 cadeia de caracteres é apresentada.

Eis um exemplo de execução:

5.6. Formulário – um exemplo completo

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

1
 

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. Um esquema clássico.

O modelo da vista [1] acima será uma instância da classe [ViewModel08]. Esta classe será simultaneamente:

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

5.6.1. O modelo de âmbito [Application]

Vamos supor que os elementos apresentados pelos botões de opção, pelas caixas de seleção e pelas várias listas são dados do âmbito [Application]. Um caso frequente. Estas informações provêm de um ficheiro de configuração ou de uma base de dados utilizados no arranque da aplicação, no método [Application_Start] de [Global.asax]. Este método evolui da seguinte forma:


    protected void Application_Start()
    {
....

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

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

A classe [ApplicationModel] serve para encapsular todos os dados do âmbito [Application]. Aqui, irá encapsular os dados que o formulário deve apresentar:


namespace Exemple_03.Models
{
  public class ApplicationModel
  {
    // as coleções a apresentar no formulário
    public Item[] RadioButtonFieldItems { get; set; }
    public Item[] CheckBoxesFieldItems { get; set; }
    public Item[] DropDownListFieldItems { get; set; }
    public Item[] SimpleChoiceListFieldItems { get; set; }
    public Item[] MultipleChoiceListFieldItems { get; set; }

    // inicialização dos campos e coleções
    public ApplicationModel()
    {
      RadioButtonFieldItems = new Item[]{
        new Item {Value="1",Label="oui"},
        new Item {Value="2", Label="non"}
      };
      CheckBoxesFieldItems = new Item[]{
        new Item {Value="1",Label="1"},
        new Item {Value="2", Label="2"},
        new Item {Value="3", Label="3"}
      };
      DropDownListFieldItems = new Item[]{
        new Item {Value="1",Label="choix1"},
        new Item {Value="2", Label="choix2"},
        new Item {Value="3", Label="choix3"}
      };
      SimpleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
      MultipleChoiceListFieldItems = new Item[]{
        new Item {Value="1",Label="liste1"},
        new Item {Value="2", Label="liste2"},
        new Item {Value="3", Label="liste3"},
        new Item {Value="4", Label="liste4"},
        new Item {Value="5", Label="liste5"}
      };
    }
    // elemento das coleções
    public class Item
    {
      public string Label { get; set; }
      public string Value { get; set; }
    }

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

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

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

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


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

5.6.3. O modelo da vista [Formulaire]

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
  {
    // os campos de introdução de dados
    public string RadioButtonField { get; set; }
    public string[] CheckBoxesField { get; set; }
    public string TextField { get; set; }
    public string PasswordField { get; set; }
    public string TextAreaField { get; set; }
    public string DropDownListField { get; set; }
    public string SimpleChoiceListField { get; set; }
    public string[] MultipleChoiceListField { get; set; }

    // as coleções a apresentar no formulário
    public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
    public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
    public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
    public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
    public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }

    // construtores
    public ViewModel08()
    {
    }

    public ViewModel08(ApplicationModel application)
    {
      // inicialização das coleções
      RadioButtonFieldItems = application.RadioButtonFieldItems;
      CheckBoxesFieldItems = application.CheckBoxesFieldItems;
      DropDownListFieldItems = application.DropDownListFieldItems;
      SimpleChoiceListFieldItems = application.SimpleChoiceListFieldItems;
      MultipleChoiceListFieldItems = application.MultipleChoiceListFieldItems;
      // inicialização dos campos
      RadioButtonField = "2";
      CheckBoxesField = new string[] { "2" };
      TextField = "quelques mots";
      PasswordField = "secret";
      TextAreaField = "ligne1\nligne2";
      DropDownListField = "2";
      SimpleChoiceListField = "3";
      MultipleChoiceListField = new string[] { "1", "3" };
    }
  }
}
  • num formulário, existem dois tipos de elementos: aqueles que são apresentados e aqueles que são objeto de introdução de dados;
  • as linhas 20-24 definem os elementos a exibir. Trata-se das diferentes coleções do formulário. Estas encontram-se no modelo da aplicação (linhas 34-38);
  • linhas 10-17: definem os campos de introdução de dados do formulário;
  • linha 10: [RadioButtonField] irá recuperar o valor introduzido nas linhas seguintes do formulário:

        <!-- os botões de opção -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
<input type="radio" name="RadioButtonField" value="1" />oui              
<input type="radio" name="RadioButtonField" value="2" checked=&quot;checked&quot;/>non              
          </td>
</tr>

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


param1=val1&RadioButtonField=2&param2=val2

se o utilizador tiver marcado a opção denominada [non]. 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:

        <!-- as caixas de seleção -->
        <tr>
          <td>Cases à cocher</td>
          <td>
<input type="checkbox" name="CheckBoxesField" value="1" />1              
<input type="checkbox" name="CheckBoxesField" value="2" checked=&quot;checked&quot;/>2              
<input type="checkbox" name="CheckBoxesField" value="3" />3              
</td>

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


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

se o utilizador tiver marcado as caixas de seleção denominadas [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 e não um valor simples. Se nenhuma caixa estiver marcada, o parâmetro [CheckBoxesField] estará ausente da cadeia enviada e a propriedade com o mesmo nome do modelo não será inicializada. Isto pode ser incómodo, como veremos.

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

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

Na linha 5, o atributo [name] do campo de introdução de dados é o nome da propriedade que vai ser inicializada. Nos dados enviados, encontrar-se-á uma cadeia de caracteres com o seguinte formato:


param1=val1&TextField=abcdef&param2=val2

se o utilizador tiver introduzido [abcdef] no campo de introdução.

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

        <!-- o campo de introdução de palavra-passe -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input type="password" name="PasswordField" value="secret" size="30" />
          </td>
</tr>

Na linha 5, o atributo [name] do campo de introdução é o nome da propriedade que vai ser inicializada. Nos dados enviados, encontrar-se-á uma cadeia de caracteres com o seguinte formato:


param1=val1&PasswordField=abcdef&param2=val2

se o utilizador tiver introduzido [abcdef] no campo de introdução.

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

        <!-- o campo de introdução de texto multilinha -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            <textarea name="TextAreaField" cols="40" rows="3">ligne1
ligne2</textarea>
          </td>
</tr>

Na linha 5, o atributo [name] do campo de introdução é o nome da propriedade que vai ser inicializada. Nos dados enviados, encontrar-se-á uma cadeia de caracteres com o seguinte formato:


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

se o utilizador tiver introduzido [abcdef] seguido de um salto de linha e de [ijk] no campo de introdução.

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

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

Na linha 5, o atributo [name] da baliza <select> é o nome da propriedade que vai ser inicializada. Nos dados enviados, encontrar-se-á uma cadeia de caracteres com o seguinte formato:


param1=val1&DropDownListField=1&param2=val2

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

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

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

            </select>
</tr>

Na linha 5, o atributo [name] da baliza <select> é o nome da propriedade que vai ser inicializada. É o atributo [size="3"] que faz com que não haja uma lista suspensa. Nos dados enviados, encontrar-se-á uma cadeia de caracteres com o 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 cadeia enviada se nenhum elemento tiver sido selecionado.

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

        <!-- a lista de escolha múltipla -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
            <select name="MultipleChoiceListField" size="3" multiple="multiple">
<option value="1" selected=&quot;selected&quot;>liste1</option>
<option value="2" >liste2</option>
<option value="3" selected=&quot;selected&quot;>liste3</option>
<option value="4" >liste4</option>
<option value="5" >liste5</option>
            </select>
</tr>

Na linha 5, o atributo [name] da tag <select> é o nome da propriedade que será inicializada. É o atributo [size="3"] que faz com que não haja uma lista suspensa e o atributo [multiple] que permite ao utilizador selecionar vários elementos mantendo premida a tecla [Ctrl]. Nos dados enviados, encontrar-se-á uma cadeia de caracteres com o seguinte formato:


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

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

Os diferentes campos de introdução de dados apresentados anteriormente irão receber os valores enviados pelo formulário. Também é possível inicializá-los antes de enviar o formulário. Foi isso que se fez aqui:


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

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

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

Vamos partir do princípio de que ocorreu um POST e que pretendemos reenviar o formulário tal como foi preenchido. É precisamente isto que acontece quando se reenvia ao utilizador um formulário com erros. Este é reenviado tal como foi preenchido.

5.6.4. A vista [Formulaire]

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>
        <!-- botões de opção -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
            {
              string strChecked = item.Value == @Model.RadioButtonField ? "checked=\"checked\"" : "";
              <input type="radio" name="RadioButtonField" value="@item.Value" @strChecked/>@item.Label
              <text/>
            }
          </td>
        </tr>
...
      </tbody>
    </table>
    <input type="submit" value="Valider" />
  </form>
</body>
</html>
  • linha 1: [ViewModel08] é o modelo do formulário;
  • linha 12: a baliza <form> do formulário. Este será enviado através do método [POST] (atributo method) para o URL [/First/Action08Post] (atributo action);
  • linha 33: o botão do tipo [submit] que serve para enviar o formulário;
  • linhas 22-27: apresentam os botões de opção:
  
  • linha 22: percorre-se a coleção apresentada pelo botão de opção;
  • linha 24: o botão cujo atributo [value] tem o valor da propriedade [RadioButtonField] deve estar selecionado. Para tal, deve ter o atributo [checked="checked"];
  • linha 25: geração da baliza <input type="radio"> com o valor [@item.Value] e o texto [@item.Label];
  • linha 26: a baliza <text/> não é uma baliza HTML reconhecida. Está presente para [Razor]. Ao encontrá-la, [Razor] irá gerar um salto de linha. Isto não tem impacto no formulário apresentado, mas tem impacto no código HTML gerado. As balizas <input type="radio"> ficam, assim, em duas linhas diferentes, em vez de estarem na mesma linha. Isto torna o código mais legível quando, no navegador, se solicita a visualização do código-fonte da página apresentada;

Analisamos agora os restantes elementos da vista:


        <!-- as caixas de seleção -->
        <tr>
          <td>Cases à cocher</td>
          <td>
            @{
              foreach (ApplicationModel.Item item in @Model.CheckBoxesFieldItems)
              {
                string strChecked = @Model.CheckBoxesField.Contains(item.Value) ? "checked=\"checked\"" : "";
              <input type="checkbox" name="CheckBoxesField" value="@item.Value" @strChecked/>@item.Label
              <text/>
              }
            }
</td>
  • linha 6: percorremos a coleção apresentada pelas caixas de seleção;
  • linha 8: uma célula que tenha como atributo [value] um dos valores da propriedade [CheckBoxesField] deve estar assinalada. Para tal, deve ter o atributo [checked="checked"]. Utiliza-se uma expressão LINQ que permite verificar se um valor está contido numa tabela;
  • linha 25: geração da baliza <input type="checkbox"> com o valor [@item.Value] e o rótulo [@item.Label];

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

        <!-- a lista suspensa -->
        <tr>
          <td>Liste déroulante</td>
          <td>
            <select name="DropDownListField">
              @{
                foreach (ApplicationModel.Item item in @Model.DropDownListFieldItems)
                {
                  string strChecked = item.Value == @Model.DropDownListField ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>
  • linha 7: percorre-se a coleção apresentada pela lista suspensa;
  • linha 9: deve então ser selecionada uma opção cujo atributo [value] tenha o valor da propriedade [DropDownListField]. Para tal, essa opção deve ter o atributo [selected="selected"];
  • linha 25: geração da baliza <option value="valeur">libellé</option> com o valor [@item.Value] e o rótulo [@item.Label];

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

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


        <!-- a lista de seleção múltipla -->
        <tr>
          <td>Liste à choix multiple</td>
          <td>
            <select name="MultipleChoiceListField" size="3" multiple="multiple">
              @{
                foreach (ApplicationModel.Item item in @Model.MultipleChoiceListFieldItems)
                {
                  string strChecked = @Model.MultipleChoiceListField.Contains(item.Value) ? "selected=\"selected\"" : "";
                <option value="@item.Value" @strChecked>@item.Label</option>
                }
              }
            </select>
</tr>
  • linha 7: percorre-se a coleção apresentada pela lista;
  • linha 9: deve ser selecionada uma opção cujo atributo [value] tenha um dos valores da propriedade [MultipleChoiceListField]. Para tal, deve possuir o atributo [selected="selected"]. Utiliza-se uma expressão LINQ que permite determinar se um valor está contido numa matriz;
  • linha 10: geração da baliza <option value="valeur">libellé</option> com o valor [@item.Value] e o rótulo [@item.Label];

5.6.5. Processamento do POST do formulário

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


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

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


    // Ação08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      return View("Formulaire", modèle);
}
  • linha 3: o modelo da aplicação é passado como parâmetro, assim como os valores enviados. Estes estão disponíveis num tipo [FormCollection]. O valor do parâmetro [RadioButtonField] enviado é obtido através da expressão posted[" RadioButtonField"]. O resultado é uma cadeia de caracteres ou o ponteiro null. Se escrevermos posted[" CheckBoxesField"], obteremos um tabuleiro de cadeias de caracteres ou o ponteiro null;
  • por que não escrever:

public ViewResult Action08Post(ApplicationModel application, ViewModel08 posted)

Há duas razões:

  • a primeira é que o framework irá instanciar o modelo [ViewModel08] com o construtor sem parâmetros, o que terá como efeito não inicializar as coleções do modelo;
  • a segunda é que queremos controlar o que vai para o modelo. Sabemos que existem quatro fontes possíveis para o modelo: os parâmetros de um GET, de um POST, da rota utilizada e os de um ficheiro uploadé. Aqui, queremos inicializar o modelo apenas com os valores enviados.
  • linha 6: instanciamos o modelo utilizando o construtor correto;
  • linha 7: inicializa-se o modelo com os valores enviados. Após esta operação, o modelo corresponde aos dados introduzidos pelo utilizador;
  • linha 8: voltamos a apresentar o formulário. O utilizador irá encontrá-lo tal como foi preenchido.

Vejamos um exemplo:

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

5.6.6. Tratamento de anomalias do POST

Já referimos que, se nenhum valor fosse assinalado ou selecionado nos campos [CheckBoxesField, SimpleChoiceListField, MultipleChoiceListField], os parâmetros correspondentes não fariam parte da cadeia enviada e, por conseguinte, as propriedades com os mesmos nomes no modelo não seriam 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 não há nenhuma caixa marcada, o parâmetro [CheckBoxesField] não faz parte dos valores enviados;
  • a ação [Action08Post] procede 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. Ora, o construtor utilizado atribui a tabela ["2"] à propriedade [CheckBoxesField];
  • linha 6: os valores lançados são registados no modelo. Como o parâmetro [CheckBoxesField] não faz parte dos valores enviados, a propriedade com o mesmo nome não é atribuída. Mantém-se, portanto, com o seu valor ["2"], o que faz com que, na visualização, a caixa n.º 2 esteja 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]:


// Ação08-POST
    [HttpPost]
    public ViewResult Action08Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel08 modèle = new ViewModel08(application);
      TryUpdateModel(modèle,posted);
      // processamento de valores não lançados
      if (posted["CheckBoxesField"] == null)
      {
        modèle.CheckBoxesField = new string[] { };
      }
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // Exibição do formulário
      return View("Formulaire", modèle);
    }
  • linhas 9-20: verifica-se se determinados parâmetros foram ou não enviados. Se não for o caso, inicializam-se com o valor que corresponde à ausência de introdução de dados pelo utilizador. O teste não foi efetuado para a lista suspensa, que tem sempre um elemento selecionado, o que não acontece com as outras listas.

Convidamos o leitor a testar 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 [Formulaire2.cshtml] que irá gerar um formulário idêntico ao anterior:

Voltemos ao código utilizado para gerar a lista suspensa do formulário:


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

Este código apresenta duas desvantagens:

  • o mais importante é que se perde de vista a natureza do componente — neste caso, uma lista suspensa — devido à complexidade do código;
  • linha 5: se nos enganarmos no nome da propriedade do modelo a utilizar como atributo [name], só nos aperceberemos disso na execução.

ASP.NET MVC oferece métodos especializados denominados [HTML Helpers] que, tal como o próprio nome indica, visam facilitar a geração do HTML, nomeadamente para formulários. Com estas classes, a lista suspensa anterior escreve-se da seguinte forma:


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

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


        <!-- a lista suspensa -->
        <tr>
          <td>Liste déroulante</td>
          <td><select id="DropDownListField" name="DropDownListField"><option value="1">choix1</option>
<option selected="selected" value="2">choix2</option>
<option value="3">choix3</option>
</select></td>
</tr>
  • linha 4: o atributo [name] está correto;
  • linhas 4-6: as opções foram geradas corretamente e a opção correta foi selecionada.

Voltemos ao código que gerou estas linhas HTML:


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

Vejamos agora os outros métodos que podemos utilizar:

Botões de opção

O novo código é o seguinte:


        <!-- botões de opção -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
            @{
    foreach (ApplicationModel.Item item in @Model.RadioButtonFieldItems)
    {
              @Html.RadioButtonFor(m => m.RadioButtonField, @item.Value)@item.Label
              <text/>
    }
            }
          </td>
</tr>

O código HTML gerado é o seguinte:


        <!-- botões de opção -->
        <tr>
          <td>Etes-vous marié(e)</td>
          <td>
<input id="RadioButtonField" name="RadioButtonField" type="radio" value="1" />oui              
<input checked="checked" id="RadioButtonField" name="RadioButtonField" type="radio" value="2" />non              
          </td>
</tr>

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

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

Caixas de seleção

O código evolui da seguinte forma:


        <!-- as caixas de seleção -->
        <tr>
          <td>Cases à cocher</td>
          <td>
            @{
              @Html.CheckBoxFor(m=>m.CheckBoxField1) @Model.CheckBoxesFieldItems[0].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField2) @Model.CheckBoxesFieldItems[1].Label
              @Html.CheckBoxFor(m=>m.CheckBoxField3) @Model.CheckBoxesFieldItems[2].Label
            }
</td>

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

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

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


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

Campo de introdução de texto de uma linha

O novo código é o seguinte:


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

O código HTML gerado é o seguinte:


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

O método utilizado é o seguinte:


@Html.TextBoxFor(m => m.TextField, new { size = "30" })
  • o primeiro parâmetro especifica a propriedade do modelo associado ao campo de introdução de dados. O nome da propriedade será utilizado nos atributos [name] e [id] da baliza <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 baliza HTML gerada, neste caso o atributo [size].

Campo de introdução de uma palavra-passe

O novo código é o seguinte:


        <!-- o campo de introdução de uma palavra-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:


        <!-- o campo de introdução de uma palavra-passe -->
        <tr>
          <td>Mot de passe</td>
          <td>
            <input id="PasswordField" name="PasswordField" size="15" type="password" />
          </td>
</tr>

O método utilizado é o seguinte:


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

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

Campo de introdução de texto com várias linhas

O novo código é o seguinte:


        <!-- o campo de introdução de texto multilinha -->
        <tr>
          <td>Boîte de saisie</td>
          <td>
            @Html.TextAreaFor(m => m.TextAreaField, new { cols = "30", rows = "5" })
          </td>
</tr>

O código HTML gerado é o seguinte:


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

O método utilizado é o seguinte:


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

O funcionamento é análogo ao do método [Html.TexBoxFor].

Lista de escolha única

O novo código é o seguinte:


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

e o código HTML gerado é o seguinte:


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

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

A lista de escolha múltipla

O novo código é o seguinte:


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

e o código HTML gerado é o seguinte:


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

O método


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

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

A baliza <form> também pode ser gerada com um método:


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

O código HTML gerado é o seguinte:


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

O método


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

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

5.7.2. As ações e o modelo

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


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

A vista apresentada na linha 6 é [Formulaire2], associada ao seguinte modelo [ViewModel09]:


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

namespace Exemple_03.Models
{
  public class ViewModel09
  {
    // os campos de introdução de dados
    public string RadioButtonField { get; set; }
    public bool CheckBoxField1 { get; set; }
    public bool CheckBoxField2 { get; set; }
    public bool CheckBoxField3 { get; set; }
    public string TextField { get; set; }
    public string PasswordField { get; set; }
    public string TextAreaField { get; set; }
    public string DropDownListField { get; set; }
    public string SimpleChoiceListField { get; set; }
    public string[] MultipleChoiceListField { get; set; }

    // as coleções a apresentar no formulário
    public ApplicationModel.Item[] RadioButtonFieldItems { get; set; }
    public ApplicationModel.Item[] CheckBoxesFieldItems { get; set; }
    public ApplicationModel.Item[] DropDownListFieldItems { get; set; }
    public ApplicationModel.Item[] SimpleChoiceListFieldItems { get; set; }
    public ApplicationModel.Item[] MultipleChoiceListFieldItems { get; set; }

    // fabricantes
    public ViewModel09()
    {
    }

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

O [ViewModel09] difere do [ViewModel08] na forma como gere as caixas de seleção. Em vez de ter uma tabela 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 ação [Action09Post] seguinte:


    // Ação09-POST
    [HttpPost]
    public ViewResult Action09Post(ApplicationModel application, FormCollection posted)
    {
      ViewBag.info = string.Format("Contrôleur={0}, Action={1}", RouteData.Values["controller"], RouteData.Values["action"]);
      ViewModel09 modèle = new ViewModel09(application);
      TryUpdateModel(modèle, posted);
      // processamento de valores não enviados
      if (posted["SimpleChoiceListField"] == null)
      {
        modèle.SimpleChoiceListField = "";
      }
      if (posted["MultipleChoiceListField"] == null)
      {
        modèle.MultipleChoiceListField = new string[] { };
      }
      // exibição do formulário
      return View("Formulaire2", modèle);
}

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

  • linha 18: é utilizada a vista [Formulaire2] em vez da vista [Formulaire];
  • já não existe a gestão das caixas de seleção que não foram marcadas. Agora, isso é gerido corretamente pelo método [Html.CheckBoxFor].

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

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

Consideremos o seguinte modelo de vista [ViewModel10]:


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    // construtor
    public ViewModel10()
    {
      Text = "tra la la";
      MultiLineText = "ligne1\nligne2";
      Number = 4;
      Decimal = 10.2;
      Tel = "0617181920";
      Date = DateTime.Now;
      Time = DateTime.Now;
      HiddenInput = "caché";
      Boolean = true;
      Email = "x@y.z";
      Url = "http://istia.univ-angers.fr";
      Password = "mdp";
      Currency = 4.2;
      CreditCard = "0123456789012345";
    }
  }
}

Os metadados são constituídos pelas etiquetas [Display, DataType, UIHint].

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


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

Na linha 5 acima, solicita-se à vista predefinida da ação [/First/Action10Get.cshtml ] que apresente 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 uma das propriedades do modelo, utilizamos o método:

  • Html.LabelFor para apresentar o valor do metadado [DisplayName] da propriedade;
  • Html.EditorFor para gerar a baliza HTML de introdução do valor da propriedade. Este método irá utilizar os metadados [DataType] e [UIHint] da propriedade;
  • Html.DisplayFor para apresentar o valor da propriedade de acordo com o formato indicado pelos metadados [DataType].

Eis um exemplo de execução com o navegador Chrome:

Image

Dependendo do navegador utilizado, podem ser apresentadas páginas diferentes. Com efeito, a visualização gerada utiliza as novas balizas introduzidas pela versão 5 do HTML, denominada 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 é processado pela seguinte ação [Action10Post]:


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

Vamos agora analisar 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 introdução de dados.

5.8.2. Propriedade [Text]

Definição


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

Visão


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

Imagem

 

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

public string Text { get; set; }

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


[Display(Name="Text")]

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

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

[DataType(DataType.Text)]
  • o método [Html.DisplayFor] gerou o texto da linha 4. Este é o valor da propriedade de 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; }

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="MultiLineText">TextArea</label></td>
        <td><textarea class="text-box multi-line" id="MultiLineText" name="MultiLineText">
ligne1
ligne2</textarea></td>
        <td>ligne1
ligne2</td>
</tr>

Comentários

  • O método [Html.EditorFor] gerou a baliza <textarea> da linha 3. Note-se que esta possui um atributo [class] que associa a classe CSS [text-box multi-line] à baliza. Os atributos [id] e [name] têm como valor o nome [MultiLineText] da propriedade «parâmetro» do método [Html.EditorFor]. É sempre assim. Não voltaremos a referir isto. A baliza gerada é <textarea> devido aos metadados

[DataType(DataType.MultilineText)]

, que especificava que a propriedade era um texto multilinha.

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

5.8.4. Propriedade [Number]

Definição


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

Visão


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

Imagem

 

HTML gerado


<tr>
        <td><label for="Number">Number</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Number doit être un nombre." data-val-required="Le champ Number est requis." id="Number" name="Number" type="number" value="4" /></td>
        <td>4</td>
      </tr>

Comentários

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

Validação

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

Introduz-se um número errado e valida-se:

 

No exemplo acima, a validação ocorreu do lado do cliente. O formulário não será enviado enquanto o erro não for corrigido.

Outro exemplo: não se introduz nada:

No [1] acima, o [Action10Post] indica um erro. Talvez nos lembremos de que já tínhamos obtido este comportamento ao utilizar o atributo [Required] na propriedade a controlar (ver página 69), 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; }

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Decimal">Decimal</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Decimal doit être un nombre." data-val-required="Le champ Decimal est requis." id="Decimal" name="Decimal" type="text" value="10,20" /></td>
        <td>10,20</td>
</tr>

Comentários

  • O método [Html.EditorFor] gerou a baliza <input> da linha 3 com um atributo [type] do tipo [text]. Os restantes 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 nos dois métodos [Html.EditorFor] e [Html.DisplayFor]

Validação

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

5.8.6. Propriedade [Tel]

Definição


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

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Tel">Tel</label></td>
        <td><input class="text-box single-line" id="Tel" name="Tel" type="tel" value="0617181920" /></td>
        <td>0617181920</td>
</tr>

Comentários

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

[DataType(DataType.PhoneNumber)]

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

Validação

Não é sinalizado qualquer erro de validação, nem do lado do cliente nem do lado do servidor. É possível introduzir qualquer valor.

5.8.7. Propriedade [Date]

Definição


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

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Date">Date</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-date="Le champ Date doit être une date." data-val-required="Le champ Date est requis." id="Date" name="Date" type="date" value="11/10/2013" /></td>
        <td>11/10/2013</td>
</tr>

Comentários

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

[DataType(DataType.Date)]

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

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

Validação

É sinalizada uma data inválida no lado do cliente [1], impedindo o envio do POST do formulário para o servidor.

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

5.8.8. Propriedade [Time]

Definição


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

Vista


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

Imagem

 

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 baliza <input> da linha 3 com um atributo [type] do tipo [time]. Este valor foi gerado devido aos metadados:

[DataType(DataType.Time)]

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

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

Validação

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

 

5.8.9. Propriedade [HiddenInput]

Definição


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

Vista


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

Imagem

 

HTML gerado


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

Comentários

  • O método [Html.EditorFor] gerou a baliza <input> da linha 3 com um atributo [type] do tipo [hidden], ou seja, um campo oculto (mas, mesmo assim, 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; }

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Boolean">Boolean</label></td>
        <td><input checked="checked" class="check-box" data-val="true" data-val-required="Le champ Boolean est requis." id="Boolean" name="Boolean" type="checkbox" value="true" /><input name="Boolean" type="hidden" value="false" /></td>
        <td><input checked="checked" class="check-box" disabled="disabled" type="checkbox" /></td>
</tr>

Comentários

  • O método [Html.EditorFor] gerou a baliza <input> da linha 3 com um atributo [type] do tipo [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; }

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Email">Email</label></td>
        <td><input class="text-box single-line" id="Email" name="Email" type="email" value="x@y.z" /></td>
        <td><a href="mailto:x@y.z">x@y.z</a></td>
</tr>

Comentários

  • O método [Html.EditorFor] gerou a baliza <input> da 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

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

A ausência de entrada não provoca qualquer erro.

5.8.12. Propriedade [Url]

Definição


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

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Url">Url</label></td>
        <td><input class="text-box single-line" id="Url" name="Url" type="url" value="http://istia.univ-angers.fr" /></td>
        <td><a href="http://istia.univ-angers.fr">http://istia.univ-angers.fr</a></td>
</tr>

Comentários

  • O método [Html.EditorFor] gerou a baliza <input> da 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

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

A ausência de entrada não provoca qualquer erro.

5.8.13. Propriedade [Password]

Definição


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

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Password">Password</label></td>
        <td><input class="text-box single-line password" id="Password" name="Password" type="password" value="mdp" /></td>
        <td>mdp</td>
</tr>

Comentários

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

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

5.8.14. Propriedade [Currency]

Definição


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

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="Currency">Currency</label></td>
        <td><input class="text-box single-line" data-val="true" data-val-number="Le champ Currency doit être un nombre." data-val-required="Le champ Currency est requis." id="Currency" name="Currency" type="text" value="4,2" /></td>
        <td>4,20 €</td>
</tr>

Comentários

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

[DataType(DataType.Currency)]

Validação

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

5.8.15. Propriedade [CreditCard]

Definição


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

Vista


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

Imagem

 

HTML gerado


      <tr>
        <td><label for="CreditCard">CreditCard</label></td>
        <td><input class="text-box single-line" id="CreditCard" name="CreditCard" type="text" value="0123456789012345" /></td>
        <td>0123456789012345</td>
</tr>

Comentários

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

[DataType(DataType.CreditCard)]

Validação

Não é efetuada qualquer verificação, nem do lado do cliente, nem do lado do servidor.

5.9. Validação de um formulário

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

  • como sinalizar os erros de introdução de dados ao utilizador;
  • efectuar as validações tanto do lado do cliente como do lado do servidor, de modo a sinalizar mais rapidamente os erros ao utilizador.

5.9.1. Validação do lado do servidor

Consideremos o seguinte modelo:


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

namespace Exemple_03.Models
{
  public class ViewModel11 : IValidatableObject
  {

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

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

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

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

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

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

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

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

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

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

    // validação
    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
      List<ValidationResult> résultats = new List<ValidationResult>();
      // Data 1
      if (Date1.Date <= DateTime.Now.Date)
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Date1" }));
      }
      // E-mail 1
      try
      {
        new MailAddress(Email1);
      }
      catch
      {
        résultats.Add(new ValidationResult("Information incorrecte", new string[] { "Email1" }));
      }
      // apresenta a lista de erros
      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: é feita referência à folha de estilo [Site.css]. Por predefinição, esta contém classes utilizadas para destacar os erros de preenchimento do formulário;
  • linhas 18-25: uma tabela com três colunas:
    • a coluna 1 apresenta texto com o método [Html.LabelFor],
    • a coluna 2 apresenta os dados introduzidos com o método [Html.EditorFor],
    • a coluna 3 apresenta o eventual erro de preenchimento com o método [Html.ValidationMessageFor];

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


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

A ação [Action11Post] serve para voltar a apresentar o formulário com os eventuais erros de introdução de dados:


    // Ação 11-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 então ocorrer erros. A cada propriedade P incorreta do modelo está associada uma mensagem de erro. É essa mensagem que permite obter o método [Html.ValidationMessageFor] do formulário.

Eis um exemplo de execução:

Eis outro exemplo:

 

Note-se que ambas as datas estão erradas (hoje é 11/10/2013), mas que os erros não são assinalados. Estes erros são detetados pelo método [Validate] do modelo:


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

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

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

 

5.9.2. Validação do lado do cliente

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

Retomamos o modelo [ViewModel11] anterior, mas agora apresentamo-lo com 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, adapte a versão de jQuery à que tem com a sua versão do Visual Studio (ver abaixo).

A validação do lado do cliente requer a presença da linha 3 abaixo 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] do ficheiro [Web.config];

A vista [Action12Get] é idêntica à vista [Action11Get] anterior, com exceção das linhas 13-15. Estas incluem na vista os scripts JavaScript necessários para a validação do lado do cliente. Estes scripts encontram-se na pasta [Scripts] do projeto:

Cada script tem uma versão normal ([.js]) e uma versão minimizada ([min.js]). Esta última versão é mais leve, 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]:


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

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


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

Vamos ver o que isto altera com um exemplo:

Assim que se digita um carácter em [1], a mensagem em [2] é apresentada porque o valor esperado deve ter, pelo menos, quatro caracteres. Assim, a validação é efetuada a cada novo carácter digitado. A mensagem de erro desaparece ao digitar o quarto carácter. Feito isto, vamos validar o formulário:

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

Vejamos, por exemplo, o código HTML gerado para o primeiro registo:


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

Os atributos [data-x] das balizas geradas são utilizados pelo JavaScript que incorporámos na vista. Se este estiver ausente, esses atributos são simplesmente ignorados e, nesse caso, não há validação do lado do cliente. O funcionamento é idêntico ao do exemplo anterior. Daí a designação [unobtrusive] para esta técnica.

Vamos criar as duas vistas seguintes para ilustrar a gestão de links numa vista:

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

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


@{
  Layout = null;
}

<!DOCTYPE html>

<html>
<head>
  <meta name="viewport" content="width=device-width" />
  <title>Action16Get</title>
  <script>
    function postForm() {
      // recupera-se o formulário do documento
      var form = document.forms[0];
      // envio
      form.submit();
    }
  </script>
</head>
<body>
  <h3>Navigation - page 1</h3>
  <h4>@ViewBag.info</h4>
  @using (Html.BeginForm("Action16Post", "Second"))
  {
    @Html.Label("data", "Tapez un texte")
    @Html.TextBox("data")
    <a href="javascript:postForm()">Valider</a>
  }
  <p>
    @Html.ActionLink("Page 2", "Action17Get", "Second")
  </p>
</body>
</html>
  • linha 22: uma informação inicializada pela ação que irá apresentar a vista;
  • linhas 23-28: um formulário;
  • linha 25: um rótulo para o campo [data];
  • linha 26: um campo de introdução de dados denominado [data];
  • linha 27: um link do tipo [submit]. Ao clicar nele, é executada a função JavaScript [postForm] (atributo href). Esta função está definida nas linhas 12-17;
  • linha 14: obtém-se uma referência ao primeiro formulário do documento, o da linha 23;
  • linha 16: este formulário é enviado. No final, tudo acontece como se tivéssemos clicado num botão do tipo [submit]. O formulário é enviado para o controlador e para 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(Texto, Ação, Controlador).

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


@{
  Layout = null;
}

<!DOCTYPE html>

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

As ações que geram estas vistas são as seguintes:


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

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

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

Convidamos o leitor a testar este exemplo.