ASP.Net MVC – View Model Pattern – Quando e como utilizar?

No desenvolvimento em ASP.Net utilizando o framework MVC é muito comum o uso do pattern View Model, esse pattern proporciona uma melhor organização do código e gestão dos dados utilizados na View, confira em quais situações ele é utilizável e algumas maneiras de como aplicá-lo.

A necessidade da utilização do pattern View Model surge na maioria dos projetos ASP.Net MVC, para os iniciantes costuma causar alguma confusão, porém é muito útil e poupa um bom trabalho.

O conceito de View Model não limita-se apenas para o ASP.Net MVC, você poderá encontrar referências sobre View Model para padrões MVC, MVP e MVVM, que por sua vez é implementado em tecnologias como ASP.Net, Silverlight e WPF.

Este artigo é dedicado para o entendimento do padrão View Model utilizando ASP.Net MVC.

O que é um ASP.Net MVC View Model?

No ASP.Net MVC os View Models nos permitem modelar várias entidades a partir de um ou mais modelos em um único objeto, eu gosto de usar como exemplo a comparação de View Models com uma DTO, pois ambas são soluções que foram projetadas para centralizar um conjunto de dados de diversas fontes assim evitando a necessidade de realizar várias chamadas para se obter todos os dados necessários e evitar a necessidade de alterar um modelo de domínio para realizar o transporte de algum dado específico.

Resumindo, um View Model representa um conjunto de uma ou mais Models e outros dados que serão representados em uma View que necessita exibir determinado conjunto de informações. A imagem abaixo ilustra o conceito de um View Model:

ASP.Net  MVC View Model Pattern

Utilizando uma View Model dedicada contendo a Model de domínio

Imagine que estamos desenvolvendo uma aplicação web de uma loja virtual onde possuímos a classe Produto:

public class Produto
{
    public string Nome { get; set; }
    public decimal Valor { get; set; }
}

Porém na View de carrinho de compras além das informações contidas na classe Produto é necessário exibir uma mensagem e o valor total do carrinho para vários produtos. O que fazer? Modificar a classe Produtos para conter essas informações?

Modificar a classe Produto não seria uma boa alternativa, afinal esses dados adicionais não fazem sentido pertencerem a entidade Produto, são dados pertinentes a View de carrinho de compras.

É nesse momento que o pattern View Model entra em ação para resolver esse problema de design. Criaremos então uma classe que irá prover dados para esta View e essa classe será uma View Model de carrinho de compras.

public class CarrinhoComprasViewModel
{
	public IEnumerable Produtos { get; set; }
	public decimal TotalCarrinho { get; set; }
	public string Mensagem { get; set; }
}

Podemos observar que esta View Model possui um IEnumerable de Produtos para uma lista de produtos e mais os dados de valor e mensagem que irão ser exibidos na View.
Não é um tipo de classe especial, é uma classe como qualquer outra Model, porém escrita especificamente para atender a uma View.

Confira o código da Controller de carrinho de compras.

public class CarrinhoComprasController : Controller
{
    public ActionResult Index()
    {

        // Criando uma lista de produtos fake para exibição na View
        var produtos = new List<Produto>();
        for (int i = 0; i < 10; i++)
	    {
            produtos.Add(new Produto
                            { Nome = "Produto " + i, Valor = 1.13M * i }
                        );
        }

        // Populando a model para exibição na View
        var model = new CarrinhoComprasViewModel
	    {
            Produtos = produtos,
            TotalCarrinho = produtos.Sum(p => p.Valor),
            Mensagem = "Obrigado por comprar conosco!"
        };

        return View(model);
    }

}

E por fim a View de carrinho de compras

@model MeuExemploMVC.Models.CarrinhoComprasViewModel

@{
    ViewBag.Title = "Carrinho de Compras";
}

<h2>@Model.Mensagem
</h2>
<fieldset>
    <legend>Carrinho de Compras</legend>

    <table>
        <caption>Produtos no Carrinho</caption>
        <thead>
        <tr>
            <th>Produto</th>
            <th>Valor</th>
        </tr>
        </thead>
        <tbody>
        @foreach (var produto in Model.Produtos) {
            <tr>
                <td>@produto.Nome</td>
                <td>@produto.Valor</td>
            </tr>
        }
        </tbody>
        <tfoot>
            <tr>
                <td><strong>Total</strong></td>
                <td>@Model.TotalCarrinho</td>
            </tr>
        </tfoot>
    </table>

