Campo de busca

Categoria Programação

O que é e como utilizar o padrão View Model em um projeto ASP.NET MVC

O que é e como utilizar o padrão View Model em um projeto ASP.NET MVC

Por | 24 de abril, 2014 | 29 comentários

Fala pessoal, tudo certo?

Dando continuidade ao assunto padrões de projeto, vamos discutir hoje sobre o padrão View Model, o qual possui a finalidade de retirar a responsabilidade dos nossos modelos de domínio no que diz respeito a camada de apresentação, permitindo assim diminuir a sua complexidade e aumentar a sua coesão.

O problema a ser resolvido

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 a resposta for sim, o seu modelo de domínio está tendo mais responsabilidade e complexidade do que ele deveria ter, ou seja, além de cuidar do seu domínio, ele está cuidando também da sua camada de apresentação, a qual deveria ser uma responsabilidade de uma outra camada.

Domain Model -> Controller -> View

Relacionamento das camadas MVC sem o padrão View Model

Vamos trabalhar com um exemplo, no qual o usuário tem a possibilidade de se autenticar para acessar uma área restrita e a possibilidade de alterar sua senha. Nesse cenário, teríamos a seguinte classe:

public class Usuario
{
   [Required]
   public string Email { get; set; }

   [Required]
   public string Senha { get; set; }

   public string NovaSenha { get; set; }

   public string NovaSenhaConfirmacao { get; set; }

   public bool ManterConectado { get; set; }
}

Na classe acima, temos alguns problemas:

  • Utilização de Data Annotations para validação da nossa classe no formulário de autenticação;
  • A existência de um atributo que irá definir se a pessoa deseja ou não manter-se conectada no formulário de autenticação;
  • Dois atributos exclusivos para alterar a senha, que serão utilizados no formulário de alteração de senha.

Veja, o nosso modelo de domínio está sendo guiado pela nossa camada de apresentação: Os atributos NovaSenha, NovaSenhaConfirmacao e ManterConectado só existem para facilitarmos a nossa vida na camada de apresentação, mas na camada de domínio em si, eles são inúteis.

O padrão em ação

Como no nosso exemplo temos dois casos, a autenticação e a alteração de senha, vamos criar um arquivo UsuarioViewModel.cs, e dentro dele, vamos criar duas classes, sendo elas AutenticacaoViewModelAlteracaoSenhaViewModel:

public class AutenticacaoViewModel
{
    [Required]
    public string Email { get; set; }

    [Required]
    public string Senha { get; set; }

    public bool ManterConectado { get; set; }
}

public class AlteracaoSenhaViewModel
{
    [Required]
    public string Senha { get; set; }

    [Required]
    public string NovaSenha { get; set; }

    [Required]
    public string NovaSenhaConfirmacao { get; set; }
}

Com isso, o nosso modelo de domínio Usuario ficou muito mais limpo, tendo apenas os atributos necessários:

public class Usuario
{
   public string Email { get; set; }
   public string Senha { get; set; }
}

Agora garantimos a separação de responsabilidade e coesão das nossas classes.

Domain Model -> View Model -> Controller -> View

Relacionamento das camadas MVC com o padrão View Model

Nossos View Models são guiados pela nossa camada de apresentação, contendo assim apenas o conteúdo necessário para criarmos nossas Views.

Veja que tivemos uma repetição de código para chegarmos até aqui, por exemplo, o atributo Nome agora está em dois lugares. Pelo que estamos acostumados a pensar, que toda repetição de código é ruim, você provavelmente achou isso um ponto negativo desse padrão, mas não se preocupe: Nem toda a repetição de código é ruim.

É importante dizer que, dentro de um View Model, você deve referenciar apenas tipos View Model, e não tipos do modelo de domínio. Vamos supor que na classe AlteracaoSenhaViewModel você precise referenciar uma classe Endereco, nesse caso, você irá referenciar um atributo do tipo EnderecoView e não um atributo do tipo Endereco. Lembre-se: Não é legal que seus View Models conheçam o seu domínio ;)

No final, vamos criar nossas duas Views, a de autenticação:

@model PatternViewModel.ViewModels.AutenticacaoViewModel

<!DOCTYPE html>

<html>
    <head>
        <title>Autenticação</title>
    </head>
    <body>
        @using (Html.BeginForm())
        {
            @Html.AntiForgeryToken()

            @Html.LabelFor(model => model.Email)
            @Html.EditorFor(model => model.Email)

            @Html.LabelFor(model => model.Senha)
            @Html.PasswordFor(model => model.Senha)

            @Html.LabelFor(model => model.ManterConectado)
            @Html.EditorFor(model => model.ManterConectado)

            <input type="submit" value="Autenticar" />
        }
    </body>
