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:
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
- Use View Models to manage data & organize code in ASP.NET MVC applications
- ASP.NET MVC View Model Patterns
- How we do MVC – View Models
- View Model pattern and AutoMapper in ASP.NET MVC Applications
Aguardem o próximo artigo complementar sobre AutoMapper 😉
Dúvidas e feedbacks são sempre muito bem vindos, utilize os comentários abaixo.
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!
Eu tentei fazer por esse exemplo, porem eu não consigo visualizar na view os atributos. Não tenho ideia do que pode ser.
Olá Leilyanne,
Poderia postar mais detalhes para eu avaliar junto com você? Se quiser enviar no meu e-mail [email protected] eu te dou uma mão.
Tive esse mesmo problema…Consegui resolver usando:
“public IEnumerable Produtos { get; set; }” na classe “CarrinhoComprasViewModel.cs”
Muito show Eduardo Pires… Parabens.
Poderia mandar um Post sobre AutoMapper.
Olá Anderson, muito obrigado!
Já fiz 🙂
https://www.eduardopires.net.br/2013/08/asp-net-mvc-utilizando-automapper-com-view-model-pattern/
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
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..
Olá Ramsés,
Acabei lhe respondendo no email que me enviou.
Abs!
Ops, escrevi errado, seria assim: Seria possível compartilhar a resposta dada ao Ramsés, pois tenho a mesma dúvida dele. Grato! e excelente artigo!
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…
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!
Olá Cleber!
Obrigado! Fico feliz por isso.
Abs!
Muito bom Eduardo!!
Obrigado Jefferson!
Abraços!
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?
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.
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.
Confesso que tenho o mesmo questionamento…
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?
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; }
}
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!
public IEnumerable Produtos { get; set; } ?
Não seria public public IEnumerable Produtos { get; set; } ?
Nossa, o site mudou o que eu escrevi! Eu coloquei o sinal de maior e menor depois do IEnumerable com produto entre eles e saiu isso aí totalmente diferente e duplicando a palavra public?!?!! Nossa!
Acho que o site retira os sinais de maior e menor sozinho rsrs
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?
Eduardo,
Perfeito o artigo, estou num processo de AUTO-RECICLAGEM e seus artigos tem sido de muita valia.
Obrigado mesmo.
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?