André Alves de Lima

Talking about Software Development and more…

Para que serve o AutoMapper?

Eu já comentei anteriormente que, sempre que alguém se inscreve na minha newsletter, eu faço algumas perguntas. Uma dessas perguntas é sobre os próximos temas que o novo(a) inscrito(a) gostaria que fossem abordados aqui no site. Uns tempos atrás um novo leitor se inscreveu e sugeriu que eu escrevesse sobre AutoMapper. Acompanhe neste artigo tudo o que eu consegui encontrar sobre essa biblioteca.

Fico impressionado como a cada dia encontramos alguma tecnologia que nunca tínhamos ouvido falar. Todos os dias novas bibliotecas, metodologias de desenvolvimento e tecnologias são desenvolvidas. Isso acontece numa velocidade tão grande que é impossível acompanhar tudo o que está sendo inventado.

Esse foi o caso do AutoMapper para mim. Quanto o leitor sugeriu que eu escrevesse sobre ele, isso é o que me veio na cabeça: “Mas que diabos é esse tal de AutoMapper? Eu nunca ouvi falar desse negócio!“. Mas, enfim, fiz uma anotação na minha lista de ideias para posts e o dia chegou em que eu tinha que escrever sobre o AutoMapper.

Após concluir a pesquisa para o artigo, eu entendi o porquê de eu nunca ter ouvido falar do AutoMapper. O motivo é que ele é mais usado em projetos do tipo ASP.NET MVC (pelo menos 99% dos exemplos que eu encontrei foram em sites que abordam desenvolvimento web com o ASP.NET MVC e o exemplo sempre mencionava a utilização dele em projetos desse tipo). E, como você sabe, esse site é focado em desenvolvimento de aplicativos desktop. Porém, como teoricamente essa biblioteca também pode ser utilizada em aplicativos desktop, resolvi abordá-la aqui no site mesmo assim. Confira o resultado da minha pesquisa nas seções a seguir.

Exemplo Model-ViewModel

Todos os exemplos que eu encontrei da utilização do AutoMapper foram relacionados ao mapeamento Model-ViewModel. Eu não sabia que o pessoal costumava utilizar ViewModels também em projetos MVC (para mim no MVC só eram necessários o Model, a View e o Controller), mas, parece ser uma prática comum a existência de uma ViewModel também.

O mapeamento Model-ViewModel que eu conheço é o utilizado no modelo arquitetural MVVM (Model-View-ViewModel), muito comum no WPF e já abordado inúmeras vezes aqui no meu site. Porém, a galera do MVC parece que utiliza ele um pouco diferente do que estamos acostumados no WPF. Nos Controllers do MVC, pelo que percebi, é costume retornar uma ViewModel para a View, que muitas vezes é uma versão mais simplificada (ou customizada) do Model, só com as propriedades que serão exibidas na página em questão.

Para entendermos esse cenário, imagine que temos uma classe chamada “Pessoa” e outra classe chamada “PessoaViewModel” (utilizei um projeto do tipo “Console Application” para testar esses conceitos). Essas classes em um primeiro momento têm somente uma propriedade, chamada “Nome”:

        public class Pessoa
        {
            public string Nome { get; set; }
        }

        public class PessoaViewModel
        {
            public string Nome { get; set; }
        }

Para mapear uma “Pessoa” em uma “PessoaViewModel“, podemos fazer manualmente da seguinte maneira:

        static void Main(string[] args)
        {
            var pessoa = new Pessoa()
            {
                Nome = "André",
            };
            var pessoaViewModel = new PessoaViewModel()
            {
                Nome = pessoa.Nome,
            };

            Console.WriteLine(pessoaViewModel.Nome);
            Console.ReadLine();
        }

Tudo funciona perfeitamente e sem maiores problemas enquanto temos somente uma propriedade na classe “Pessoa“. Agora, imagine que adicionamos mais uma propriedade às classes “Pessoa” e “PessoaViewModel“, chamada “Sobrenome“. Além disso, adicionamos mais uma propriedade na classe “PessoaViewModel” para retornar o nome completo:

        public class Pessoa
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
        }

        public class PessoaViewModel
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
            public string NomeCompleto 
            { 
                get
                {
                    return string.Format("{0} {1}", Nome, Sobrenome);
                }
            }
        }

