ASP .NET MVC 5 - A misteriosa classe ModelState


  Neste tutorial vou desmistificar a misteriosa classe ModelState mostrando o seu significado, seus recursos e como devemos tratá-la em aplicações ASP .NET MVC.
 

Chegou o curso Curso ASP .NET .MVC 5 Vídeo Aulas onde você vai aprender a criar aplicações web dinâmicas usando a ASP .NET MVC 5.

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 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 ?

O ModelState têm dois propósitos:

  1. Armazenar o valor submetido ao servidor
  2. 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 utilizado 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 :

Desmistificando o ModelState

Abra o VS 2013 Express for web e clique em New Project;

A seguir selecione Visual C# -> ASP .NET Web Application;

Informe o nome Mvc_ModelState e 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 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 esta 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 permite 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;

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;

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 nome IncluirUsuario():

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

Muito bem...

Criando as Views

Vamos criar a view Index().

Clique com o botão direito do mouse sobre o método Index() e a seguir em Add View;

A seguir 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;

A seguir 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 a seguir onde temos um formulário para o usuário informar o nome, o sobrenome e o email:

@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 ai ???

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 para 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 para nós quando submetemos os dados, e o método Action POST tem os inputs que mapeia os valores submetidos.

Essencialmente o MVC esta encapsulando os inputs do usuário em 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.

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 com 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 (sem as referências)

Porque a palavra de Deus é viva e eficaz, e mais penetrante do que espada alguma de dois gumes, e penetra até à divisão da alma e do espírito, e das juntas e medulas, e é apta para discernir os pensamentos e intenções do coração.
Hebreus 4:12

Veja os Destaques e novidades do SUPER DVD Visual Basic (sempre atualizado) : clique e confira !

Quer migrar para o VB .NET ?

Quer aprender C# ??

Quer aprender os conceitos da Programação Orientada a objetos ?

Quer aprender o gerar relatórios com o ReportViewer no VS 2013 ?

Quer aprender a criar aplicações Web Dinâmicas usando a ASP .NET MVC 5 ?

  Gostou ?   Compartilhe no Facebook   Compartilhe no Twitter

Referências:


José Carlos Macoratti