4. O modelo de uma ação
Voltemos à arquitetura de uma aplicação ASP.NET MVC:
![]() |
No capítulo anterior, analisámos o processo que encaminha o pedido [1] para o controlador e a ação [2a] que o irá tratar, um mecanismo conhecido como encaminhamento. Apresentámos também as várias respostas que uma ação pode enviar de volta ao navegador. Até agora, apresentámos ações que não processaram a solicitação que lhes foi apresentada. Uma solicitação [1] transporta várias informações que o ASP.NET MVC apresenta [2a] à ação na forma de um modelo. Este termo não deve ser confundido com o modelo M de uma vista V [2c] que é produzida pela ação:
![]() |
- a solicitação HTTP do cliente chega a [1];
- em [2], a informação contida na solicitação é transformada num modelo de ação [3], frequentemente, mas não necessariamente, uma classe, que serve como entrada para a ação [4];
- em [4], a ação, com base neste modelo, gera uma resposta. Esta resposta tem dois componentes: uma vista V [6] e o modelo M desta vista [5];
- a vista V [6] utilizará o 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 de vista [5] é o M e a vista [6] é o V.
Este capítulo examina os mecanismos para ligar a informação transportada pela solicitação — que é inerentemente composta por cadeias de caracteres — ao modelo de ação, que pode ser uma classe com propriedades de vários tipos.
4.1. Inicialização dos parâmetros da ação
Adicionamos [1] um novo projeto básico ASP.NET MVC à solução existente:
![]() |
![]() |
- em [2], o nome do novo projeto;
- em [3, 4], selecionamos um projeto ASP.NET MVC básico;
- em [5], o novo projeto.
Vamos definir o novo projeto como o projeto inicial da solução.
Tal como feito na Secção 3.1, criamos um controlador denominado [First] [1]:

Neste controlador, criamos a seguinte ação [Action01]:
using System.Web.Mvc;
namespace Exemple_02.Controllers
{
public class FirstController : Controller
{
// Action01
public ContentResult Action01(string nom)
{
return Content(string.Format("Contrôleur=First, Action=Action01, nom={0}", nom));
}
}
}
A nova funcionalidade encontra-se na linha 8: o método [Action01] tem um parâmetro. Neste capítulo, exploraremos as diferentes formas de inicializar os parâmetros de uma ação. O parâmetro [name] acima é inicializado por ordem com os seguintes valores:
Request.Form["name"] | um parâmetro chamado [name] enviado por uma solicitação POST |
RouteData.Values["name"] | um elemento de URL chamado [name] |
Request.QueryString["name"] | um parâmetro chamado [name] enviado por uma solicitação GET |
Request.Files["name"] | um ficheiro carregado chamado [name] |
Vamos examinar estes diferentes casos. Vamos introduzir a URL [/First/Action01?name=someone] diretamente no navegador. Obtemos a seguinte resposta:

A solicitação HTTP do navegador foi a seguinte:
- Linha 1: O pedido é um GET. O URL solicitado inclui o parâmetro [name]. No lado do servidor, o pedido é encaminhado para a ação [Action01], que tem a seguinte assinatura:
public ContentResult Action01(string nom)
Para atribuir um valor ao parâmetro name, o ASP.NET MVC verifica os seguintes valores por ordem: *Request.Form["name"], RouteData.Values["name"],* *<span style="color: #2323dc">Request.QueryString["name"]</span>**, Request.Files["name"]*. Ele pára assim que encontra um valor. O parâmetro [name] incorporado na URL GET foi colocado pela estrutura em Request.QueryString["name"]. É com este valor [someone] que o parâmetro [name] 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:

Nota: O mecanismo de ligação de parâmetros não distingue maiúsculas de minúsculas. Assim, se a nossa ação estiver definida da seguinte forma:
public ContentResult Action01(string NOM)
e o parâmetro passado for [?Name=zébulon], a ligação ocorrerá na mesma. O parâmetro [Name] de [Action01] receberá o valor [zébulon].
Agora, vamos solicitar a mesma URL com um POST. Para isso, vamos utilizar a aplicação [Advanced Rest Client]:
![]() |
- em [1], a URL solicitada;
- em [2], será utilizado o método POST;
- em [3], os parâmetros POST.
Vamos enviar esta solicitação e analisar os registos HTTP. A solicitação HTTP é a seguinte:
![]() |
- em [1], o POST;
- em [2], os parâmetros POST. Tecnicamente, foram enviados após os cabeçalhos HTTP, a seguir à linha em branco que indica o fim desses cabeçalhos;
- em [3], a resposta recebida. Conseguimos recuperar com sucesso o parâmetro [name] do POST. Entre os valores testados para o parâmetro name — Request.Form["name"], RouteData.Values["name"], Request.QueryString["name"], Request.Files["name"] — o primeiro funcionou.
Agora, vamos modificar a rota padrão em [App_Start/RouteConfig]. Atualmente, esta rota é a seguinte:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{id}",
defaults: new { controller = "Home", action = "Index", id = UrlParameter.Optional }
);
Vamos alterá-lo para:
routes.MapRoute(
name: "Default",
url: "{controller}/{action}/{nom}",
defaults: new { controller = "Home", action = "Index", nom = UrlParameter.Optional }
);
- Na linha 3, nomeámos o terceiro elemento de uma rota [name];
- na linha 4, este elemento é declarado como opcional.
Agora, vamos recompilar a aplicação e solicitar a URL [/First/Action01/zébulon] diretamente no navegador. Obtemos a seguinte resposta:

Entre os valores testados para o parâmetro «name» — Request.Form["name"], RouteData.Values["name"], Request.QueryString["name"], Request.Files["name"] — o segundo funcionou.
Vamos fazer a mesma solicitação utilizando uma solicitação POST e o [Advanced Rest Client]:
![]() |
- Em [1], atribuímos um valor ao elemento {name} da rota;
- em [2], adicionamos um parâmetro [name] à solicitação enviada;
- a resposta obtida está em [3].
Dos valores testados para o parâmetro [name] — Request.Form["name"], RouteData.Values["name"], Request.QueryString["name"], Request.Files["name"] — dois eram válidos: os dois primeiros. Foi utilizado o primeiro.
4.2. Validação de parâmetros de ação
Se uma ação tiver um parâmetro denominado [p], o ASP.NET MVC tentará atribuir-lhe um dos seguintes valores: Request.Form["p"], RouteData.Values["p"], Request.QueryString["p"] ou Request.Files["p"]. Os três primeiros valores são cadeias de caracteres. Se o parâmetro [p] não for do tipo [string], poderão surgir problemas.
Vamos criar a seguinte nova ação:
// Action02
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);
}
- Linha 2: A ação [Action02] aceita um parâmetro chamado [age] do tipo int. A string recuperada deve ser convertível para int.
Vamos solicitar a URL [http://localhost:55483/First/Action02?age=21]. Obtemos a seguinte página:

Vamos solicitar a URL [http://localhost:55483/First/Action02?age=21x]. Obtemos a seguinte página:

Desta vez, recebemos uma página de erro. É interessante analisar 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 para este erro.
Agora vamos criar a seguinte ação [Action03]:
// Action03
public ContentResult Action03(int? age)
{
...
}
[Action03] é idêntica a [Action02], exceto que alterámos o tipo do parâmetro [age] para int?, o que significa inteiro ou nulo.
Vamos solicitar a URL [http://localhost:55483/First/Action03?age=21x]. Obtemos a seguinte página:

O ASP.NET MVC não conseguiu converter [21x] para um tipo int. Por isso, atribuiu o valor null ao parâmetro [age], conforme permitido pelo seu tipo int?. No entanto, é possível determinar se o parâmetro recebeu um valor da solicitação ou não.
Criamos a seguinte nova ação [Action04]:
// Action04
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?]. Isto permite especificamente que a solicitação omita o parâmetro [age], que recebe então o valor nulo;
- linha 4: verificamos se o modelo de ação é válido. O modelo de ação consiste em todos os seus parâmetros, neste caso [age]. O modelo é válido se todos os parâmetros conseguiram obter um valor da solicitação ou o valor nulo, caso o tipo do parâmetro o permita;
- Linha 5: Adicionamos o valor da variável [valid] ao texto enviado ao cliente.
Vamos solicitar a URL [http://localhost:55483/First/Action04?age=21x]. Obtemos a seguinte página:

O ASP.NET MVC não conseguiu converter [21x] para o tipo int. Por isso, atribuiu o valor nulo ao parâmetro [age], conforme permitido pelo seu tipo int?. No entanto, ocorreram erros de conversão, como demonstrado pelo valor de [valid].
É possível obter a mensagem de erro associada a uma conversão falhada. Vamos examinar a seguinte nova ação:
// Action05
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 nova funcionalidade encontra-se na linha 4. Aqui, chamamos um método privado [getErrorMessagesFor] e passamos-lhe o estado do modelo da ação. Este método devolve uma string que contém todas as mensagens de erro que ocorreram. Este 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 [ModelState] efetivamente passado para o método é do tipo [ModelStateDictionary];
- linha 3: uma lista de mensagens de erro, inicialmente vazia;
- linha 5: verificamos se o estado passado como parâmetro é válido ou não. Se não for, agregaremos todas as mensagens de erro numa única cadeia de caracteres;
- linha 7: o tipo [ModelStateDictionary] tem uma propriedade [Values], que é uma coleção de tipos [ModelState]. Existe um [ModelState] por elemento do modelo. Por exemplo:
- ModelState["age"]: o estado do modelo da ação para o parâmetro [age],
- ModelState["age"].Errors: a coleção de erros para este parâmetro. Os erros são do tipo [ModelError],
- ModelState["age"].Errors[i].ErrorMessage: a mensagem de erro (se houver) para o parâmetro [age] do modelo
- ModelState["age"].Errors[i].Exception: a exceção para o erro #i na coleção de erros do parâmetro [age],
- ModelState["age"].Errors[i].Exception.InnerException: a causa desta exceção,
- ModelState["age"].Errors[i].Exception.InnerException.Message: a mensagem da causa da exceção;
- linha 9: percorremos a coleção [Errors] de um [ModelState] específico;
- linha 11: recuperamos a mensagem de erro de um [ModelError] específico e adicionamo-la à lista de mensagens de erro da linha 3;
- linhas 14–17: os elementos da lista de mensagens de erro são concatenados numa única cadeia de caracteres.
O método [getErrorMessageFor] na 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: Recebemos um tipo [ModelError] que encapsula um erro num dos elementos do modelo da ação. Recuperamos a mensagem de erro de três locais diferentes:
- em [ModelError].ErrorMessage, linhas 3–6;
- em [ModelError].Exception.Message, linhas 7–10;
- em [ModelError].Exception.InnerException.Message, linhas 11–14;
Durante os testes, verificamos que a mensagem de erro se encontra nestes três locais, dependendo da natureza do elemento do modelo. Deve existir uma regra que garanta que possamos obter a mensagem de erro associada a um elemento do modelo, mas eu desconheço-a. Por isso, procuro-a nos vários locais onde a posso encontrar, seguindo uma ordem específica. Assim que for encontrada uma mensagem não vazia, esta é devolvida.
Vamos aceder ao URL [http://localhost:55483/First/Action05?age=21x]. Aparece a seguinte página:

4.3. Uma ação com vários parâmetros
Considere a seguinte nova ação:
// Action06
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 [peso] e [idade].
As regras descritas acima aplicam-se agora a ambos os parâmetros. Aqui estão alguns exemplos de execução:


4.4. Usar uma classe como modelo de ação
Vamos definir uma classe que servirá de modelo para uma ação. Vamos colocá-la na pasta [Models] [1].
![]() |
O seu código será o seguinte:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double? Poids { get; set; }
public int? Age { get; set; }
}
}
A nossa classe tem como propriedades automáticas os dois parâmetros [Peso] e [Idade] discutidos anteriormente. Esta classe será o parâmetro de entrada para a ação [Ação07]:
// Action07
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 de ação é uma instância do tipo [ActionModel01].
Vamos rever os mesmos dois exemplos de antes:


Note que a ligação de parâmetros não distingue maiúsculas de minúsculas. Os parâmetros da solicitação eram [age] e [weight]. Eles preencheram as propriedades [Age] e [Weight] da classe [ModelAction01].
Além disso, até agora utilizámos pedidos HTTP [GET]. Vamos demonstrar que os pedidos [POST] se comportam da mesma forma. Para tal, vamos utilizar novamente a aplicação [Advanced Rest Client]:
![]() |
- em [1], o URL solicitado;
- em [2], será enviada através de uma solicitação POST;
- em [3], os parâmetros POST.
Recebemos a mesma resposta que com a solicitação GET:
![]()
4.5. Modelo de ação com restrições de validação - 1
Utilizando o modelo anterior:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double? Poids { get; set; }
public int? Age { get; set; }
}
}
Os parâmetros [weight] e [age] podem ser omitidos da solicitação. Neste caso, as propriedades [Weight] e [Age] são definidas como [null] e nenhum erro é relatado. Pode querer transformar o modelo da seguinte forma:
namespace Exemple_02.Models
{
public class ActionModel01
{
public double Poids { get; set; }
public int Age { get; set; }
}
}
Linhas 5 e 6: as propriedades [Weight] e [Age] já não podem ter o valor [null]. Vamos ver o que acontece com este novo modelo quando os parâmetros [weight] e [age] não constam na solicitação.