E o código do mapeamento? Agora temos que considerar também a propriedade “Sobrenome“:

        static void Main(string[] args)
        {
            var pessoa = new Pessoa()
            {
                Nome = "André",
                Sobrenome = "Alves de Lima"
            };
            var pessoaViewModel = new PessoaViewModel()
            {
                Nome = pessoa.Nome,
                Sobrenome = pessoa.Sobrenome
            };

            Console.WriteLine(pessoaViewModel.Nome);
            Console.WriteLine(pessoaViewModel.Sobrenome);
            Console.WriteLine(pessoaViewModel.NomeCompleto);
            Console.ReadLine();
        }

Já deu pra entender até onde isso pode chegar, não é mesmo? Imagine que essas classes não tenham somente duas propriedades, mas sim, vinte propriedades. Imagine o código para fazer o mapeamento das propriedades de “Pessoa” em “PessoaViewModel“. É justamente para resolver esse tipo de problema que o AutoMapper foi inventado.

Utilizando o AutoMapper

A biblioteca AutoMapper pode ser adicionada facilmente a qualquer projeto utilizando o NuGet. Basta procurar por “AutoMapper” na janela de gerenciamento dos pacotes NuGet e instalá-la (caso esteja com dificuldade, confira o meu outro artigo sobre como gerenciar pacotes do NuGet no Visual Studio).

Com a biblioteca adicionada ao projeto, para fazer o mapeamento de “Pessoa” para “PessoaViewModel“, temos que realizar duas operações. Primeiro, precisamos chamar o método “CreateMap” passando os tipos “Pessoa” e “PessoaViewModel“. Depois, temos que chamar o método “Map” passando as instâncias de “Pessoa” e “PessoaViewModel“:

        static void Main(string[] args)
        {
            var pessoa = new Pessoa()
            {
                Nome = "André",
                Sobrenome = "Alves de Lima"
            };
            var pessoaViewModel = new PessoaViewModel();

            AutoMapper.Mapper.CreateMap<Pessoa, PessoaViewModel>();
            AutoMapper.Mapper.Map(pessoa, pessoaViewModel);

            Console.WriteLine(pessoaViewModel.Nome);
            Console.WriteLine(pessoaViewModel.Sobrenome);
            Console.WriteLine(pessoaViewModel.NomeCompleto);
            Console.ReadLine();
        }

Pronto! Com apenas essas duas linhas o AutoMapper vai varrer as propriedades do objeto fonte (instância da classe “Pessoa“) e copiar os valores nas propriedades do objeto destino (instância da classe “PessoaViewModel“). E com isso não temos que fazer mais o mapeamento manual de cada uma das propriedades. Desde que as propriedades tenham o mesmo nome tanto na classe fonte como na classe destino, o AutoMapper conseguirá mapeá-las automaticamente.

Mas, e se as propriedades tiverem nomes diferentes? Sem problema! Basta fazer um mapeamento customizado.

Mapeamentos customizados

Os mapeamentos customizados servem para mapearmos propriedades que não têm o mesmo nome na classe fonte e na classe destino. Eles servem também para adicionarmos uma lógica customizada no mapeamento de valores.

Seguindo com o nosso exemplo, imagine que tenhamos um “enum” que representa o sexo da pessoa e que esse “enum” tenha os itens “Masculino” e “Feminino“. E se quisermos que a classe “Pessoa” tenha uma propriedade utilizando esse “enum“, mas, a classe “PessoaViewModel” tenha uma propriedade string que representa o sexo entre “M” ou “F“? Nesse caso, temos que criar um mapeamento customizado.

Para criar um mapeamento customizado, na chamada do método “CreateMap“, temos que adicionar uma chamada ao método “ForMember“. Esse método serve para informar ao mecanismo do AutoMapper que para um determinado membro da classe destino (daí o nome “ForMember“), queremos mapeá-lo de forma diferente. Veja como ficaria a situação mencionada no parágrafo anterior:

        public enum Sexo
        {
            Masculino,
            Feminino
        }

        public class Pessoa
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
            public Sexo Sexo { get; set; }
        }

        public class PessoaViewModel
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
            public string NomeCompleto 
            { 
                get
                {
                    return string.Format("{0} {1}", Nome, Sobrenome);
                }
            }
            public string Sexo { get; set; }
        }

        static void Main(string[] args)
        {
            var pessoa = new Pessoa()
            {
                Nome = "André",
                Sobrenome = "Alves de Lima",
                Sexo = Sexo.Masculino
            };
            var pessoaViewModel = new PessoaViewModel();

            AutoMapper.Mapper.CreateMap<Pessoa, PessoaViewModel>()
                .ForMember(pvm => pvm.Sexo, map => map.MapFrom(p => p.Sexo == Sexo.Masculino ? "M" : "F"));
            AutoMapper.Mapper.Map(pessoa, pessoaViewModel);

            Console.WriteLine(pessoaViewModel.Nome);
            Console.WriteLine(pessoaViewModel.Sobrenome);
            Console.WriteLine(pessoaViewModel.NomeCompleto);
            Console.WriteLine(pessoaViewModel.Sexo);
            Console.ReadLine();
        }