</html>

E a de alteração de senha:

@model PatternViewModel.ViewModels.AlteracaoSenhaViewModel

<!DOCTYPE html>

<html>
    <head>
        <title>Alteração de senha</title>
    </head>
    <body>
        @using (Html.BeginForm())
        {
            @Html.AntiForgeryToken()

            @Html.LabelFor(model => model.Senha)
            @Html.PasswordFor(model => model.Senha)

            @Html.LabelFor(model => model.NovaSenha)
            @Html.PasswordFor(model => model.NovaSenha)

            @Html.LabelFor(model => model.NovaSenhaConfirmacao)
            @Html.PasswordFor(model => model.NovaSenhaConfirmacao)

            <input type="submit" value="Alterar senha" />
        }
    </body>
</html>

Veja que o que muda nos dois casos, é o modelo que estamos definindo para as Views: AlteracaoSenhaViewModel para alteração de senha, e AutenticacaoViewModel para a autenticação.

Agora que aplicamos o padrão e dividimos as responsabilidades, vamos ver como iremos passar os dados das nossas View Models para os nossos domínios.

Mapeando os objetos, modo hard

No modo Chuck Norris, faremos a passagem de dados entre as camadas de forma manual. No nosso Controller, teremos:

//
// POST: /autenticacao.html

[HttpPost]
public ActionResult Autenticacao(AutenticacaoViewModel autenticacaoVM)
{
    if (ModelState.IsValid)
    {
        var usuario = new Usuario
        {
            Email = autenticacaoVM.Email,
            Senha = autenticacaoVM.Senha
        };

        ...
    }

    return View();
}

Nesse caso, como temos apenas duas propriedades, ficou bem simples fazermos esse mapeamento de forma manual. Mas a coisa começa a complicar quando nossos modelos possuem muitos atributos. Nesse cenário, o AutoMapper entra em ação.

Mapeando os objetos, modo easy

Para passarmos os dados entre objetos de forma automática, vamos utilizar o AutoMapper. Como eles mesmo se definem, o AutoMapper é um mapeador objeto-objeto, 100% orgânico e livre de glúten.

Primeiro de tudo, iremos utilizar o NuGet para instalar o AutoMapper em nosso projeto:

PM> Install-Package AutoMapper

Após isso, vamos criar o arquivo AutoMapperBootStrapper.cs na raiz do nosso projeto, que será responsável por iniciar a configuração do AutoMapper:

public class AutoMapperBootStrapper
{
    public static void ConfigureAutoMapper()
    {
        Mapper.CreateMap<AutenticacaoViewModel, Usuario>();
        Mapper.CreateMap<AlteracaoSenhaViewModel, Usuario>();
    }
}

Veja que utilizamos o CreateMap para fazer uma relação entre nossos View Models e o nosso modelo de domínio. No caso acima, especificamos que o View Model é o source, e o modelo de domínio é o tipo de destino.

Caso a gente precise fazer o processo inverso, ou seja, passarmos os dados de Usuario para os nossos View Models, teremos que fazer o mapeamento inverso:

Mapper.CreateMap<Usuario, AutenticacaoViewModel>();
Mapper.CreateMap<Usuario, AlteracaoSenhaViewModel>();

No arquivo Global.asax.cs, vamos chamar a nossa configuração do AutoMapper no método Application_Start:

protected void Application_Start()
{
    ...

    AutoMapperBootStrapper.ConfigureAutoMapper();
}

Agora vamos voltar no nosso Controller, e utilizaremos o AutoMapper para passar os dados entre os objetos:

//
// POST: /autenticacao.html

[HttpPost]
public ActionResult Autenticacao(AutenticacaoViewModel autenticacaoVM)
{
    if (ModelState.IsValid)
    {
        var usuario = Mapper.Map<Usuario>(autenticacaoVM);

        ...
    }

    return View();
}

Fácil, não?

No caso do alterar senha, teremos que fazer algumas configurações no AutoMapper, pois não temos um relacionamento direto entre o atributo NovaSenha do nosso View Model e o atributo Senha do nosso modelo de domínio. Vamos abrir o nosso AutoMapperBootStrapper.cs e fazer algumas alterações:

Mapper.CreateMap<AlteracaoSenhaViewModel, Usuario>()
    .AfterMap((s, d) => d.Senha = s.NovaSenha);

Aqui coloquei que, após fazer o mapeamento, o valor do atributo Senha do Usuario deverá ser o valor do atributo NovaSenha do AlteracaoSenhaViewModel.

<update>

O mapeamento entre Senha e NovaSenha pode ser feito também utilizando o ForMember:


Mapper.CreateMap<AlteracaoSenhaViewModel, Usuario>()
     .ForMember(d => d.Senha, o => o.MapFrom(s => s.NovaSenha));

Creio que essa seja uma melhor solução do que o AfterMap. Obrigado Alberto Monteiro pelo toque :)

</update>

Veja que o AutoMapper nos dá a possibilidade de customização, não engessando assim os nossos mapeamentos.

O código de exemplo que usei aqui está no meu GitHub.

Qualquer dúvida, sugestão, crítica ou doação de cerveja, deixe um comentário :)

Até a próxima!

 

Deixe um comentário

Queremos saber o que você pensa sobre esse post! Ficaremos felizes se você nos deixar um comentário :-)

Tenha em mente que os comentários são moderados, e os links possuem rel="nofollow". Então por favor, não faça spam de palavras-chave, senão o seu comentário será excluído. Obrigado!

 

[29] Comentários  

Mateus Oliveira

Fala Rodolfo.
Cara, meus parabéns,o artigo fico MUITO bem explicado.
Você depois poderia falar também do MVVM e qual a diferença para esse design mostrado.

24-04-2014 às 16:17 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Mateus Oliveira

Opa, valeu :)

Sobre o MVVM, eu nunca utilizei ele, então não tenho como te explicar a diferença. Mas creio que ele seja um modelo de arquitetura, assim como o MVC é, certo?

30-04-2014 às 18:24 Responder

Alberto Monteiro

Em resposta a Rodolfo Pereira

Geralmente MVVM é usado em aplicações WPF e afins que usam XAML.
Mas eu uso esse padrão também em apps web com o framework knockout.
Pelo que eu já vi do blog, você usa AngularJS, então provavelmente você usar MVVM e não sabe :D

25-06-2015 às 21:50 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Alberto Monteiro

É verdade, dei uma lida aqui e por causa do 2-way binding o padrão é o MVVM. Valeu de novo huahauhau!

26-06-2015 às 10:39 Responder

Jonatas

MUITO bom, cara!
Passei algumas semanas atrás de uma explicação dessas.
Parabéns!

06-06-2014 às 12:50 Responder

Anderson

Amigo muito obrigado pela explicação. Tenho um colega de trabalho que afirma que a modal domain pode ser referenciada direto sem o uso da modal view eu disse que isso é fora do padrão MVC, que o MVC é um framework esta numa camada de web e a camada de negocio e camada de entidade sao separada seguindo o modelo DDD, você poderia me ajudar de como posso explicar.

19-07-2014 às 22:25 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Anderson

Hm, você mostrou este post para ele? Acho que é um bom começo, hehe.

Procure focar nos problemas resolvidos pelo padrão View Model, e veja se eles existem no projeto de vocês. Não utilize-o apenas por ele ser um "padrão" e ponto final. Veja se ele irá solucionar problemas atuais ou se ele evitará problemas futuros em sua aplicação.

Abs!

22-07-2014 às 16:16 Responder

José Maria

Rodolfo muito bom seu artigo;
estou desenvolvendo meu TCC em MVC4 e estou com alguns problemas ainda;
como posso fazer um pesquisa de uma data, tipo tenho a data de nascimento do cliente e gostaria de fazer um relatório de quem nasceu no ano 1985. como posso fazer essa busca.
obrigado

21-07-2014 às 21:22 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a José Maria

Bom, e onde está a sua dificuldade nesse processo?

Pois desde pegar a data, passar para o BD, e retornar para a aplicação, tem algumas etapas a serem feitas.

Abs!

22-07-2014 às 16:37 Responder

Marcos

Ola! =D

Posso expressar minha opiniao? Nao tenho aqui criar discordia nem nada. So quero apresentar meu ponto de vista ;)

Vamos la:
MVC = Model View Controller e um padrao de UI ou seja, deve ser localizado exclusivamente em um projeto de UI (No caso WEB).

Se voce colocar codigo que possui responsabilidades de UI tais como o atributo [Required] por exemplo, ja esta errado!

Ai voce me fala: Ah Marcos, ai voce usa o ViewModel que eu expliquei.