Não ocorreram erros, e as propriedades [Weight] e [Age] mantiveram o seu valor de inicialização: 0. ASP.NET MVC:
- criou uma instância do modelo utilizando `new ActionModel01`. Foi aqui que as propriedades [Weight] e [Age] receberam o valor 0;
- não atribuiu quaisquer valores a estas duas propriedades porque não existiam parâmetros com esses nomes.
O primeiro modelo permite-nos verificar a ausência de um parâmetro: a propriedade correspondente tem então o valor [null]. O segundo não permite isso. É possível adicionar restrições de validação para além do tipo simples dos parâmetros. Vamos agora apresentá-las.
Considere 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 [Weight] é obrigatório;
- linha 7: indica que o campo [Peso] deve estar no intervalo [1,200];
- linha 9: indica que o campo [Idade] é obrigatório;
- linha 7: indica que o campo [Idade] deve estar no intervalo [1,150];
A ação que utiliza este modelo será a seguinte [Ação08]:
// Action08
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 executar alguns testes:




Os erros são detetados corretamente. Agora, vamos atualizar 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; }
}
}
Linhas 8 e 11: as propriedades já não podem ter o valor [null]. Vamos compilar e executar o teste novamente sem parâmetros:

A ausência de parâmetros fez com que as propriedades [Weight] e [Age] mantivessem o valor que adquiriram quando o modelo foi instanciado: 0. A validação ocorre então. O atributo [Required] é então satisfeito. Podemos ver que a mensagem de erro acima se refere ao atributo [Range]. Portanto, para verificar a presença de um parâmetro, a propriedade associada deve ser nula, ou seja, deve poder aceitar o valor nulo.
Voltemos ao modelo inicial [ActionModel02] e consideremos uma ação cujo modelo consiste numa instância [ActionModel02] e num tipo [DateTime] nulo:
// Action09
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 realizar alguns testes:

Não passámos quaisquer parâmetros para a ação. Os atributos [Required] nas propriedades [Weight] e [Age] cumpriram a sua função. A data, no entanto, recebeu o valor nulo e não foram reportados erros.
Agora, vamos passar parâmetros inválidos:

Agora vamos passar valores válidos:

Vamos examinar outras restrições de validação. 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 definida por nós;
- linha 7: o atributo [EMailAddress] exige que o campo [Email] contenha um endereço de e-mail válido;
- linha 11: o atributo [RegularExpression] exige que o campo [Day] contenha uma sequência de um ou dois dígitos. O primeiro parâmetro é a expressão regular contra a qual o campo deve ser validado;
- linha 15: o atributo [MaxLength] exige que o campo [Info1] tenha no máximo 4 caracteres;
- linha 19: o atributo [MinLength] exige que o campo [Info2] tenha pelo menos 2 caracteres;
- linhas 23-24: os atributos [MaxLength] e [MinLength] combinados exigem que o campo [Info3] tenha exatamente 4 caracteres;
A ação [Action10] utilizará este modelo:
// Action10
public ContentResult Action10(ActionModel03 modèle)
{
string erreurs = getErrorMessagesFor(ModelState);
string texte = string.Format("email={0}, jour={1}, info1={2}, info2={3}, info3={4}, erreurs={5}",
modèle.Email, modèle.Jour, modèle.Info1, modèle.Info2, modèle.Info3, erreurs);
return Content(texte, "text/plain", Encoding.UTF8);
}
Vamos realizar alguns testes com esta ação.
Primeiro, sem parâmetros:

Depois, com parâmetros inválidos:

Depois, com parâmetros válidos:

4.6. Modelo de ação com restrições de validade - 2
Introduzimos restrições de integridade adicionais. O novo modelo de 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: exige 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:
// Action11
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 a aplicação [Advanced Rest Client]:
![]() |
- em [1], a URL da ação [Action11];
- em [2], esta URL será solicitada com um POST;
- em [3], selecione o separador [Form];
- em [4], os valores dos quatro parâmetros esperados. Esta inicialização é uma funcionalidade fornecida pelo [ARC]. Os parâmetros efetivamente enviados podem ser visualizados no separador [Raw] [5];
![]() |
- em [6], os parâmetros POST.
Para este pedido, recebemos a seguinte resposta:

Vamos passar parâmetros inválidos:

Recebemos então a seguinte resposta:
![]()
4.7. Modelo de ação com restrições de validade - 3
Por vezes, as restrições de integridade disponíveis não são suficientes. Nesse caso, pode criar as suas próprias. Em particular, pode utilizar um modelo que implemente a interface [IValidatableObject]. Neste caso, adiciona as suas próprias validações de modelo ao método [Validate] desta interface. Vejamos um exemplo. O novo modelo de 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] desta interface. Retorna uma coleção de elementos do tipo [ValidationResult]. Este tipo encapsula os erros a serem reportados;
- linha 9: uma taxa válida é uma taxa <4,2 ou > 6,7;
- linha 12: criamos uma lista vazia de elementos do tipo [ValidationResult];
- linha 13: verificamos a validade da propriedade [Rate];
- linhas 14–17: se a propriedade [Rate] for inválida, então um elemento do tipo [ValidationResult] é adicionado à lista de resultados. O primeiro parâmetro é uma mensagem de erro. O segundo parâmetro, que é opcional, é uma coleção das propriedades afetadas por este erro.
A ação que utiliza este modelo será a seguinte:
// Action12
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);
}
Aqui está um exemplo de execução:

4.8. Modelo de ação do tipo Table ou List
Considere a seguinte ação [Ação13]:
// Action13
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 de ação consiste numa matriz de [string]. Permite-nos recuperar um parâmetro denominado [data], que pode aparecer várias vezes nos parâmetros da solicitação, como em [?data=data1&data=data2&data=data3]. Os vários parâmetros [data] na solicitação irão preencher a matriz [data] no modelo de ação. Este cenário ocorre com listas suspensas. O navegador envia então os diferentes valores selecionados pelo utilizador, todos com o mesmo nome de parâmetro.
Eis um exemplo:

O modelo também pode ser uma lista:
// Action14
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 inteiros (linha 2). Aqui está a primeira execução:

e uma segunda:

4.9. Filtrar um modelo de ação
Por vezes, temos um modelo, mas queremos que apenas determinados elementos do modelo sejam inicializados pela solicitação HTTP. Considere 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] na linha 12 é excluído da ligação do pedido HTTP ao seu modelo.
A ação será a seguinte [Action15]:
// Action15
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);
}
Aqui está um exemplo de execução:
![]() |
- em [1]: passamos o parâmetro [info2] na URL;
- em [2]: a propriedade [Info2] do modelo de ação permanece vazia.
4.10. Ampliando o modelo de ligação de dados
Vamos rever a arquitetura de execução de uma ação:
![]() |
A classe de ação é instanciada no início da solicitação do cliente e destruída no final da mesma. Portanto, não pode ser usada para armazenar dados entre solicitações, mesmo que seja chamada repetidamente. Pode querer armazenar dois tipos de dados:
- dados partilhados por todos os utilizadores da aplicação web. Geralmente, trata-se de dados de leitura apenas. São utilizados três ficheiros para implementar esta partilha de dados:
- [Web.Config]: o ficheiro de configuração da aplicação
- [Global.asax, Global.asax.cs]: permitem-lhe definir uma classe, denominada classe de aplicação global, cuja duração corresponde à da aplicação, bem como manipuladores para determinados eventos dessa mesma aplicação.
A classe de aplicação global permite-lhe definir dados que estarão disponíveis para todos os pedidos de todos os utilizadores.
- dados partilhados entre pedidos do mesmo cliente. Estes dados são armazenados num objeto chamado Sessão. Referimo-nos a isto como a sessão do cliente para designar a memória do cliente. Todos os pedidos de um cliente têm acesso a esta sessão. Podem armazenar e ler informações nessa sessão.
![]() |
Acima, mostramos os tipos de memória a que uma ação tem acesso:
- a memória da aplicação, que contém principalmente dados de leitura única e é acessível a todos os utilizadores;
- a memória de um utilizador específico, ou sessão, que contém dados de leitura/gravação e é acessível a pedidos sucessivos do mesmo utilizador;
- não mostrado acima, existe uma memória de solicitação, ou contexto de solicitação. A solicitação de um utilizador pode ser processada por várias ações sucessivas. O contexto de solicitação permite que a Ação 1 passe informações para a Ação 2.
Vejamos um primeiro exemplo que ilustra estes diferentes tipos de memória:
Primeiro, modificamos o ficheiro [Web.config] do projeto [Example-02] da seguinte forma:
<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]. Estes serão os nossos dados no âmbito [Application]: estarão acessíveis a todos os pedidos de todos os utilizadores.
Em seguida, modificamos o método [Application_Start] no ficheiro [Global.asax]. Este método é executado uma vez quando a aplicação é iniciada. É aqui que precisamos de utilizar o ficheiro [Web.config]:
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
// intialisation application
Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
}
Adicionamos a linha 10. Ela faz duas coisas:
- recupera o valor da chave [infoAppli1] do ficheiro [Web.config] utilizando a classe [System.Configuration.ConfigurationManager];
- armazena-o no dicionário [HttpApplication.Application], associado à chave [infoAppli1]. Todas as ações têm acesso a este dicionário.
No mesmo ficheiro [Global.asax], adicionamos o seguinte método [Session_Start]:
protected void Session_Start()
{
// initialisation compteur
Session["compteur"] = 0;
}
O método [Session_Start] é executado para cada novo utilizador. O que é um novo utilizador? Um utilizador é «rastreado» por um token de sessão. Este token é:
- criado pelo servidor web e enviado ao novo utilizador nos cabeçalhos HTTP da primeira resposta que lhe é enviada;
- enviado de volta pelo navegador do utilizador com cada novo pedido que este efetua. Isto permite ao servidor reconhecer o utilizador e gerir um espaço de memória para ele, denominado sessão do utilizador.
O servidor web reconhece que está a lidar com um novo utilizador quando este não envia um token de sessão. O servidor cria então um para ele.
Na linha 4 acima, colocamos um contador na sessão do utilizador que será incrementado com cada pedido desse utilizador. Isto ilustra a memória associada a um utilizador. A classe [Session] é utilizada como um dicionário (linha 4).
Feito isso, escrevemos a seguinte ação [Action16]:
// Action16
public ContentResult Action16()
{
// on récupère le contexte de la requête HTTP
HttpContextBase contexte = ControllerContext.HttpContext;
// on récupère les infos de portée Application
string infoAppli1 = contexte.Application["infoAppli1"] as string;
// et celles de portée Session
int? compteur = contexte.Session["compteur"] as int?;
compteur++;
contexte.Session["compteur"] = compteur;
// la réponse au client
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 atualmente em processamento. Este contexto nos dará acesso aos dados nos escopos [Application] e [Session];
- linha 7: recuperamos as informações do âmbito [Application];
- linha 9: recuperamos o contador da sessão;
- linhas 10–11: este é incrementado e, em seguida, armazenado novamente na sessão;
- Linhas 13–14: ambas as informações são enviadas para o cliente.
Aqui estão alguns exemplos de execução:
[Ação16] é solicitada uma vez [1], depois a página é atualizada [F5] duas vezes [2]:
![]() |
Em [2], o cliente efetuou um total de três pedidos. Em cada ocasião, conseguiu recuperar o contador atualizado pelo pedido anterior.
Para simular um segundo utilizador, utilizamos um segundo navegador para solicitar o mesmo URL:
![]() |
Em [3], o segundo utilizador recupera com sucesso a mesma informação do âmbito [Application], mas possui o seu próprio contador do âmbito [Session].
Voltemos ao código da ação [Action16]:
// Action16
public ContentResult Action16()
{
// on récupère le contexte de la requête HTTP
HttpContextBase contexte = ControllerContext.HttpContext;
// on récupère les infos de portée Application
string infoAppli1 = contexte.Application["infoAppli1"] as string;
// et celles de portée Session
int? compteur = contexte.Session["compteur"] as int?;
compteur++;
contexte.Session["compteur"] = compteur;
// la réponse au client
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
Um dos objetivos da estrutura ASP.NET MVC é tornar os controladores e as ações testáveis de forma isolada, sem um servidor web. No entanto, como se vê na linha 5, o contexto da solicitação HTTP é necessário para recuperar dados dos âmbitos [Application] e [Session]. Propomos a criação de uma nova ação [Action17] que receberia dados dos âmbitos [Application] e [Session] como parâmetros:
// Action17
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
{
// retrieve range info Application
string infoAppli1 = applicationData.InfoAppli1;
// and Session range
int compteur = sessionData.Compteur++;
// customer response
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
O código já não tem quaisquer dependências da solicitação HTTP. Por isso, pode ser testado independentemente de um servidor web.
Vamos ver como fazer isto. Primeiro, precisamos de criar as classes [ApplicationModel] e [SessionModel], que irão encapsular os dados dos âmbitos [Application] e [Session], respetivamente. 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 de modificar os métodos [Application_Start] e [Session_Start] no ficheiro [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);
// intialisation application - case 1
Application["infoAppli1"] = ConfigurationManager.AppSettings["infoAppli1"];
// intialisation application - case 2
ApplicationModel data=new ApplicationModel();
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
Application["data"] = data;
}
protected void Session_Start()
{
// counter initialization - case 1
Session["compteur"] = 0;
// counter initialization - case 2
Session["data"] = new SessionModel();
}
}
- linha 14: é criada uma instância de [ApplicationModel];
- linha 15: é inicializada;
- linha 16: e colocada no dicionário [Application], associada à chave [data]. [Application] é uma propriedade da classe [HttpApplication] da linha 1;
- linha 24: é criada uma instância de [SessionModel] e colocada no dicionário [Session], associada à chave [data]. [Session] é uma propriedade da classe [HttpApplication] da linha 1;
Com base no que vimos até agora, a assinatura
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
significa que o pedido HTTP processado pela ação deve incluir parâmetros denominados [applicationData] e [sessionData]. Não será esse o caso. Temos de criar um novo modelo de ligação de dados para que, quando uma ação receber um tipo como parâmetro:
- [ApplicationModel], os dados com âmbito [Application] e chave [data] lhe sejam fornecidos;
- [SessionModel], os dados com âmbito [Session] e chave [data] lhe sejam fornecidos.
Para tal, precisamos de criar classes que implementem a interface [IModelBinder].
Começamos por criar uma pasta [Infrastructure] no projeto [Example-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)
{
// render scope data [Application]
return controllerContext.RequestContext.HttpContext.Application["data"];
}
}
}
- linha 5: a classe implementa a interface [IModelBinder]. Para compreender o seu código, é necessário saber que será chamada sempre que uma ação tiver um parâmetro do tipo [ApplicationModel]. Esta ligação [ApplicationModel] --> [ApplicationModelBinder] será estabelecida quando a aplicação for iniciada, no método [Application_Start] do [Global.asax];
- linha 7: o único método da interface [IModelBinder];
- Linha 7: O parâmetro [ControllerContext] dá-nos acesso ao pedido HTTP que está a ser processado;
- linha 7: o parâmetro do tipo [ModelBindingContext] dá-nos 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: simplesmente devolvemos o objeto com o âmbito [Application] e a chave [data].
A classe [ SessionModelBinder] segue o mesmo padrão:
using System.Web.Mvc;
namespace Exemple_02.Infrastructure
{
public class SessionModelBinder : IModelBinder
{
public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
{
// render scope data [Session]
return controllerContext.HttpContext.Session["data"];
}
}
}
Resta apenas associar cada modelo [XModel] ao seu [XModelBinder]. Isto é feito no método [Application_Start] do [Global.asax]:
protected void Application_Start()
{
....
// intialisation application - case 2
ApplicationModel data=new ApplicationModel();
data.InfoAppli1=ConfigurationManager.AppSettings["infoAppli1"];
Application["data"] = data;
// model binders
ModelBinders.Binders.Add(typeof(ApplicationModel), new ApplicationModelBinder());
ModelBinders.Binders.Add(typeof(SessionModel), new SessionModelBinder());
}
- linha 9: quando uma ação tem um parâmetro do tipo [ApplicationModel], o método [ApplicationModelBinder.Bind] será chamado. Sabemos que ele retorna os dados no escopo [Application] associados à chave [data];
- linha 10: o mesmo se aplica ao tipo [SessionModel].
Voltemos à nossa ação [Action17]:
// Action17
public ContentResult Action17(ApplicationModel applicationData, SessionModel sessionData)
{
// retrieve range info Application
string infoAppli1 = applicationData.InfoAppli1;
// and Session range
sessionData.Compteur++;
int compteur = sessionData.Compteur;
// customer response
string texte = string.Format("infoAppli1={0}, compteur={1}", infoAppli1, compteur);
return Content(texte, "text/plain", Encoding.UTF8);
}
- Linha 2: Quando [Action17] for chamada, receberá
- primeiro parâmetro: os dados do âmbito [Application] associados à chave [data],
- segundo parâmetro: os dados do âmbito [Session] associados à chave [data];
Estes dois conjuntos de dados podem ser tão complexos quanto desejado e podem incluir, por um lado, todos os dados do âmbito [Application] e, por outro, todos os dados do âmbito [Session].
Eis um exemplo da execução da ação [Action17]:

4.11. Ligação tardia do modelo de ação
Escrevemos a seguinte [Ação12]:
// Action12
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);
}
Nos bastidores, o ASP.NET MVC:
- cria uma instância do tipo [ActionModel05] utilizando o seu construtor sem parâmetros;
- inicializa-a com 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].
Por vezes, este comportamento não é o que pretendemos. Isto acontece especialmente quando queremos utilizar um construtor específico do modelo de ação. Podemos então proceder da seguinte forma:
// Action18
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 já não recebe parâmetros. Por conseguinte, já não existe qualquer ligação automática de dados;
- linha 4: criamos nós próprios uma instância do modelo de ação. É aqui que poderíamos usar um construtor diferente;
- linha 5: inicializamos o modelo com as informações da solicitação. O ASP.NET MVC realiza este trabalho. Faz-o da mesma forma que faria se o modelo fosse um parâmetro;
- linha 6: estamos agora na mesma situação que na ação [Action12].
Eis um exemplo de execução:

4.12. Conclusão
Voltemos à arquitetura de uma aplicação ASP.NET MVC:
![]() |
Uma solicitação [1] transporta várias informações que o ASP.NET MVC apresenta [2a] à ação na forma de um modelo, ao qual chamamos de modelo de ação.
![]() |
- A solicitação HTTP do cliente chega a [1];
- em [2], as informações contidas na solicitação são transformadas num modelo de ação [3];
- Em [4], a ação, com base neste modelo, irá gerar uma resposta. Esta resposta terá dois componentes: uma vista V [6] e o modelo M para essa vista [5];
- a vista V [6] utilizará o 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 de vista [5] é o M e a vista [6] é o V.
Este capítulo examinou os mecanismos que ligam a informação transportada pela solicitação — que é inerentemente composta por strings — ao modelo da ação, que pode ser uma classe com propriedades de vários tipos. Também vimos que é possível validar o modelo apresentado à ação. Por fim, vimos como estender este modelo para incluir dados dos escopos [Session] e [Application].
Vamos agora concentrar-nos no final da cadeia de processamento da solicitação [1]: a criação da vista [6] e do seu modelo [5]. Estes dois elementos são produzidos pela ação [4].




















