Neste artigo vou desmistificar a misteriosa classe ModelState, mostrando o seu significado, recursos e como devemos tratá-la em aplicações ASP .NET MVC.
O que é o ModelState?
O ModelSate é uma propriedade do Controller e pode ser acessado a partir das classes que herdam de System.Web.Mvc.Controller. Ele é um dicionário disponível na classe base do controlador que armazena as informações adicionais e de estado sobre o modelo.
O ModeState representa uma coleção de pares de nome e valor que são submetidos ao servidor durante o POST. Ele também contém uma coleção de mensagens de erros para cada valor submetido.
Apesar de usar o nome ModelState, ele não conhece nada sobre qualquer classe de modelo. Ele apenas contém nomes, valores e erros.
Então, para que serve o ModelState? Ele tem dois propósitos:
- Armazenar o valor submetido ao servidor;
- Armazenar os erros de validação associados com esses valores.
Assim, quando um post acontece, você pode realizar suas verificações; e se algo der errado, você pode adicionar um item ao dicionário ModelState e esta informação estará disponível para ser utilizada pela View para exibir um resumo das incoerências.
O ModelStateDictionary tem vários métodos para adicionar entradas:
void Add (KeyValuePair <string, ModelState> item); void Add (string chave, valor ModelState); AddModelError void (string chave, exceção Exception); AddModelError void (chave string, string errorMessage);
Simples assim.
Vamos, então, mostrar isso na prática.
Recursos usados: Visual Studio 2013 Express for Web
Desmistificando o ModelState
Abra o VS 2013 Express for web e clique em New Project. A seguir, selecione Visual C# -> ASP .NET Web Application e informe o nome Mvc_ModelState. Clique no botão OK.
A seguir, selecione o template Empty, marque MVC e clique no botão OK.
Será criado um projeto contendo toda a estrutura de pastas criadas pelo framework ASP .NET MVC.
Definindo o Model
Vamos definir um view model na pasta Models para representar um usuário. Não sabe que é um View Model? Então, dê uma olhada nos modelos de domínio do seu projeto, e veja se há códigos que são utilizados exclusivamente pelas Views, não tendo nenhuma relação com o domínio do negócio em questão.
Se isto estiver ocorrendo, então, seu modelo de domínio está assumindo muitas responsabilidades. O padrão View Model veio justamente para resolver isso.
O ViewModel deve conter a lógica da interface do usuário e permitir modelar entidades a partir de um ou mais modelos em um único objeto, representando um conjunto de um ou mais Models e outros dados que serão representados em uma View.
Assim, um View Model tem as seguintes características:
- Contém toda lógica de interface e a referência ao modelo e assim atua como modelo para a View;
- Separar as responsabilidades do Model usando Informações que somente serão exibidas nas Views;
- A View direciona a construção da ViewModel;
- O ViewModel contém somente dados e comportamento relacionados com a view;
- Cada ViewModel possui uma View Tipada.
Clique com o botão direito do mouse na pasta Models e a seguir Add Class. Depois informe o nome UsuarioViewModel e defina o código abaixo para esta classe:
public class UsuarioViewModel { public string Nome { get; set; } public string Sobrenome { get; set; } public string Email { get; set; } }
Definindo o Controller
Vamos agora definir o controlador do nosso projeto: clique com o botão do mouse sobre a pasta Controllers e, a seguir, clique em Add -> Controller. Selecione o Scaffold – MVC 5 Controller Empty – e clique em Add. Depois informe o nome HomeController e, a seguir, defina dois métodos Actions (o get e o post) neste controlador para incluir um usuário usando o nomeIncluirUsuario():
using System.Web.Mvc; using Mvc_ModelState.Models; namespace Mvc_ModelState.Controllers { public class HomeController : Controller { [HttpGet] public ActionResult IncluirUsuario() { UsuarioViewModel model = new UsuarioViewModel(); return View(model); } [HttpPost] public ActionResult IncluirUsuario(UsuarioViewModel model) { if (!ModelState.IsValid) { return View(model); } return RedirectToAction("Index"); } public ActionResult Index() { return View(); } } }
O código referencia o namespace Mvc_ModelState.Models para ter acesso ao modelo definido na pasta Models.
Temos o método IncluirUsuario(), que apresenta o formulário ao usuário com base no modelo UsuarioViewModel e, a seguir, trata o POST deste formulário no mesmo método onde recebe os dados enviados da view para o controlador via ModelBinding.
O método Action Index() irá definir uma view Index bem simples.
Criando as Views
Vamos criar a view Index(). Para isso, clique com o botão direito do mouse sobre o método Index() e a seguir em Add View. Depois, defina o template como Empty(without model) e clique no botão Add.
Informe apenas um texto para identificar a view, pois talvez ela nem será usada.
Vamos agora criar a view IncluirUsuario: clique com o botão direito do mouse sobre o método IncluirUsuario() e, a seguir, em Add View. Defina o template como Empty e o Model Class igual a UsuarioViewModel e clique no botão Add.
A seguir, vamos definir código da view onde temos um formulário para o usuário informar o nome, o sobrenome e o e-mail:
@model Mvc_ModelState.Models.UsuarioViewModel <h2>Incluir Usuário</h2> @using (Html.BeginForm()) { <div> <div> Nome : @Html.TextBoxFor(x => x.Nome) </div> <div> Sobrenome: @Html.TextBoxFor(x => x.Sobrenome) </div> <div> Email: @Html.TextBoxFor(x => x.Email) </div> <div> <input type="submit" value="Salvar Usuário" /> </div> </div> }
Executando o projeto e preenchendo o formulário, ao clicar no botão – Salvar Usuário – veremos que todos os valores entrados serão exibidos na instância de UsuarioViewModel (model) no controlador.
Nota: Marque um breakpoint no método Action IncluirUsuario(Post) e verifique os valores de model.
Como esses valores foram parar aí?
Vamos dar uma espiada no código HTML da view IncluirUsuario.cshtml que foi renderizada:
No momento do POST, todos os valores nas tags <input> são submetidos para o servidor como pares chave-valor.
Quando o MVC recebe o POST, ele recebe todos os parâmetros do POST e os inclui em uma instância ModelStateDictionary.
Quando debugamos o método Action POST do Controlador, podemos usar a janela Local para investigar os valores neste dicionário:
Espiando o ModelState no Debug, vemos o seguinte:
Cada uma das propriedade tem uma instância de ValueProviderResult que contém os valores atuais submetidos ao servidor.
O MVC cria todas essas instâncias automaticamente quando submetemos os dados, e o método Action POST tem os inputs que mapeia os valores submetidos.
Essencialmente, o MVC está encapsulando os inputs do usuário em classes no servidor (ModelState).
E os erros, onde estão?
Vamos aplicar atributos Data Annotations em nosso UsuarioViewModel para realizar a validação e espiar os erros. Altere a classe UsuarioViewModel incluindo os atributos abaixo no código da classe:
using System.ComponentModel.DataAnnotations; namespace Mvc_ModelState.Models { public class UsuarioViewModel { [Required(ErrorMessage = "Informe o nome do usuário.")] [StringLength(20, ErrorMessage = "O nome deve ser menor que {1} caracteres.")] [Display(Name = "Nome do Usuário:")] public string Nome { get; set; } [Required(ErrorMessage = "Informe o sobrenome do usuário.")] [StringLength(20, ErrorMessage = "O sobrenome não pode ter mais que {1} caracteres.")] [Display(Name = "Sobrenome do Usuário:")] public string Sobrenome { get; set; } [EmailAddress(ErrorMessage = "Email inválido")] [Required(ErrorMessage = "Informe o endereço de Email.")] [Display(Name = "Endereço de Email:")] public string Email { get; set; } } }
Feito isso, vamos alterar a nossa view IncluirUsuario.cshtml incluindo o ValidationSummary() e o ValidationMessageFor() para exibir mensagens de erros se eles ocorrerem.
- ValidationSummary() – realiza a validação do modelo e lê os erros do estado do modelo e exibe em uma lista;
- ValidationMessageFor() – Exibe os erros para a propriedade específica.
Veja como deve ficar o código da view:
@model Mvc_ModelState.Models.UsuarioViewModel <h2>Incluir Usuário</h2> @using (Html.BeginForm()) { <div> @Html.ValidationSummary() <div> Nome : @Html.TextBoxFor(x => x.Nome) @Html.ValidationMessageFor(x => x.Nome) </div> <div> Sobrenome: @Html.TextBoxFor(x => x.Sobrenome) @Html.ValidationMessageFor(x => x.Sobrenome) </div> <div> Email: @Html.TextBoxFor(x => x.Email) @Html.ValidationMessageFor(x => x.Email) </div> <div> <input type="submit" value="Salvar Usuário" /> </div> </div> }
Agora, vamos executar novamente e simular um erro (não informar o nome do usuário) e fazer o Debug espiando o ModelState:
Conforme era esperado, a instância de ModelState para o nome agora possui um erro na coleção Errors.
Quando o MVC cria o model state para as propriedade submetidas, ele vai até cada propriedade no ViewModel e valida a propriedade usando os atributos associados a ela.
Se houver algum erro, ele é adicionado na coleção Errors na propriedade do ModelState. Note também que IsValid agora é false, indicando que existe um erro.
Dessa forma, definindo a validação como fizemos, permitimos ao MVC trabalhar da forma esperada: o ModelState armazena os valores submetidos e permite que eles sejam mapeados para propriedades de classe, mantendo uma coleção de erros para cada propriedade.
É tudo que precisamos e tudo isso ocorre de forma transparente e não precisa de nenhuma configuração extra.
Pegue o projeto completo aqui: Mvc_ModelState.zip