Traduzindo: para o membro “Sexo” de “PessoaViewModel“, utilize a expressão p => p.Sexo == Sexo.Masculino ? “M” : “F”, ou seja, caso “Sexo” na instância de “Pessoa” seja “Masculino“, utilize “M“, caso contrário, utilize “F“. E com isso temos o resultado esperado.

Vejamos um outro exemplo. Imagine que na classe “Pessoa” nós temos agora uma nova propriedade chamada “Idade“. Porém, ao fazer o mapeamento, queremos que a idade seja transformada em uma categoria de idades (por exemplo: criança, jovem, adulto, idoso). O mapeamento customizado ficaria o seguinte:

        public enum Sexo
        {
            Masculino,
            Feminino
        }

        public class Pessoa
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
            public Sexo Sexo { get; set; }
            public short Idade { get; set; }
        }

        public class PessoaViewModel
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
            public string NomeCompleto 
            { 
                get
                {
                    return string.Format("{0} {1}", Nome, Sobrenome);
                }
            }
            public string Sexo { get; set; }
            public string ClasseIdade { get; set; }
        }

        static void Main(string[] args)
        {
            var pessoa = new Pessoa()
            {
                Nome = "André",
                Sobrenome = "Alves de Lima",
                Sexo = Sexo.Masculino,
                Idade = 30
            };
            var pessoaViewModel = new PessoaViewModel();

            AutoMapper.Mapper.CreateMap<Pessoa, PessoaViewModel>()
                .ForMember(pvm => pvm.Sexo, map => map.MapFrom(p => p.Sexo == Sexo.Masculino ? "M" : "F"))
                .ForMember(pvm => pvm.ClasseIdade, map => map.MapFrom(p => p.Idade <= 10 ? "Criança" : (p.Idade <= 18 ? "Jovem" : (p.Idade <= 50 ? "Adulto" : "Idoso"))));
            AutoMapper.Mapper.Map(pessoa, pessoaViewModel);

            Console.WriteLine(pessoaViewModel.Nome);
            Console.WriteLine(pessoaViewModel.Sobrenome);
            Console.WriteLine(pessoaViewModel.NomeCompleto);
            Console.WriteLine(pessoaViewModel.Sexo);
            Console.WriteLine(pessoaViewModel.ClasseIdade);
            Console.ReadLine();
        }

Note que podemos incluir múltiplas chamadas ao método “ForMember“, uma para cada mapeamento customizado.

Esse tipo de customização dá uma grande flexibilidade ao processo de mapeamento. Porém, como você deve ter percebido, o código fica bem complexo. Para simplificar um pouco esse código, podemos utilizar os “ValueResolvers” do AutoMapper.

ValueResolver (resolução customizada de valores)

A classe abstrata “ValueResolver” do AutoMapper permite criarmos lógicas bem complexas para utilizarmos no processo de mapeamento de propriedades. A ideia é simplificar um pouco o processo de criação de mapeamentos customizados. Ao invés de colocarmos a lógica diretamente na chamada do método “ForMember“, nós colocamos a lógica em uma classe separada e utilizamos essa classe no processo de mapeamento.

A ideia é muito simples, basta criarmos uma classe herdando de “ValueResolver” e implementar o método “ResolveCore“. Veja como ficaria a mesma lógica que criamos anteriormente para converter o valor do “enum” Sexo em “M” e “F“:

        public class SexoValueResolver : AutoMapper.ValueResolver<Sexo, string>
        {
            protected override string ResolveCore(Sexo source)
            {
                if (source == Sexo.Masculino)
                    return "M";
                return "F";
            }
        }

Com essa classe criada, podemos utilizá-la na chamada de “ForMember“, através do método “ResolveUsing“:

            AutoMapper.Mapper.CreateMap<Pessoa, PessoaViewModel>()
                .ForMember(pvm => pvm.Sexo, map => map.ResolveUsing<SexoValueResolver>().FromMember(p => p.Sexo))
                .ForMember(pvm => pvm.ClasseIdade, map => map.MapFrom(p => p.Idade <= 10 ? "Criança" : (p.Idade <= 18 ? "Jovem" : (p.Idade <= 50 ? "Adulto" : "Idoso"))));