</fieldset>

O pattern de View Model está aplicado e não será mais necessário modificar a Model de Produto para adequar a View.

O arquivo físico de uma View Model pode estar em diferentes lugares, sendo:

  • Em uma pasta chamada ViewModels na estrutura raiz do projeto MVC (aplicações pequenas)
  • Uma *.dll referenciada no projeto MVC (aplicações de qualquer tamanho)
  • Em projetos separados (como uma camada de serviços) para gerar dados específicos (aplicações grandes)

Utilizando AutoMapper para realizar o mapeamento de uma Model e suas variantes

Uma outra forma de utilizar View Models seria criando um mapeamento entre a Model entidade de domínio e a View Model que será exibida na View.

Os autores do livro ASP.Net MVC 4 in Action defendem fortemente a utilização de mapeamento entre as Models e suas possíveis variantes.

Com base no exemplo acima, a classe Produto poderia ter uma variante chamada ProdutoViewModel, qual possuiria os mesmos atributos de Produto entre outros mais.

O mapeamento entre essas duas Models seria feito através da ferramenta AutoMapper.
Este é um processo mais complexo e será abordado em meu post exclusivo sobre utilização do AutoMapper.

Benefícios de usar uma View Model

  • Não precisar alterar uma classe Model para atender as necessidades de uma View.
  • Poder agrupar informações de uma ou mais Models em uma única classe, poupando a necessidade de realizar N consultas.
  • Um dado não contido em uma Model de domínio pode ser facilmente transportado através de uma View Model.
  • Mudanças são muito mais fáceis de se realizar e sem afetar a Model do domínio.
  • Não é necessário “poluir” as Models de domínio com DataAnnotations de validação de formulário, pois estas podem estar contidas diretamente na View Model.

Resumo

O uso de View Models é muito recomendado e irá ajudar a organizar e gerenciar os dados a serem transportados e exibidos, proporcionando flexibilidade para montar conjuntos de dados compatíveis com as necessidades da View.

No MVC existe o conceito de separação de responsabilidades onde “M” (Model) é considerada a menos importante, pois nem sempre a Model estará presente no projeto MVC e sim numa camada de domínio que irá prover estas classes. A Model do MVC está voltada ao uso na View, portanto algumas técnicas são necessárias para separar e mapear Models de entidades de domínio para Models do projeto MVC.

Referências

Aguardem o próximo artigo complementar sobre AutoMapper 😉
Dúvidas e feedbacks são sempre muito bem vindos, utilize os comentários abaixo.

