4. O modelo de uma ação
Voltemos à arquitetura de um aplicativo ASP.NET MVC:
![]() |
No capítulo anterior, analisamos o processo que leva a solicitação [1] ao controlador e à ação [2a] que irão processá-la, um mecanismo chamado roteamento. Além disso, apresentamos as diferentes respostas que uma ação pode enviar ao navegador. Até o momento, apresentamos ações que não utilizavam a solicitação que lhes era enviada. Uma solicitação [1] traz consigo diversas informações que ASP.NET e MVC apresentam à ação na forma de um modelo. Não se deve confundir esse termo com o modelo M de uma visualização V [2c], que é gerado pela ação:
![]() |
- a solicitação HTTP do cliente chega como [1];
- em [2], as informações contidas na solicitação serão transformadas no modelo de ação [3] — frequentemente, mas não necessariamente, uma classe — que servirá de entrada para a ação [4];
- em [4], a ação, a partir desse modelo, irá gerar uma resposta. Esta terá dois componentes: uma vista V [6] e o modelo M dessa vista [5];
- a visualização V [6] utilizará seu modelo M [5] para gerar a resposta HTTP destinada ao cliente.
No modelo MVC, a ação [4] faz parte do C (controlador), o modelo da vista [5] é o M e a vista [6] é o V.
Este capítulo aborda os mecanismos de ligação entre as informações transportadas pela solicitação, que são, por natureza, cadeias de caracteres, e o modelo da ação, que pode ser uma classe com propriedades de diversos tipos.
4.1. Inicialização dos parâmetros da ação
Adicionamos [1] à solução existente, um novo projeto ASP.NET baseado em MVC:
![]() |
![]() |
- em [2], o nome do novo projeto;
- em [3, 4], escolhemos um projeto base ASP.NET MVC;
- em [5], o novo projeto.
Faremos do novo projeto o projeto inicial da solução.
Assim como foi feito no parágrafo 3.1, criamos um controlador chamado [First] [1]:
![]() |
Nesse controlador, criaremos a seguinte ação [Action01]:
using System.Web.Mvc;
namespace Exemple_02.Controllers
{
public class FirstController : Controller
{
// Ação01
public ContentResult Action01(string nom)
{
return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom));
}
}
}
A novidade está na linha 8: o método [Action01] possui um parâmetro. Neste capítulo, abordaremos as diferentes maneiras de inicializar os parâmetros de uma ação. O parâmetro [nom] acima é inicializado na ordem com os seguintes valores:
Request.Form["nom"] | um parâmetro denominado [nom] enviado por um comando POST |
RouteData.Values["nom"] | um elemento de URL chamado [nom] |
Request.QueryString["nom"] | um parâmetro chamado [nom] enviado por um comando GET |
Request.Files["nom"] | um arquivo enviado chamado [nom] |
Vamos examinar esses diferentes casos. Vamos solicitar diretamente no navegador o URL [/First/Action01?nom=someone]. Obtemos a seguinte resposta:
![]() |
A solicitação HTTP do navegador foi a seguinte:
- linha 1: a solicitação é um GET. O URL solicitado contém o parâmetro [nom]. No lado do servidor, a solicitação chega à ação [Action01], que possui a seguinte assinatura:
public ContentResult Action01(string nom)
Para atribuir um valor ao parâmetro nom, ASP.NET MVC tenta sucessivamente e na ordem os valores Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"]. Ele para assim que encontra um valor. O parâmetro [nom], incorporado no URL do GET, foi colocado pelo framework no Request.QueryString["nom"]. É com esse valor [someone] que o parâmetro [nom] de [Action01] será inicializado. Em seguida, o código de [Action01] é executado:
return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom), "text/plain", Encoding.UTF8);
Este código fornece a resposta enviada ao cliente:
![]() |
Observação: o mecanismo de vinculação de parâmetros não diferencia maiúsculas de minúsculas. Portanto, se nossa ação estiver definida como:
public ContentResult Action01(string NOM)
e o parâmetro passado for [?NoM=zébulon], a vinculação ocorrerá normalmente. O parâmetro [NOM] de [Action01] receberá o valor [zébulon].
Agora, vamos solicitar o mesmo URL com um POST. Para isso, usamos o aplicativo [Advanced Rest Client]:
![]() |
- no [1], o URL solicitado;
- no [2], será utilizado o comando POST;
- em [3], os parâmetros de POST.
Vamos enviar essa solicitação e verificar os logs do HTTP. A solicitação HTTP é a seguinte:
![]() |
- no [1], o POST;
- no [2], os parâmetros do POST. Tecnicamente, eles foram enviados após os cabeçalhos HTTP, logo após a linha em branco que sinaliza o fim desses cabeçalhos;
- no [3], a resposta obtida. O parâmetro [nom] do POST é recuperado corretamente. Entre os valores testados para o parâmetro nome Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], foi o primeiro que funcionou.
Agora, vamos alterar a rota padrão no [App_Start/RouteConfig]. Atualmente, essa rota é a seguinte:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Vamos alterá-la para:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{nom}",
defaults: new { controller = "Home", action = "Index", nom = UrlParameter.Optional }
);
- na linha 3, nomeamos [nom] como o terceiro elemento de uma rota;
- na linha 4, esse elemento é declarado como opcional.
Agora, vamos recompilar o aplicativo e acessar o URL [/First/Action01/zébulon] diretamente no navegador. Obtemos a seguinte resposta:
![]() |
Entre os valores testados para o parâmetro “nom”, Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], foi a segunda que funcionou.
Vamos fazer a mesma consulta com um POST e um [Advanced Rest Client]:
![]() |
- no [1], atribuímos um valor ao elemento {nom} da rota;
- em [2], adicionamos um parâmetro [nom] à solicitação enviada;
- a resposta obtida é em [3].
Entre os valores testados para o parâmetro [nom], Request.Form["nom"], RouteData.Values["nom"], Request.QueryString["nom"], Request.Files["nom"], dois eram válidos: os dois primeiros. Foi utilizado o primeiro.
4.2. Verificar a validade dos parâmetros da ação
Se uma ação tiver um parâmetro chamado [p], ASP.NET e MVC tentarão atribuir a ela um dos valores Request.Form["p"], RouteData.Values["p"], Request.QueryString["p"], Request.Files["p"]. Os três primeiros valores são cadeias de caracteres. Se o parâmetro [p] não for do tipo [string], podem ocorrer problemas.
Vamos criar a seguinte nova ação:
// Ação02
public ContentResult Action02(int age)
{
string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}", RouteData.Values["controller"], RouteData.Values["action"],age);
return Content(texte, "text/plain", Encoding.UTF8);
}
- Na linha 2, a ação [Action02] aceita um parâmetro chamado [age] do tipo int. Será necessário que a sequência de caracteres recuperada seja convertível em int.
Vamos solicitar o URL [http://localhost:55483/First/Action02?age=21]. Obtemos a seguinte página:
![]() |
Solicitemos o URL e o [http://localhost:55483/First/Action02?age=21x]. Obtemos a seguinte página:
![]() |
Desta vez, recebemos uma página de erro. É interessante observar os cabeçalhos HTTP enviados pelo servidor neste caso:
- linha 1: o servidor respondeu com um código [500 Internal Server Error] e enviou uma página HTML (linha 3) de 12.438 bytes (linha 5) para explicar as possíveis razões desse erro.
Vamos agora criar a seguinte ação [Action03]:
// Ação03
public ContentResult Action03(int? age)
{
...
}
[Action03] é idêntico a [Action02], exceto que alteramos o tipo do parâmetro [age] para int?, o que significa inteiro ou nulo.
Vamos solicitar o URL [http://localhost:55483/First/Action03?age=21x]. Obtemos a seguinte página:
![]() |
ASP.NET MVC não conseguiu converter [21x] para o tipo int. Assim, atribuiu o valor nulo ao parâmetro [age], conforme permitido pelo seu tipo int?. No entanto, é possível saber se o parâmetro recebeu ou não um valor da consulta.
Criamos a seguinte nova ação [Action04]:
// Ação04
public ContentResult Action04(int? age)
{
bool valide = ModelState.IsValid;
string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}", RouteData.Values["controller"], RouteData.Values["action"], age, valide);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 2: mantivemos o tipo [int?]. Isso permite, em particular, que a consulta não forneça o parâmetro [age], que, portanto, recebe o valor nulo;
- linha 4: verifica-se se o modelo da ação é válido. O modelo da ação é formado pelo conjunto de seus parâmetros, neste caso [age]. O modelo é válido se todos os parâmetros tiverem obtido um valor da solicitação ou, caso o tipo do parâmetro permita, o valor nulo;
- linha 5: adiciona-se o valor da variável [valide] ao texto enviado ao cliente.
Vamos solicitar o URL [http://localhost:55483/First/Action04?age=21x]. Obtemos a seguinte página:
![]() |
ASP.NET MVC não conseguiu converter [21x] para o tipo int. Assim, atribuiu o valor nulo ao parâmetro [age], conforme permitido pelo seu tipo int?. No entanto, ocorreram erros de conversão, conforme mostra o valor de [valide].
É possível que apareça uma mensagem de erro associada a uma conversão malsucedida. Vamos examinar a seguinte nova ação:
// Ação05
public ContentResult Action05(int? age)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, âge={2}, valide={3}, erreurs={4}", RouteData.Values["controller"], RouteData.Values["action"], age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
A novidade está na linha 4. Nela, é chamado um método privado [getErrorMessagesFor], ao qual é passado o estado do modelo da ação. Ele retorna uma sequência de caracteres que reúne as mensagens de todos os erros que ocorreram. Esse método é o seguinte:
private string getErrorMessagesFor(ModelStateDictionary état)
{
List<String> erreurs = new List<String>();
string messages = string.Empty;
if (!état.IsValid)
{
foreach (ModelState modelState in état.Values)
{
foreach (ModelError error in modelState.Errors)
{
erreurs.Add(getErrorMessageFor(error));
}
}
foreach (string message in erreurs)
{
messages += string.Format("[{0}]", message);
}
}
return messages;
}
- linha 1: o parâmetro efetivo [ModelState] passado ao método é do tipo [ModelStateDictionary];
- linha 3: uma lista de mensagens de erro, inicialmente vazia;
- linha 5: verifica-se se o estado passado como parâmetro é válido ou não. Se não for, todas as mensagens de erro serão agregadas em uma única sequência de caracteres;
- linha 7: o tipo [ModelStateDictionary] possui uma propriedade [Values], que é uma coleção de tipos [ModelState]. Há um [ModelState] para cada elemento do modelo. Por exemplo:
- ModelState["age"]: o status do modelo da ação para o parâmetro [age],
- ModelState["age"].Errors: a coleção de erros para esse parâmetro. Os erros são do tipo [ModelError],
- ModelState["age"].Errors[i].ErrorMessage: a eventual mensagem de erro nº i para o parâmetro [age] do modelo
- ModelState["age"].Errors[i].Exceção: a exceção do erro nº i da coleção de erros no parâmetro [age],
- ModelState["age"].Errors[i].Exception.InnerException: a causa dessa exceção,
- ModelState["age"].Errors[i].Exception.InnerException.Message: a mensagem da causa da exceção;
- linha 9: percorre-se a coleção [Errors] de um [ModelState] específico;
- linha 11: recupera-se a mensagem de erro de um [ModelError] específico e ela é adicionada à lista de mensagens de erro da linha 3;
- linhas 14-17: agrega-se os elementos da lista de mensagens de erro em uma única sequência de caracteres.
O método [getErrorMessageFor] da linha 11 é o seguinte:
private string getErrorMessageFor(ModelError error)
{
if (error.ErrorMessage != null && error.ErrorMessage.Trim() != string.Empty)
{
return error.ErrorMessage;
}
if (error.Exception != null && error.Exception.InnerException == null && error.Exception.Message != string.Empty)
{
return error.Exception.Message;
}
if (error.Exception != null && error.Exception.InnerException != null && error.Exception.InnerException.Message != string.Empty)
{
return error.Exception.InnerException.Message;
}
return string.Empty;
}
- linha 1: recebe-se um tipo [ModelError] que encapsula um erro em um dos elementos do modelo da ação. A mensagem de erro é buscada em três locais diferentes:
- em [ModelError].ErrorMessage, linhas 3-6;
- em [ModelError].Exception.Message, linhas 7 a 10;
- em [ModelError].Exception.InnerException.Message, linhas 11 a 14;
Durante os testes, percebe-se que a mensagem de erro é encontrada nesses três locais, dependendo da natureza do elemento do modelo. Deve haver uma regra que permita obter com certeza a mensagem de erro associada a um elemento do modelo, mas eu não a conheço. Por isso, procuro-a nos diferentes locais onde posso encontrá-la, seguindo uma determinada ordem. Assim que uma mensagem não vazia é encontrada, ela é retornada.
Vamos solicitar o URL [http://localhost:55483/First/Action05?age=21x]. Obtemos a seguinte página:
![]() |
4.3. Uma ação com vários parâmetros
Consideremos a seguinte nova ação:
// Ação06
public ContentResult Action06(double? poids, int? age)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], poids, age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 2: temos dois parâmetros, [poids] e [age].
As regras descritas anteriormente se aplicam agora a ambos os parâmetros. Aqui estão alguns exemplos de execução:
![]() |
![]() |
4.4. Usar uma classe como modelo de uma ação
Vamos definir uma classe que servirá de modelo para uma ação. Vamos colocá-la na pasta [Models] [1].
![]() |
Seu código será o seguinte:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double? Poids { get; set; }
public int? Age { get; set; }
}
}
Nossa classe possui, como propriedades automáticas, os dois parâmetros [Poids] e [Age] estudados anteriormente. Essa classe será o parâmetro de entrada da ação [Action07]:
// Ação07
public ContentResult Action07(ActionModel01 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 2: o modelo da ação é uma instância do tipo [ActionModel01].
Vamos retomar os mesmos dois exemplos de antes:
![]() |
![]() |
Observe-se que a vinculação dos parâmetros não diferencia maiúsculas de minúsculas. Os parâmetros da consulta eram [age] e [poids]. Eles alimentaram as propriedades [Age] e [Poids] da classe [ModelAction01].
Além disso, até agora utilizamos as consultas HTTP e [GET]. Vamos demonstrar que as consultas [POST] apresentam o mesmo comportamento. Para isso, vamos utilizar novamente o aplicativo [Advanced Rest Client]:
![]() |
- em [1], a URL solicitada;
- em [2], ela será solicitada por um comando POST;
- no [3], os parâmetros do POST.
O resultado é o mesmo que com o GET:
![]()
4.5. Modelo da ação com restrições de validade - 1
Com o modelo anterior:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double? Poids { get; set; }
public int? Age { get; set; }
}
}
os parâmetros [poids] e [age] podem estar ausentes da consulta. Nesse caso, as propriedades [Poids] e [Age] recebem o valor [null] e nenhum erro é sinalizado. Pode-se querer transformar o modelo da seguinte forma:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double Poids { get; set; }
public int Age { get; set; }
}
}
Nas linhas 5 e 6, as propriedades [Poids] e [Age] não podem mais ter o valor [null]. Vamos ver o que acontece com esse novo modelo quando os parâmetros [poids] e [age] não constam na consulta.
![]() |
Não ocorreram erros e as propriedades [Poids] e [Age] mantiveram seu valor de inicialização: 0. ASP.NET MVC:
- criou uma instância do modelo por meio de um `new ActionModel01`. Foi nesse momento que as propriedades [Poids] e [Age] receberam o valor 0;
- não atribuiu nenhum valor a essas duas propriedades, pois não havia nenhum parâmetro com esses nomes.
O primeiro modelo nos permite verificar a ausência de um parâmetro: a propriedade correspondente passa então a ter o valor [null]. O segundo não nos permite isso. É possível adicionar outras restrições de validação além do simples tipo dos parâmetros. Vamos apresentá-las agora.
Consideremos o seguinte novo modelo de ação:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel02
{
[Required]
[Range(1, 200)]
public double? Poids { get; set; }
[Required]
[Range(1, 150)]
public int? Age { get; set; }
}
}
- linha 6: indica que o campo [Poids] é obrigatório;
- linha 7: indica que o campo [Poids] deve estar no intervalo [1,200];
- linha 9: indica que o campo [Age] é obrigatório;
- linha 7: indica que o campo [Age] deve estar no intervalo [1,150];
A ação que utiliza este modelo será a seguinte ação [Action08]:
// Ação08
public ContentResult Action08(ActionModel02 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, valide={4}, erreurs={5}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 2: a ação recebe uma instância do modelo [ActionModel02];
Vamos fazer alguns testes:
![]() |
![]() |
![]() |
![]() |
Os erros foram detectados corretamente. Agora, vamos adaptar o modelo da seguinte forma:
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel02
{
[Required]
[Range(1, 200)]
public double Poids { get; set; }
[Required]
[Range(1, 150)]
public int Age { get; set; }
}
}
Nas linhas 8 e 11, as propriedades não podem mais ter o valor [null]. Vamos compilar e refazer o teste sem parâmetros:
![]() |
A ausência de parâmetros fez com que as propriedades [Poids] e [Age] mantivessem o valor adquirido durante a instanciação do modelo: 0. A validação ocorre em seguida. O atributo [Required] é, então, satisfeito. Vemos que a mensagem de erro acima é referente ao atributo [Range]. Portanto, para verificar a presença de um parâmetro, é necessário que a propriedade associada seja nullable, ou seja, que possa receber o valor null.
Voltemos ao modelo inicial [ActionModel02] e consideremos uma ação cujo modelo é constituído por uma instância [ActionModel02] e por um tipo [DateTime] nullable:
// Ação09
public ContentResult Action09(ActionModel02 modèle, DateTime? date)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("Contrôleur={0}, Action={1}, poids={2}, âge={3}, date={4}, valide={5}, erreurs={6}", RouteData.Values["controller"], RouteData.Values["action"], modèle.Poids, modèle.Age, date, ModelState.IsValid, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Vamos fazer alguns testes:
![]() |
Não foram passados parâmetros para a ação. Os atributos [Required] das propriedades [Poids] e [Age] cumpriram sua função. A data, por sua vez, recebeu o valor null e nenhum erro foi relatado.
Agora, passaremos parâmetros inválidos:
![]() |
Agora, passamos valores válidos:
![]() |
Vamos examinar outras restrições de validade. O novo modelo de ação é o seguinte:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.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 6: o atributo [Required], desta vez com uma mensagem de erro que nós mesmos definimos;
- linha 7: o atributo [EMailAddress] exige que o campo [Email] contenha um endereço de e-mail com formato válido;
- linha 11: o atributo [RegularExpression] exige que o campo [Jour] contenha uma sequência de um ou dois dígitos. O primeiro parâmetro é a expressão regular que o campo deve verificar;
- linha 15: o atributo [MaxLength] exige que o campo [Info1] tenha no máximo 4 caracteres;
- linha 19: o atributo [MinLength] determina que o campo [Info2] tenha pelo menos 2 caracteres;
- linhas 23-24: os atributos [MaxLength] e [MinLength], combinados, determinam que o campo [Info3] tenha exatamente 4 caracteres;
A ação [Action10] utilizará este modelo:
// 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);
}
Vamos fazer alguns testes com essa ação.
Primeiro, sem parâmetros:
![]() |
Depois, com parâmetros inválidos:
![]() |
Depois, com parâmetros válidos:
![]() |
4.6. Modelo da ação com restrições de validade - 2
Apresentamos outras restrições de integridade. O novo modelo da ação será a seguinte classe [ActionModel04]:
![]() |
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel04
{
[Required(ErrorMessage="Le paramètre url est requis")]
[Url(ErrorMessage="URL invalide")]
public string Url { get; set; }
[Required(ErrorMessage = "Le paramètre info1 est requis")]
public string Info1 { get; set; }
[Required(ErrorMessage = "Le paramètre info2 est requis")]
[Compare("Info1",ErrorMessage="Les paramètres info1 et info2 doivent être identiques")]
public string Info2 { get; set; }
[Required(ErrorMessage = "Le paramètre cc est requis")]
[CreditCard(ErrorMessage = "Le paramètre cc n'est pas un n° de carte de crédit valide")]
public string Cc { get; set; }
}
}
- linha 8: determina que o campo anotado seja um URL válido;
- linha 13: exige que as propriedades [Info1] e [Info2] tenham o mesmo valor;
- linha 16: exige que o campo anotado seja um número de cartão de crédito válido.
A ação que utiliza este modelo será a seguinte:
// Ação11
public ContentResult Action11(ActionModel04 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("URL={0}, Info1={1}, Info2={2}, CC={3},erreurs={4}",
modèle.Url, modèle.Info1, modèle.Info2, modèle.Cc, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Para testar a ação [Action11], utilizamos o aplicativo [Advanced Rest Client]:
![]() |
- em [1], o URL da ação [Action11];
- em [2], esse URL será solicitado com um POST;
- no [3], seleciona-se a aba [Form];
- em [4], os valores dos quatro parâmetros esperados. Essa inicialização é um recurso oferecido por [ARC]. Os parâmetros efetivamente enviados podem ser visualizados na aba [Raw] [5];
![]() |
- no [6], os parâmetros do POST.
Para essa consulta, recebe-se a seguinte resposta:
![]() |
Vamos passar parâmetros inválidos:
![]() |
Obtemos então a seguinte resposta:
4.7. Modelo da ação com restrições de validade - 3
Às vezes, as restrições de integridade disponíveis não são suficientes. Nesse caso, é possível criar as próprias restrições. Em particular, pode-se utilizar um modelo que implemente a interface [IValidatableObject]. Nesse caso, adicionamos nossas próprias verificações do modelo no método [Validate] dessa interface. Vejamos um exemplo. O novo modelo da ação será a seguinte classe [ActionModel05]:
![]() |
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
namespace Exemple_02.Models
{
public class ActionModel05 : IValidatableObject
{
[Required(ErrorMessage = "Le paramètre taux est requis")]
public double? Taux { get; set; }
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
{
List<ValidationResult> résultats = new List<ValidationResult>();
bool ok = Taux < 4.2 || Taux > 6.7;
if (!ok)
{
résultats.Add(new ValidationResult("Le paramètre taux doit être < 4.2 ou > 6.7", new string[] { "Taux" }));
}
return résultats;
}
}
}
- linha 6: o modelo implementa a interface [IValidatableObject];
- linha 10: o método [Validate] dessa interface. Ele retorna uma coleção de elementos do tipo [ValidationResult]. Esse tipo encapsula os erros que se deseja sinalizar;
- linha 9: uma taxa válida é uma taxa <4,2 ou > 6,7;
- linha 12: cria-se uma lista vazia de elementos do tipo [ValidationResult];
- linha 13: verifica-se a validade da propriedade [Taux];
- linhas 14-17: se a propriedade [Taux] for inválida, então adiciona-se um elemento do tipo [ValidationResult] à lista de resultados. O primeiro parâmetro é uma mensagem de erro. O segundo parâmetro, opcional, é uma coleção das propriedades afetadas por esse erro.
A ação que utiliza este modelo será a seguinte:
// Ação12
public ContentResult Action12(ActionModel05 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Veja um exemplo de execução:
![]() |
4.8. Modelo de ação do tipo Tabela ou Lista
Consideremos a seguinte ação [Action13]:
// Ação13
public ContentResult Action13(string[] data)
{
string strData = "";
if (data != null && data.Length != 0)
{
strData = string.Join(",", data);
}
string texte = string.Format("data=[{0}]", strData);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 2: o modelo da ação é constituído por uma tabela de [string]. Ele nos permite recuperar um parâmetro chamado [data], que pode estar presente várias vezes nos parâmetros da consulta, como em [?data=data1&data=data2&data=data3]. Os diferentes parâmetros [data] da solicitação irão alimentar a tabela [data] do modelo da ação. Esse caso ocorre com listas de múltipla escolha. O navegador envia, então, os diferentes valores selecionados pelo usuário, com o mesmo nome de parâmetro.
Veja um exemplo:
![]() |
O modelo também pode ser uma lista:
// Ação14
public ContentResult Action14(List<int> data)
{
string erreurs = getErrorMessagesFor(ModelState);
string strData = "";
if (data != null && data.Count != 0)
{
strData = string.Join(",", data);
}
string texte = string.Format("data=[{0}], erreurs=[{1}]", strData, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
O modelo aqui é uma lista de números inteiros (linha 2). Veja uma primeira execução:
![]() |
e uma segunda:
![]() |
4.9. Filtragem de um modelo de ação
Às vezes, temos um modelo, mas queremos que apenas alguns elementos do modelo sejam inicializados pela consulta HTTP. Consideremos o seguinte modelo de ação [ActionModel06]:
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;
namespace Exemple_02.Models
{
[Bind(Exclude = "Info2")]
public class ActionModel06
{
[Required(ErrorMessage = "Le paramètre [info1] est requis")]
public string Info1 { get; set; }
public string Info2 { get; set; }
}
}
- linhas 9-10: o parâmetro [info1] é obrigatório;
- linha 6: o parâmetro [info2] da linha 12 está excluído da ligação da consulta HTTP ao seu modelo.
A ação será a seguinte [Action15]:
// Ação15
public ContentResult Action15(ActionModel06 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("valide={0}, info1={1}, info2={2}, erreurs={3}", ModelState.IsValid, modèle.Info1, modèle.Info2, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Veja um exemplo de execução:
![]() |
- em [1]: o parâmetro [info2] é passado para o URL;
- em [2]: a propriedade [Info2] do modelo da ação permaneceu vazia.
4.10. Ampliar o modelo de vinculação de dados
Voltemos à arquitetura de execução de uma ação:
![]() |
A classe da ação é instanciada no início da solicitação do cliente e destruída ao final dela. Portanto, ela não pode ser usada para armazenar dados entre duas solicitações, mesmo que seja chamada repetidamente. Pode-se querer armazenar dois tipos de dados:
- dados compartilhados por todos os usuários do aplicativo web. Geralmente, trata-se de dados somente para leitura. Três arquivos são utilizados para implementar esse compartilhamento de dados:
- [Web.Config]: o arquivo de configuração da aplicação
- [Global.asax, Global.asax.cs]: permite definir uma classe, chamada classe global da aplicação, cuja duração corresponde à da própria aplicação, bem como manipuladores para determinados eventos dessa mesma aplicação.
A classe global da aplicação permite definir dados que estarão disponíveis para todas as consultas de todos os usuários.
- dados compartilhados pelas solicitações de um mesmo cliente. Esses dados são armazenados em um objeto chamado Sessão. Fala-se, então, em sessão do cliente para se referir à memória do cliente. Todas as solicitações de um cliente têm acesso a essa sessão. Elas podem armazenar e ler informações nela.
![]() |
Acima, mostramos os tipos de memória aos quais uma ação tem acesso:
- a memória do aplicativo, que na maioria das vezes contém dados somente para leitura e é acessível a todos os usuários;
- a memória de um usuário específico, ou sessão, que contém dados de leitura/gravação e é acessível às solicitações sucessivas do mesmo usuário;
- não representada acima, existe uma memória de solicitação, ou contexto de solicitação. A solicitação de um usuário pode ser processada por várias ações sucessivas. O contexto da solicitação permite que uma ação 1 transmita informações para uma ação 2.
Vejamos um primeiro exemplo que ilustra essas diferentes memórias:
Primeiramente, modificamos o arquivo [Web.config] do projeto [Exemple-02] da seguinte maneira:
<appSettings>
<add key="webpages:Version" value="2.0.0.0" />
...
<add key="infoAppli1" value="infoAppli1"/>
</appSettings>
Adicionamos a linha 4, que associa o valor [infoAppli1] à chave [infoAppli1]. Esse será nosso dado de escopo [Application]: ele estará acessível a todas as consultas de todos os usuários.
Em seguida, modificamos o método [Application_Start] do arquivo [Global.asax]. Esse método é executado uma única vez ao iniciar o aplicativo. É nesse momento que devemos utilizar o arquivo [Web.config]:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// inicialização do aplicativo
Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
}
Adicionamos a linha 10. Ela realiza duas ações:
- ela recupera o valor da chave [infoAppli1] no arquivo [Web.config] por meio da classe [System.Configuration.ConfigurationManager];
- e o grava no dicionário [HttpApplication.Application], associado à chave [infoAppli1]. Todas as ações têm acesso a esse dicionário.
No mesmo arquivo [Gloabal.asax], adiciona-se o seguinte método [Session_Start]:
protected void Session_Start()
{
// inicialização do contador
Session["compteur"] = 0;
}
O método [Session_Start] é executado para todo novo usuário. O que é um novo usuário? Um usuário é “rastreado” por um token de sessão. Esse token é:
- criado pelo servidor web e enviado ao novo usuário nos cabeçalhos HTTP da primeira resposta que ele recebe;
- reenviado pelo navegador do usuário a cada nova solicitação que ele faz. Isso permite que o servidor reconheça o usuário e gerencie uma memória para ele, chamada de sessão do usuário.
O servidor web reconhece que está lidando com um novo usuário quando este não lhe envia um token de sessão. O servidor, então, cria um para ele.
Na linha 4 acima, colocamos na sessão do usuário um contador que será incrementado a cada solicitação desse usuário. Isso ilustrará a memória associada a um usuário. A classe [Session] é utilizada como um dicionário (linha 4).
Feito isso, escrevemos a seguinte ação [Action16]:
// Ação 16
public ContentResult Action16()
{
// recuperação do contexto da solicitação HTTP
HttpContextBase contexte = ControllerContext.HttpContext;
// recuperando as informações do escopo Aplicação
string infoAppli1 = contexte.Application["infoAppli1"] as string;
// e as do escopo da sessão
int? compteur = contexte.Session["compteur"] as int?;
compteur++;
contexte.Session["compteur"] = compteur;
// a resposta ao cliente
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 5: recuperamos o contexto da solicitação HTTP em processamento. Esse contexto nos dará acesso aos dados de escopo [Application] e [Session];
- linha 7: recuperamos as informações do escopo [Application];
- linha 9: recuperamos o contador da sessão;
- linhas 10-11: ele é incrementado e, em seguida, devolvido à sessão;
- linhas 13-14: as duas informações são enviadas ao cliente.
Veja alguns exemplos de execução:
[Action16] é solicitado pela primeira vez como [1]; em seguida, a página é atualizada duas vezes como [F5] e [2]:
![]() |
No [2], o cliente fez, no total, três solicitações. Em cada uma delas, ele conseguiu recuperar o contador atualizado pela solicitação anterior.
Para simular um segundo usuário, usamos um segundo navegador para solicitar o mesmo URL:
![]() |
No [3], o segundo usuário recupera corretamente a mesma informação de alcance do [Application], mas possui seu próprio contador de alcance, o [Session].
Voltemos ao código da ação [Action16]:
// Ação 16
public ContentResult Action16()
{
// recupera-se o contexto da solicitação HTTP
HttpContextBase contexte = ControllerContext.HttpContext;
// recuperam-se as informações do escopo Aplicação
string infoAppli1 = contexte.Application["infoAppli1"] as string;
// e as do escopo da sessão
int? compteur = contexte.Session["compteur"] as int?;
compteur++;
contexte.Session["compteur"] = compteur;
// a resposta ao cliente
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
Um dos objetivos do framework ASP.NET MVC é tornar os controladores e as ações testáveis de forma isolada, sem a necessidade de um servidor web. No entanto, vemos na linha 5 que o contexto da solicitação HTTP é necessário para recuperar as informações do escopo [Application] e do escopo [Session]. Propõe-se a criação de uma nova ação [Action17] que receberia os dados dos escopos [Application] e [Session] como parâmetros:
// Ação 17
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
{
// recuperam-se as informações do escopo Aplicação
string infoAppli1 = applicationData.InfoAppli1;
// e as do escopo de sessão
int compteur = sessionData.Compteur++;
// a resposta ao cliente
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
O código não possui mais dependências da consulta HTTP. Portanto, ele pode ser testado independentemente de um servidor web.
Vamos ver como fazer isso. Primeiramente, precisamos criar as classes [ApplicationModel] e [SessionModel], que irão encapsular, respectivamente, os dados de escopo [Application] e [Session]. São as seguintes:
![]() |
namespace Exemple_02.Models
{
public class ApplicationModel
{
public string InfoAppli1 { get; set; }
}
}
namespace Exemple_02.Models
{
public class SessionModel
{
public int Compteur { get; set; }
public SessionModel()
{
Compteur = 0;
}
}
}
Em seguida, precisamos modificar os métodos [Application_Start] e [Session_Start] do arquivo [Global.asax]:
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// inicialização da aplicação – caso 1
Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
// inicialização da aplicação – caso 2
ApplicationModel data=new ApplicationModel();
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
Application["data"] = data;
}
protected void Session_Start()
{
// inicialização do contador – caso 1
Session["compteur"] = 0;
// inicialização do contador – caso 2
Session["data"] = new SessionModel();
}
}
- linha 14: é criada uma instância de [ApplicationModel];
- linha 15: ela é inicializada;
- linha 16: e colocada no dicionário de [Application], associada à chave [data]. [Application] é uma propriedade da classe [HttpApplication] da linha 1;
- linha 24: uma instância de [SessionModel] é criada e inserida no dicionário de [Session], associada à chave [data]. [Session] é uma propriedade da classe [HttpApplication] da linha 1;
Se nos basearmos no que vimos até agora, a assinatura
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
significa que a consulta HTTP processada pela ação deverá conter parâmetros denominados [applicationData] e [sessionData]. Esse não será o caso. Precisamos criar um novo modelo de ligação de dados para que, quando uma ação receber como parâmetro um tipo:
- [ApplicationModel], os dados com escopo [Application] e chave [data] sejam fornecidos a ela;
- [SessionModel], desde que sejam fornecidos os dados de escopo [Session] e de chave [data].
Para isso, é necessário criar classes que implementem a interface [IModelBinder].
Começamos criando uma pasta [Infrastructure] no projeto [Exemple-02]:
![]() |
Nela, criamos a seguinte classe [ApplicationModelBinder]:
using System.Web.Mvc;
namespace Exemple_02.Infrastructure
{
public class ApplicationModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// os dados do escopo [Application] são retornados
return controllerContext.RequestContext.HttpContext.Application["data"];
}
}
}
- linha 5: a classe implementa a interface [IModelBinder]. Para entender seu código, é preciso saber que ela será chamada sempre que uma ação tiver um parâmetro do tipo [ApplicationModel]. Essa ligação [ApplicationModel] --> [ApplicationModelBinder] será estabelecida no início da aplicação, no método [Application_Start] de [Global.asax];
- linha 7: o único método da interface [IModelBinder];
- linha 7: o parâmetro do tipo [ControllerContext] nos dá acesso à consulta HTTP que está sendo processada;
- linha 7: o parâmetro do tipo [ModelBindingContext] nos dá acesso a informações sobre o modelo a ser construído, neste caso, o tipo [ApplicationModel];
- linha 7: o resultado de [BindModel] é o objeto que será atribuído ao parâmetro vinculado, neste caso, um parâmetro do tipo [ApplicationModel];
- linha 10: limitamo-nos a definir o objeto com escopo [Application] e chave [data].
A classe [SessionModelBinder] segue o mesmo esquema:
using System.Web.Mvc;
namespace Exemple_02.Infrastructure
{
public class SessionModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// retornando os dados do escopo [Session]
return controllerContext.HttpContext.Session["data"];
}
}
}
Resta-nos apenas associar cada um dos modelos [XModel] ao seu binder e [XModelBinder]. Isso é feito no método [Application_Start] de [Global.asax]:
protected void Application_Start()
{
....
// inicialização do aplicativo - caso 2
ApplicationModel data=new ApplicationModel();
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
Application["data"] = data;
// ligadores de modelo
ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
- linha 9: quando uma ação tiver um parâmetro do tipo [ApplicationModel], o método [ApplicationModelBinder.Bind] será chamado. Sabe-se que ele retorna o dado de escopo [Application] associado à chave [data];
- linha 10: o mesmo vale para o tipo [SessionModel].
Voltemos à nossa ação [Action17]:
// Ação 17
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
{
// recuperando as informações do escopo Aplicação
string infoAppli1 = applicationData.InfoAppli1;
// e as do escopo “Sessão”
sessionData.Compteur++;
int compteur = sessionData.Compteur;
// a resposta ao cliente
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 2: quando [Action17] for chamada, ela receberá como
- primeiro parâmetro: o dado de escopo [Application] associado à chave [data],
- segundo parâmetro: o dado de escopo [Session] associado à chave [data];
Esses dois dados podem ser tão complexos quanto se desejar e agrupar, em um deles, todos os dados do escopo [Application] e, no outro, todos os dados do escopo [Session].
Segue um exemplo de execução da ação [Action17]:
![]() |
4.11. Ligação tardia do modelo da ação
Escrevemos a seguinte ação [Action12]:
// Ação 12
public ContentResult Action12(ActionModel05 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
De forma oculta, ASP.NET MVC:
- cria uma instância do tipo [ActionModel05] usando seu construtor sem parâmetros;
- inicializa-a com as informações da solicitação que têm o mesmo nome (sem distinção entre maiúsculas e minúsculas) que uma das propriedades de [ActionModel05].
Às vezes, esse comportamento não nos convém. Esse é o caso, principalmente, quando se deseja utilizar um construtor específico do modelo da ação. Nesse caso, pode-se proceder da seguinte maneira:
// Ação 18
public ContentResult Action18()
{
ActionModel05 modèle = new ActionModel05();
TryUpdateModel(modèle);
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("taux={0}, erreurs={1}", modèle.Taux, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
- linha 2: a ação não recebe mais parâmetros. Portanto, não há mais vinculação automática de dados;
- linha 4: criamos nós mesmos uma instância do modelo da ação. É aqui que poderíamos usar um construtor diferente;
- linha 5: inicializamos o modelo com as informações da solicitação. São os códigos ASP.NET e MVC que realizam essa tarefa. Eles fazem isso da mesma forma que fariam se o modelo tivesse sido passado como parâmetro;
- linha 6: agora estamos na mesma situação que na ação [Action12].
Aqui está um exemplo de execução:
![]() |
4.12. Conclusion
Voltemos à arquitetura de uma aplicação ASP.NET MVC:
![]() |
Uma solicitação [1] traz consigo diversas informações que ASP.NET MVC apresenta a [2a] para a ação na forma de um modelo que chamamos de modelo de ação.
![]() |
- a solicitação HTTP do cliente chega em [1];
- em [2], as informações contidas na solicitação são transformadas no modelo de ação [3];
- em [4], a ação, a partir desse modelo, irá gerar uma resposta. Esta terá dois componentes: uma vista V [6] e o modelo M dessa vista [5];
- a visualização V [6] utilizará seu modelo M [5] para gerar a resposta HTTP destinada ao cliente.
No modelo MVC, a ação [4] faz parte do C (controlador), o modelo da vista [5] é o M e a vista [6] é o V.
Este capítulo abordou os mecanismos de ligação entre as informações transportadas pela solicitação — que, por natureza, são cadeias de caracteres — e o modelo da ação, que pode ser uma classe com propriedades de diversos tipos. Vimos também que é possível verificar a validade do modelo apresentado à ação. Por fim, vimos como ampliar esse modelo para os dados de escopo [Session] e [Application].
Vamos nos concentrar agora na etapa final da cadeia de processamento da solicitação [1]: a criação da visualização [6] e de seu modelo [5]. Esses dois elementos são gerados pela ação [4].
























