Vamos fazer o mesmo para a lógica do mapeamento entre “Idade” e “ClasseIdade“:

        public class ClassIdadeResolver : AutoMapper.ValueResolver<short, string>
        {
            protected override string ResolveCore(short source)
            {
                if (source <= 10)
                    return "Criança";
                else if (source <= 18)
                    return "Jovem";
                else if (source <= 50)
                    return "Adulto";
                return "Idoso";
            }
        }

E então é só utilizar essa nova classe na chamada de “ForMember“:

            AutoMapper.Mapper.CreateMap<Pessoa, PessoaViewModel>()
                .ForMember(pvm => pvm.Sexo, map => map.ResolveUsing<SexoValueResolver>().FromMember(p => p.Sexo))
                .ForMember(pvm => pvm.ClasseIdade, map => map.ResolveUsing<ClassIdadeResolver>().FromMember(p => p.Idade));

Bem mais simples e fácil de manter do que utilizar a expressão lambda, não?

IValueFormatter (formatações customizadas)

Em algumas situações em que convertemos algum tipo de dado em strings, é necessário fazermos uma formatação especial. Isso é muito comum ao convertermos DateTimes em strings. A conversão padrão de DateTime em string faz com que a data e hora seja utilizada no resultado. Para entendermos o que estou querendo dizer, imagine que tenhamos uma nova propriedade na classe “Pessoa” e “PessoaViewModel“, chamada “DataNascimento“. Na classe “Pessoa“, vamos criar essa nova propriedade como DateTime. Já na classe “PessoaViewModel“, vamos criá-la como string (note também que, como agora temos a data de nascimento na classe “Pessoa“, podemos calcular automaticamente o valor de idade diretamente no “getter” da propriedade “Idade“):

        public class Pessoa
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
            public Sexo Sexo { get; set; }
            public short Idade
            {
                get
                {
                    short idade = (short)(DateTime.Now.Year - DataNascimento.Year);
                    if (DateTime.Now.Month < DataNascimento.Month || (DateTime.Now.Month == DataNascimento.Month && DateTime.Now.Day < DataNascimento.Day)) idade--;
                    return idade;
                }
            }
            public DateTime DataNascimento { get; set; }
        }

        public class PessoaViewModel
        {
            public string Nome { get; set; }
            public string Sobrenome { get; set; }
            public string NomeCompleto 
            { 
                get
                {
                    return string.Format("{0} {1}", Nome, Sobrenome);
                }
            }
            public string Sexo { get; set; }
            public string ClasseIdade { get; set; }
            public string DataNascimento { get; set; }
        }

Como fica o resultado padrão do mapeamento de “Pessoa” em “PessoaViewModel” realizado pelo AutoMapper?

        static void Main(string[] args)
        {
            var pessoa = new Pessoa()
            {
                Nome = "André",
                Sobrenome = "Alves de Lima",
                Sexo = Sexo.Masculino,
                DataNascimento = new DateTime(1984, 12, 20)
            };
            var pessoaViewModel = new PessoaViewModel();

            AutoMapper.Mapper.CreateMap<Pessoa, PessoaViewModel>()
                .ForMember(pvm => pvm.Sexo, map => map.ResolveUsing<SexoValueResolver>().FromMember(p => p.Sexo))
                .ForMember(pvm => pvm.ClasseIdade, map => map.ResolveUsing<ClassIdadeResolver>().FromMember(p => p.Idade));
            AutoMapper.Mapper.Map(pessoa, pessoaViewModel);

            Console.WriteLine(pessoaViewModel.Nome);
            Console.WriteLine(pessoaViewModel.Sobrenome);
            Console.WriteLine(pessoaViewModel.NomeCompleto);
            Console.WriteLine(pessoaViewModel.Sexo);
            Console.WriteLine(pessoaViewModel.ClasseIdade);
            Console.WriteLine(pessoaViewModel.DataNascimento);
            Console.ReadLine();
        }

Reparou que a conversão da data de nascimento contém também a hora? E se quisermos que somente a data seja exibida? Simples, é só criarmos uma classe herdando da interface “IValueFormatter” que terá a implementação da formatação customizada. Veja como ficaria a implementação dessa classe:

        public class ShortDateFormatter : AutoMapper.IValueFormatter
        {
            public string FormatValue(AutoMapper.ResolutionContext context)
            {
                return ((DateTime)context.SourceValue).ToShortDateString();
            }
        }