35 pensou em “ASP.Net MVC – View Model Pattern – Quando e como utilizar?

  1. Olá Eduardo,

    Eu não gosto de usar o nome View Model, no mesmo livro que você citou (Professional ASP.NET MVC 4) tem o seguinte trecho: “Note that the term ‘view model’ here is different from the concept of view model within the Model View ViewModel (MVVM) pattern. That’s why I tend to use the term ‘view specific model’ when I discuss view models.”

    Tenho a mesma opinião do livro, o termo View Model é muito ligado ao padrão MVVM e procuro não utilizá-lo.

    • Olá Victor,

      Obrigado por contribuir, muito bom o ponto levantado!

      Eu também concordo que causa confusão, seria necessário utilizar um termo que sirva exclusivamente à técnica utilizada no MVC, o termo “view specific model” é bem claro nesse ponto mas parece que limita-se apenas ao uso do autor, nas demais referências que consultei todas usam o termo “View Model”, porém com a ressalva “estamos falando do padrão MVC View Model”, acredito que para resolver qualquer confusão logo no começo.

      É realmente um detalhe a se apegar, vou colocar esse ponto de atenção quando for falar de View Model no MVC.

      Valeu! Abraços!

  2. Olá, muito top, parabéns!
    Na classe ViewModel qual diferença entre criar a propriedade IEnumerable Produto e herdar uma lista de Produto?

    public class CarrinhoComprasViewModel :List()
    {
    public string Mensagem { get; set; }
    }

    Wennder Santos

    • Obrigado Wennder,

      Eu não herdaria nada, pois o objetivo da View Model é ser uma classe que compõe todo conjunto de dados que você necessita, por isso eu criaria todas as propriedades a propria classe sem heranças.

      Ajudei?

      Abraços.

      • Em alguns momentos eu utilizei dessa forma:
        Tenho a classe Produto com todas as suas propriedades. Quando é feita uma venda sempre são vários produtos, então eu precisava de uma lista de produtos, entretanto, com algumas propriedades a mais que não seria legal colocar na classe Produto como no seu exemplo. Fiz da forma que mostrei.
        Cria uma classe Produtos (no plural) herdando uma lista de Produto, e dentro da classe Produtos eu tenho alguns métodos além das propriedades que a complementam. No final ela chegaria no mesmo objetivo mostrado no seu exemplo, mas, a sua resolução ficou mais “bonita”. Em termos de desempenho será que tem diferença?

        Wennder Santos

        • Wennder,

          A ViewModel tem o propósito exclusivo de atender a View com todos os dados necessários, coloque nela tudo o que determinada View dor precisar.

          Não ficou muito claro, quando vc diz herdando uma lista de produtos, você:

          – Criou uma propriedade que é uma lista da classe Produto?
          – Herdou a classe produto na sua ViewModel

          A segunda alternativa não é recomendada, uma ViewModel não deve herdar classes de domínio.
          Quanto a primeira alternativa ok, pois se vc vai utilizar dados de produtos não tem problema nenhum ter um tipo (ou lista) de produto em sua ViewModel.

          Ficou mais claro?

          • Olá,
            entendi, eu uso dessa segunda maneira, crio uma outra classe herdando uma lista de Produto, assim:( VB.NET)

            Public Class Produtos()
            inherits List(Of Produto)

            Property ValorTotal As Integer

            End Class

            Funciona perfeito, mas, eu sabia que não era a maneira ”correta” de se fazer isso.

            Obrigado.

            Wennder Santos

  3. Olá, Eduardo Pires…

    Tenho uma dúvida, como seria o calcular TotalCarrinho se vc utiliza-se AutoMapper em aplicações em camadas e seguindo a regra de arquitetura, a camada negocio/serviço não conheceria as classes ViewModel, retornando a Entidade Produto. Já no Controller você faria o mapeamento de entidade para viewmodel. Mas como ficaria o cálculo do TotalCarrinho???
    Grato,

    Ramsés F..

        • Muito Obrigado Mario!

          Segue a resposta:

          É simples a controller recebe a model de dominio populada converte para ViewModel e entrega para a View.
          No inverso a View faz um post para a controller que recebe uma ViewModel e converte para Model que será entregue para alguma camada acima…

  4. Olá Eduardo,

    Parabéns pelo artigo. Sou iniciante em MVC e finalmente entendi o conceito de ViewModel e AutoMapper graças ao seu texto.
    Continue assim, é de grande ajuda a comunidade.

    Abraços e sucesso!

  5. Ola tudo bem, tenho um amigo, que disse que tenho que usar a camada de negocio e a camada de entidade como Model, exemplo tenho a Model View ele coloca a Model view na camada de entidade domínio ao invés de colocar na camada WEB e fazer o uso do automap para pode passar as informações para outra camada de entidade. Como devo fazer sou novo no projeto, e quero fazer o que é certo sem ofender ninguém. Como poderia explicar de uma forma que ele entenda que isso é uma boa pratica e segurança também. grato.

    • Anderson,

      Isto está errado.
      O domínio não deve conhecer as ViewModels, pois elas são para resolver problemas da sua View (o domínio não depende da sua camada de apresentação).
      Você pode criar DTOs na sua camada de domínio, mas elas não devem ser decoradas com Annotations e etc… Classes POCO apenas.

      Sugiro continuar com suas ViewModels na camada do MVC (apresentação) o automapper pode resolver para você ou na controller ou numa camada de Application, que é uma camada intermediária entre o domínio e a apresentação.

      Beleza?

  6. Olá Pires, tenho models Aluno e Endereço, como faço para que na view do aluno eu consiga cadastrar também o endereço?

    As ViewModels ajudam?

    • Olá Murillo,

      Nesse caso vc pode resolver com ViewModel sim, depois vc converte usando AutoMapper.

      Abs.

  7. Olá, Eduardo.

    Ultimamente ando utilizando uma classe Metadata para cada classe de domínio da aplicação (Models do EF), onde coloco os dataannotations.
    Esta classe fica assim:

    [MetadataType(typeof(CargoMetadata))]
    [DisplayName(“Cargo”)]
    public partial class Cargo
    {
    //Propriedades customizadas adicionais da View
    public bool exibeMenu { get; set; }
    }

    public class CargoMetadata
    {
    //Espelho das propriedades da classe Cargo gerada pelo EF
    public int CargoId { get; set; }
    [DisplayName(“Descrição”)]
    public string Descricao { get; set; }
    public virtual ICollection Funcionario { get; set; }
    }

    Por enquanto é o jeito mais fácil que vi para fazer, mas é o mais recomendado ?

    Abraço.

  8. Olá Eduardo.

    Ótimo material, estou utilizando AutoMapper no meu projeto, e vi o seguinte problema

    Minhas PK são Guid, então ela é gerada no Model, acontece que ao fazer
    Mapper.Map(viewmodel,model) (no create por ex) na minha viewModel não existe a Guid, logo eu o Mapper passa o valor 000… para a minha model, alterando o ID que foi gerado dentro dela

    Há como contornar isso?

  9. Qual o padrão mais correto adotado pelo MVC, ter varias classes na viewModel afim de que cada uma tenha apenas o necessário, ou ter o menor numero de classes ainda que alguns atributos não sejam usados em todas as consultas existentes para essa classe?

    exemplo: é melhor ter 3 classes ou adicionar o email em PessoaFisicaConsulta ainda que nem todas as telas que utilizem o PessoaFisicaConsulta precisem do atributo email?

    public class PessoaFisicaConsulta
    {
    public virtual string Nome { get; set; }
    public virtual string CPF { get; set; }
    public virtual string RG { get; set; }
    }

    public class PessoaFisicaCadastro
    {
    public virtual string Nome { get; set; }
    public virtual string CPF { get; set; }
    public virtual string RG { get; set; }
    public virtual string Email{ get; set; }
    public virtual string Endereco { get; set; }
    public virtual string EnderecoNumero { get; set; }
    public virtual string EnderecoComplemento { get; set; }
    }

    public class PessoaFisicaEmail
    {
    public virtual string Nome { get; set; }
    public virtual string CPF { get; set; }
    public virtual string Email{ get; set; }
    }

  10. Boa Tarde Eduardo!

    Com essa técnica eu na posso utilizar dois models em uma view? Tentei adaptar essa técnica mas não obtive sucesso!

  11. Ola Eduardo, seus artigos me ajudam bastante, só tenho algumas pequenas duvidas, sobre View Models, gostaria de saber quando dois models possui o mesmo atributo numa viewModel, o que fazer nesse caso para não dar conflito?

  12. Suponha as seguintes Entidades:
    public class Matricula
    {
    public Guid MatriculaId { get; private set; }
    public string CodAluno { get; private set; }
    public DateTime Data { get; private set; }
    }
    public class Aluno
    {
    public string CodAluno { get; private set; }
    public string Nome { get; private set; }
    }

    E, a seguinte ViewModel:
    public class MatriculaAlunoViewModel
    {
    public string CodAluno { get; private set; }
    public string Nome { get; private set; }
    public DateTime Data { get; private set; }
    }

    Seguindo os exemplos do seu curso ASP.NET MVC 5 a ViewModel está na camada de Aplicação.
    Esta camada de Aplicação tem referências às camadas de Domínio e de Dados(Repositório).

    Como fazer com que o Repositório retorne os dados mesclados das 2 entidades, se ele não tem acesso a camada de Aplicação?

Os comentários estão fechados.