Errado! =O

A resposta e: Nunca se use entidades do Dominio como Models caso elas precisem extendidas de alguma forma. Nesse caso basta criar um Model para a view em questao, que por sua vez possuira a entidade que esta localizada em outro projeto.

Ahh Marcos mas nesse caso foi o que eu fiz utilizando ViewModel!

Exato!
O que eu quero dizer e que existe um equivoco no entendimento sobre o que e ViewModel e que no exemplo do seu post ela na verdade e apenas uma Model comum ;)


Na verdade falei que era "minha opiniao" mas nao e, isso e apenas a verdade. Eis um link do nosso PAI/REI/O Fodao dos design patter:
http://martinfowler.com/eaaDev/uiArchs.html#ModelViewController O Grand Martin Fowler.

Segue a linha exata que ele fala isso:
...The idea behind Separated Presentation is to make a clear division between domain objects that model our perception of the real world, and presentation objects that are the GUI elements we see on the screen.

Domain objects should be completely self contained and work without reference to the presentation...


Rodolfo, na verdade o ViewModel possui uma utilidade sim. Mas e usado em casos muito mais especificos, tais como no WPF utilizando o padrao MVVM...
Mas ai e outra questao :S

Espero ter ajudado em algo, continue escrevendo Rodolfo, voce escreve bem o/

Abracos!

Marcos Paulo - Slipmp
www.slipmp.com

28-07-2014 às 17:20 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Marcos

Fala Marcos!

Obrigado pela sua opinião. Darei uma lida no texto do Martin Fowler :)

Abs!

20-08-2014 às 18:11 Responder

Alberto Monteiro

Em resposta a Marcos

Isso que o Rodolfo fez, é o uso de ViewModel sim, e você que está errado.
ViewModel não é utilizável somente em WPF, Windows Phone, Windows 8 app, XAML em geral.
É possível sim usar ViewModel em apps Web.
No caso que o Rodolfo fez, ele tem algumas VM, como:
- AlteracaoSenhaViewModel
- AutenticacaoViewModel

A classe que ele fez, é especifica para a view, é o modelo da View, que posteriormente é mapeada para um modelo de domínio.

E você erra mais ainda quando fala que MVC é padrão de UI.
MVC foi concebido como um padrão ARQUITETURAL.

O Required não tem nada haver com responsabilidade de UI, é a regra do DOMINIO dele, inclusive o Entity Framework entende esse atributo para marcar uma coluna como not null.

Eu acho que você deveria reler o post do Martin Fowler, e deveria também parar de pagar tanto pau para esse cara.

25-06-2015 às 21:49 Responder

Marcos

Em resposta a Alberto Monteiro

Alberto Monteiro,

Por favor mencione as referencias. Sem referencias, o argumento se torna invalido.

Obrigado!

26-06-2015 às 10:36 Responder

Alberto Monteiro

Em resposta a Marcos

Referencia?
Você é um grande falastrão.
Se você não respeita minha opinião, tudo bem, mas não venha dizer que ela é inválida.

Exemplo: Seu comentário está completamente errado, e possui uma referencia.
Referencia não garante nada, principalmente quando não se sabe do que fala, que é o seu caso.

26-06-2015 às 12:06 Responder

Marcos

Em resposta a Marcos

Alberto,

Eu nunca quis dizer que o autor estava errado ou algo do tipo.
So gostaria de gerar uma discussao saudavel sobre o conteudo para que possamos aprender juntos. Afinal um blog tem esse objetivo certo?

Vejo que agora a discussao saiu da linha do conteudo e agora se tornou ofensiva, nesse caso me retirarei da discussao, pois ela ja nao se faz mais sentido.

Veja por exemplo as respostas do Rodolfo, sem nenhum tipo de ofensa.

O que eu posso dizer como profissional e que a experiencia de trabalhar com voce nao deve ser muito agradavel se voce possui esse tipo de postura.

De qualquer forma eu agradeco!

Att,

Marcos Soares - Slipmp

26-06-2015 às 13:08 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Alberto Monteiro

"MVC foi concebido como um padrão ARQUITETURAL."

Essa parte me lembrou deste tópico do .Net Architects: https://groups.google.com/d/msg/dotnetarchitects/uividNqPJhA/Cd9Gw-TUYgEJ

26-06-2015 às 10:43 Responder

Alberto Monteiro

Em resposta a Rodolfo Pereira

Pois é Rodolfo,

Ai vem o cara, cheio de moral, dizer que teu post está errado, e na verdade ele está totalmente equivocado sobre o que fala.
É cada uma que a gente tem que ver....

26-06-2015 às 12:03 Responder

Marcos

Em resposta a Rodolfo Pereira

Fui reler minha resposta para o Rodolfo para ver se eu tinha sido agressivo de alguma forma,
Ou se eu de alguma forma tinha sido mal educado. Mas nao, ate comecei com meu post falando:
"Posso expressar minha opiniao? Nao tenho aqui criar discordia nem nada. So quero apresentar meu ponto de vista ;)"

Era so minha opiniao. Nao queria criar uma experiencia negativa.

Rodolfo ate peco desculpas caso esse tenha sido o ponto de vista, definitivamente nao foi meu objetivo. So queria gerar uma discussao saudavel.

Mas ai pessoas comecam a te chamar de: paga pau, fanfarrao, etc. Alem de ser super agressivo.

E uma pena, fiz uma pesquisa sobre o profissional:
http://blog.albertomonteiro.net.br/category/net/csharp/
Possui um otimo curriculo, exemplar! Porem uma postura totalmente infantil diante discussoes. Alberto Monteiro por favor mude essa sua postura porque isso pode afetar a sua carreira profissional.

Desejo muito boa sorte para todos nos! E que continuemos a aprender mais e mais cada dia.

Abracos!

26-06-2015 às 13:22 Responder

Junior

Doação de cerveja? Serve café? kkk
Vlw pelo POST, parece ser legal trabalhar com o automapper, vou procurar estudá-lo.
Obrigado!

27-10-2014 às 10:21 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Junior

Opa, café também aceito hauhau

Abs!

29-10-2014 às 11:51 Responder

Thiago

Fala Rodolfo, tudo bem?

Gostei muito deste post, queria tirar uma dúvida contigo: No caso de eu ter um viewModel que contém properties de dois Models (ex Notas e Itens da nota).

Como que eu faço para mapear essa informação? No caso após um post na página, terei o viewmodel preenchido, então como a partir deste viewModel preenchido eu posso colocar as informações referentes ao model Nota e as informações referentes ao model Itens da Nota?

Eu tinha pensado no model Nota, ter uma propriedade do tipo ItensDaNota também...

Desde já agradeço.

08-01-2015 às 08:24 Responder

Alberto Monteiro

Em resposta a Thiago

Fala Thiago, blz?!

Com o AutoMapper você consegue fazer isso.
Basicamente você teria algo do tipo

Mapper.CreateMap<ViewModel, Notas>();
Mapper.CreateMap<ViewModel, ItensDaNota>();

Assim você criou o mapa, e provavelmente teria que configurar algo, depois disso seria só usar assim:

var nota = Mapper.Map<Notas>(objViewModel);
var itensDaNota = Mapper.Map<ItensDaNota>(objViewModel);

É basicamente isso.
Se você ainda tem duvida, me envia o corpo das suas classes que te respondo melhor.

Obs.: Troca os @ pelos simboles de maior e menor que, pois o blog não deixou eu postar eles, alias, deu a tela amarela da morta do ASP.NET

25-06-2015 às 21:57 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Alberto Monteiro

Opa, coloque os sinais de maior que e menor que.

26-06-2015 às 10:34 Responder

Claudio Maia

Excelente artigo, claro e objetivo! Meus parabéns =)

29-04-2015 às 16:17 Responder

Alberto Monteiro

Rodolfo,

Por que você escolheu usar o AfterMap ao invés de usar o ForMember?

25-06-2015 às 22:06 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Alberto Monteiro

Fala Alberto,

Por desconhecimento na época mesmo. Atualizei o post com o seu feedback, valeu :D

Abs!

26-06-2015 às 10:18 Responder

Renato Cesar Ribeiro

Que bom seria se todos os posts fossem assim bem simples de se entender. Nada de termo muito tecnico, a melhor parte foi "100% orgânico e livre de glúten" rs..
Muito bom cara, entendi perfeitamente como funciona esse treco de modelo de dominio e modelo de apresentação.

Valeu por compartilhar o conhecimento.

05-01-2016 às 17:18 Responder

Rodolfo Pereira

Rodolfo Pereira

Em resposta a Renato Cesar Ribeiro

:)

27-01-2016 às 08:37 Responder

Alexandre Muniz

Parabéns pela explicação Rodolfo Pereira, sempre fiz o cast entre objetos "na mão"... Mapper realmente ajuda bastante...

09-02-2017 às 17:42 Responder