E para utilizar esse formatador customizado, temos que utilizar o método “ForMember” em conjunto com o método “AddFormatter“:

            AutoMapper.Mapper.CreateMap<Pessoa, PessoaViewModel>()
                .ForMember(pvm => pvm.Sexo, map => map.ResolveUsing<SexoValueResolver>().FromMember(p => p.Sexo))
                .ForMember(pvm => pvm.ClasseIdade, map => map.ResolveUsing<ClassIdadeResolver>().FromMember(p => p.Idade))
                .ForMember(pvm => pvm.DataNascimento, map => map.AddFormatter<ShortDateFormatter>());

Execute o aplicativo novamente e veja que agora a data será formatada sem a hora:

Concluindo

Após essas explicações e os exemplos apresentados, acredito que tenha ficado claro para que serve o AutoMapper, certo? A grande maioriados exemplos que eu encontrei na minha pesquisa foram relacionados ao mapeamento Model / ViewModel no ASP.NET MVC. Infelizmente eu não consegui visualizar como o AutoMapper pode ser utilizado em aplicações desenvolvidas com WPF + MVVM, uma vez que, nesse tipo de aplicação, ao implementarmos a interface INotifyPropertyChanged, nós provavelmente faremos um “wrapping” direto das propriedades do Model na nossa ViewModel. Mas, se alguém tiver um exemplo prático de como utilizar o AutoMapper em aplicações desktop, por favor, me avise nos comentários!

Na minha pesquisa eu encontrei também alguns artigos com desvantagens do AutoMapper. Um artigo sugere que não utilizemos o AutoMapper na camada de acesso a dados. Outro artigo recomenda a não utilização do AutoMapper porque ele não é “refactor-friendly. E, por fim, um outro artigo faz uma análise de performance com o AutoMapper vs cópia manual dos dados, e a diferença não é animadora (o AutoMapper demorou sete vezes mais tempo!).

Mas, de qualquer maneira, o importante é que agora você (e eu!) sabemos que o AutoMapper existe e como ele funciona. Se quiser saber maiores detalhes sobre essa biblioteca, dê uma olhada na página oficial do AutoMapper. Além disso, o código da biblioteca está hospedado no GitHub, então, caso você fique curioso, é possível entender exatamente como o AutoMapper foi implementado.

Por fim, para ficar por dentro de todas as novidades publicadas aqui no meu site, inscreva-se na minha newsletter. Dessa forma você recebe um e-mail toda semana comentando sobre o último artigo lançado, além de receber dicas extras que eu só compartilho por e-mail. Inscreva-se agora mesmo clicando aqui ou utilizando o formulário abaixo.

Até a próxima!

André Lima

Newsletter do André Lima

* indicates required



Powered by MailChimp

11 thoughts on “Para que serve o AutoMapper?

  • […] implementa essa funcionalidade. O mesmo vale para leitura de RSS, manipulação de arquivos zip, mapeamento de dados entre objetos, manipulação de PDFs, […]

  • Renato disse:

    Parabéns pelo artigo sobre o AutoMapper me ajudou a resolver uma situação no projeto o qual estou trabalhando além de sanar varias duvidas que tinha quanto o seu uso. Grande abraço.

  • Wesley disse:

    Parabéns pelo artigo sobre o AutoMapper e pelo blog pois contém diversos conteúdo interessante. Muito bem explicado com o exemplo ficou bem fácil de entender AutoMapper. Abraço!

    • andrealveslima disse:

      Olá Wesley, muito obrigado pelo comentário! Fico feliz por ter conseguido te ajudar de alguma maneira.. :)

      Qualquer dúvida, é só entrar em contato..

      Abraço!
      André Lima

  • Luiz Benincasa Júnior disse:

    Parabéns, muito util seu post.

  • Marinpietri disse:

    Parabens pelo post…
    Muito obrigado

  • Guilherme Seabra disse:

    Bom dia Rodrigo, sigo bastante seus artigos e estou no desenvolvimento de um projeto Core com Angular utilizando tudo de boas práticas porém no decorrer do projeto estou vendo que não havia tanta necessidade utilizar o AutoMapper, quais os pontos positivos em utilizar o mapper em um projeto de grande porte levando em consideração que a empresa quer agilidade nas entregas tentando ao máximo não utilizar uma arquitetura meia boca como você mesmo menciona.

    • andrealveslima disse:

      Olá Guilherme!

      Na verdade você tem que ver caso a caso se compensa ou não utilizá-lo.. Se você está trabalhando com ViewModels e tem que ficar copiando valores dos seus Models para as suas ViewModels, aí compensa utilizar o AutoMapper para ele fazer esse trabalho mais automaticamente.. Agora, se você não utiliza essa estrutura e não tem que ficar copiando dados de um lado pro outro, não faz sentido utilizá-lo..

      Abraço!
      André Lima

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *