O AutoMapper é uma biblioteca pequena e simples construída para resolver um problema aparentemente complexo, que é livrar-se de um código que mapeou um objeto para outro. Este tipo de problema é muito comum e relativamente trabalhoso de resolver, a ferramenta AutoMapper atua nesse cenário de forma simples e elegante.
No artigo anterior foi abordado o padrão View Model no ASP.Net MVC, esse padrão ajuda a organizar o código, pois divide as responsabilidades entre as Models de domínio e as Models que atendem Views, evitando que as Models de domínio sejam alteradas e poluídas com dados desnecessários ao domínio.
A primeira técnica de trabalhar com View Models foi abordada no artigo anterior.
A técnica abordada neste artigo é mais complexa e necessita de um mapeamento entre objetos que é feito com a ajuda da ferramenta AutoMapper.
No livro Padrões de Arquitetura de Aplicações Corporativas, Martin Fowler descreve um padrão base chamado Mapper. O AutoMapper foi desenvolvido por Jimmy Bogard, um dos autores do livro ASP.Net MVC 4 in Action.
Mapeando uma Model de domínio para uma View Model
O cenário deste exemplo é baseado em um sistema muito simples que consulta os dados de um cliente cadastrado e informa um número da sorte gerado randomicamente para este cliente.
Neste cenário possuímos a Model de domínio (cliente).
using System;
namespace MvcMapping.Models
{
public class Cliente
{
public string Nome { get; set; }
public string Sobrenome { get; set; }
public DateTime DataNascimento { get; set; }
public bool Ativo { get; set; }
}
}
Note que não existe referência ao dado de número da sorte, pois ele é exibido apenas na View e não é armazenado com os dados de cliente, logo torna-se desnecessário fazer parte da modelagem da Model.
Na técnica apresentada anteriormente a solução seria criar uma View Model com uma propriedade do tipo cliente e complementar com dados adicionais a serem exibidos na View.
Este artigo irá abordar outra técnica que consiste em criar uma classe com a mesma estrutura de cliente e mais os novos dados adicionais, chamaremos ela de ClienteViewModel e estará disponível em uma pasta raiz do projeto MVC chamada ViewModels.
using System;
using System.ComponentModel.DataAnnotations;
namespace MvcMapping.ViewModels
{
public class ClienteViewModel
{
[Required(ErrorMessage = "Preencher campo Nome")]
public string Nome { get; set; }
public string Sobrenome { get; set; }
[Required(ErrorMessage = "Preencher campo Data de Nascimento")]
[Display(Name = "Data de Nascimento")]
[DisplayFormat(ApplyFormatInEditMode = true, DataFormatString = "{0:yyyy-MM-dd}")]
[DataType(DataType.Date, ErrorMessage="Data em formato inválido")]
public DateTime DataNascimento { get; set; }
public bool Ativo { get; set; }
public int NumeroDaSorte { get; set; }
}
}
Na classe ClienteViewModel possuímos a mesma estrutura de clientes, mais o dado adicional de número da sorte e os DataAnnotations para validação de formulário.
Até este momento já é possível listar três benefícios imediatos:
- A Model de domínio não precisou ser modificada sem necessidade.
- A Model de domínio não está poluída com DataAnnotations de validação de formulário.
- A manutenção destas classes torna-se muito mais fácil, uma vez que a Model de domínio não está amarrada às características de uma View.
Após a separação das responsabilidades temos uma Model e uma View Model, ambas representando a entidade cliente, agora é necessário mapear um objeto ao outro para que a ClienteController receba a Model cliente e responda para View uma ClienteViewModel (e vice-versa), esse trabalho é executado pelo AutoMapper.
Configurando o AutoMapper no projeto ASP.Net MVC
Primeiramente é necessário configurar as referências das bibliotecas do AutoMapper e isto pode ser feito facilmente com o NuGet
PM> Install-Package AutoMapper
No projeto MVC crie uma pasta vazia chamada Mappers, dentro desta pasta será necessário criar uma classe que servirá de configuração dos profiles de mapeamento (Model > View Model) e (View Model > Model), esses profiles de mapeamento foram separados em dois arquivos, confira como abaixo como criar cada um deles.
Classe AutoMapperConfig
using AutoMapper;
namespace MvcMapping.Mappers
{
public class AutoMapperConfig
{
public static void RegisterMappings()
{
Mapper.Initialize(x =>
{
x.AddProfile<DomainToViewModelMappingProfile>();
x.AddProfile<ViewModelToDomainMappingProfile>();
});
}
}
}
Classe DomainToViewModelMappingProfile
[UPDATE – 12/2015] – Não é mais possível fazer o override do ProfileName nas novas versões do AutoMapper. Basta não sobrescrever esta property
using AutoMapper;
using MvcMapping.Models;
using MvcMapping.ViewModels;
namespace MvcMapping.Mappers
{
public class DomainToViewModelMappingProfile : Profile
{
// Não realizar este override na versão 4.x e superiores
public override string ProfileName
{
get { return "DomainToViewModelMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<Cliente, ClienteViewModel>();
}
}
}
Classe ViewModelToDomainMappingProfile
using AutoMapper;
using MvcMapping.Models;
using MvcMapping.ViewModels;
namespace MvcMapping.Mappers
{
public class ViewModelToDomainMappingProfile : Profile
{
// Não realizar este override na versão 4.x e superiores
public override string ProfileName
{
get { return "ViewModelToDomainMappings"; }
}
protected override void Configure()
{
Mapper.CreateMap<ClienteViewModel, Cliente>();
}
}
}
Esta é a estrutura necessária para configurar o AutoMapper de forma a utilizar o mínimo possível de código de mapeamento em outras classes da aplicação.
- AutoMapperConfig > Inicializa os profiles de mapeamento (Model > View Model) e (ViewModel > Model).
- DomainToViewModelMappingProfile > Profile de mapeamento (Model > View Model)
- ViewModelToDomainMappingProfile > Profile de mapeamento (View Model > Model)
Confira como o projeto ficou estruturado
Neste momento resta apenas configurar que a classe AutoMapperConfig seja inicializada junto com a aplicação para registrar os profiles de mapeamento, essa configuração é feita no arquivo Global.asax
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using MvcMapping.Mappers;
namespace MvcMapping
{
public class MvcApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
WebApiConfig.Register(GlobalConfiguration.Configuration);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
AuthConfig.RegisterAuth();
// Configurando o AutoMapper para registrar os profiles
// de mapeamento durante a inicialização da aplicação.
AutoMapperConfig.RegisterMappings();
}
}
}
O AutoMapper está devidamente configurado na aplicação, no próximo passo será feita a conversão da Model cliente para a ClienteViewModel na Controller através do mapeamento que foi criado, confira como ficou o código da ClienteController
using System;
using System.Web.Mvc;
using MvcMapping.Models;
using MvcMapping.ViewModels;
using AutoMapper;
namespace MvcMapping.Controllers
{
public class ClienteController : Controller
{
public ActionResult Index()
{
var rdnGen = new Random();
// Um fake de uma consulta de cliente na base de dados.
var cliente = new Cliente {
Nome = "Eduardo",
Sobrenome = "Pires",
DataNascimento = Convert.ToDateTime("24/04/1982"),
Ativo = true,
};
// Transformando a Model Cliente em ClienteViewModel
var clienteView = Mapper.Map<Cliente, ClienteViewModel>(cliente);
// Atribuindo valor aos dados adicionais da entidade Cliente
clienteView.NumeroDaSorte = rdnGen.Next(1, 100);
return View(clienteView);
}
}
}
Note que foi necessário apenas uma linha para transformar a Model Cliente em ClienteViewModel. De forma muito elegante foi criado um objeto do tipo ClienteViewModel já populado com os dados existentes no objeto Cliente.
A partir deste momento o AutoMapper está fazendo o trabalho de mapeamento e a View está recebendo uma View Model compatível com suas necessidades, confira o código da View
@model MvcMapping.ViewModels.ClienteViewModel
@{
ViewBag.Title = "Sorte do Dia";
}
<hgroup class="title">
<h1>@ViewBag.Title.</h1>
</hgroup>
<table>
<tr>
<td>
@Html.LabelFor(m => m.Nome)
@Html.DisplayFor(m => m.Nome)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(m => m.Sobrenome)
@Html.DisplayFor(m => m.Sobrenome)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(m => m.DataNascimento)
@Html.DisplayFor(m => m.DataNascimento)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(m => m.Ativo)
@Html.DisplayFor(m => m.Ativo)
</td>
</tr>
<tr>
<td>
@Html.LabelFor(m => m.NumeroDaSorte)
@Html.DisplayFor(m => m.NumeroDaSorte)
</td>
</tr>
</table>
@section Scripts {
@Scripts.Render("~/bundles/jqueryval")
}
Para mapear mais Models / View Models basta editar os arquivos DomainToViewModelMappingProfile e ViewModelToDomainMappingProfile com os mapeamentos necessários.
Achou muito trabalhoso ter que montar a estrutura de pasta e os 3 arquivos de mapeamento? Existe uma forma que dispensa toda essa configuração (inclusive inicialização no Global.asax), basta adicionar uma linha a mais em cada momento que houver o mapeamento, confira.
public class ClienteController : Controller
{
public ActionResult Index()
{
var rdnGen = new Random();
// Um fake de uma consulta de cliente na base de dados.
var cliente = new Cliente {
Nome = "Eduardo",
Sobrenome = "Pires",
DataNascimento = Convert.ToDateTime("24/04/1982"),
Ativo = true,
};
// Criando o Mapeamento por demanda.
Mapper.CreateMap<Cliente, ClienteViewModel>();
// Transformando a Model Cliente em ClienteViewModel
var clienteView = Mapper.Map<Cliente, ClienteViewModel>(cliente);
// Atribuindo valor aos dados adicionais da entidade Cliente
clienteView.NumeroDaSorte = rdnGen.Next(1, 100);
return View(clienteView);
}
}
Apesar de ser mais simples eu pessoalmente não recomendo utilizar o mapeamento desta forma, pois além de somar uma linha a mais em todo momento de executar o mapeamento, o comando de criação de mapeamento não fica centralizado, é difícil ter visão dos mapeamentos já existentes e dificulta também a manutenção.
Resumo
Utilizar o AutoMapper é muito simples conforme foi exemplificado, colabora para adoção do padrão View Model e evita a necessidade de escrever extensos códigos de mapeamento, assim proporcionando mais agilidade no desenvolvimento e facilitando a manutenção, e claro, o código final fica com um aspecto muito mais elegante.
Existem outras ferramentas para mapeamento com a mesma finalidade, a mais popular até o momento é o AutoMapper.
Referências
Nota
“Como mencionado no artigo anterior o padrão View Model está presente no MVC, MVP e MVVM, este artigo aborda a utilização deste padrão para MVC.”
Espero que este artigo ajude a entender o conceito do padrão View Model e o mapeamento de objetos através do AutoMapper.
Feedbacks são sempre muito bem vindos, utilize os comentários abaixo.