Onde colocar o AutoMapper.CreateMaps?

216

Estou usando AutoMapperem um ASP.NET MVCaplicativo. Foi-me dito que eu deveria me mudar para AutoMapper.CreateMapoutro lugar, pois eles têm muita sobrecarga. Não tenho muita certeza de como projetar meu aplicativo para fazer essas chamadas em apenas um lugar.

Eu tenho uma camada web, camada de serviço e uma camada de dados. Cada um projeto próprio. Eu uso Ninjectpara DI tudo. Utilizarei AutoMappernas camadas web e de serviço.

Então, qual é a sua configuração para AutoMappero CreateMap? Onde você coloca isso? Como você chama isso?

Shawn Mclean
fonte

Respostas:

219

Não importa, desde que seja uma classe estática. É tudo sobre convenções .

Nossa convenção é que cada "camada" (web, serviços, dados) possui um único arquivo chamado AutoMapperXConfiguration.cs, com um único método chamado Configure(), onde Xestá a camada.

O Configure()método chama privatemétodos para cada área.

Aqui está um exemplo de nossa configuração de camada da web:

public static class AutoMapperWebConfiguration
{
   public static void Configure()
   {
      ConfigureUserMapping();
      ConfigurePostMapping();
   }

   private static void ConfigureUserMapping()
   {
      Mapper.CreateMap<User,UserViewModel>();
   } 

   // ... etc
}

Criamos um método para cada "agregado" (Usuário, Postagem), para que as coisas sejam bem separadas.

Então seu Global.asax:

AutoMapperWebConfiguration.Configure();
AutoMapperServicesConfiguration.Configure();
AutoMapperDomainConfiguration.Configure();
// etc

É como uma "interface de palavras" - não pode ser aplicada, mas você espera, portanto pode codificar (e refatorar), se necessário.

EDITAR:

Apenas pensei em mencionar que agora uso perfis do AutoMapper , então o exemplo acima se torna:

public static class AutoMapperWebConfiguration
{
   public static void Configure()
   {
      Mapper.Initialize(cfg =>
      {
        cfg.AddProfile(new UserProfile());
        cfg.AddProfile(new PostProfile());
      });
   }
}

public class UserProfile : Profile
{
    protected override void Configure()
    {
         Mapper.CreateMap<User,UserViewModel>();
    }
}

Muito mais limpo / mais robusto.

RPM1984
fonte
2
@ AliRızaAdıyahşi Ambos os projetos devem ter um arquivo de mapeamento. O núcleo deve ter AutoMapperCoreConfiguration e a interface do usuário deve ter AutoMapperWebConfiguration. A configuração da web deve adicionar os perfis da configuração principal.
RPM1984
7
A chamada Mapper.Initializeem cada classe de configuração substitui os perfis anteriores adicionados? Se sim, o que deve ser usado em vez de inicializar?
Cody
4
Isso não faz com que seu projeto de API da web tenha uma referência às suas camadas de serviço e domínio?
precisa saber é o seguinte
3
Se eu tiver Web -> Serviço -> BLL -> DAL. Minhas entidades estão no meu DAL. Não quero fazer referência ao meu DAL na Web ou no Serviço. Como inicializá-lo?
Vyache
19
A partir do AutoMapper 4.2 Mapper.CreateMap()agora está obsoleta. 'Mapper.Map<TSource, TDestination>(TSource, TDestination)' is obsolete: 'The static API will be removed in version 5.0. Use a MapperConfiguration instance and store statically as needed. Use CreateMapper to create a mapper instance.'. Como você atualizaria seu exemplo para estar em conformidade com os novos requisitos?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
34

Você pode realmente colocá-lo em qualquer lugar, desde que o seu projeto da Web faça referência à montagem em que está. Na sua situação, eu o colocaria na camada de serviço, pois isso será acessível pela camada da Web e pela camada de serviço e, posteriormente, se você decidir em um aplicativo de console ou em um projeto de teste de unidade, a configuração de mapeamento também estará disponível nesses projetos.

No seu Global.asax, você chamará o método que define todos os seus mapas. Ver abaixo:

Arquivo AutoMapperBootStrapper.cs

public static class AutoMapperBootStrapper
{
     public static void BootStrap()
     {  
         AutoMapper.CreateMap<Object1, Object2>();
         // So on...


     }
}

Global.asax no início do aplicativo

apenas ligue

AutoMapperBootStrapper.BootStrap();

Agora, algumas pessoas argumentam contra esse método viola alguns princípios do SOLID, com argumentos válidos. Aqui estão eles para a leitura.

Configurar o Automapper no Bootstrapper viola o princípio de aberto-fechado?

Brett Allred
fonte
13
Este. Cada passo em direção à arquitetura "hardcore" adequada parece envolver exponencialmente mais código. Isso é facil; será suficiente para 99,9% dos codificadores por aí; e seus colegas de trabalho apreciarão a simplicidade. Sim, todos devem ler a questão sobre o princípio Aberto-Fechado, mas todos também devem pensar na troca.
anon
onde você criou a classe AutoMapperBootStrapper?
user6395764
16

Atualização: a abordagem publicada aqui não é mais válida, pois SelfProfilerfoi removida a partir do AutoMapper v2.

Eu adotaria uma abordagem semelhante à de Thoai. Mas eu usaria a SelfProfiler<>classe interna para manipular os mapas e, em seguida, usaria a Mapper.SelfConfigurefunção para inicializar.

Usando este objeto como fonte:

public class User
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
    public DateTime BirthDate { get; set; }
    public string GetFullName()
    {
        return string.Format("{0} {1}", FirstName, LastName);
    }
}

E estes como o destino:

public class UserViewModel
{
    public int Id { get; set; }
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

public class UserWithAgeViewModel
{
    public int Id { get; set; }
    public string FullName { get; set; }
    public int Age { get; set; }
}

Você pode criar estes perfis:

public class UserViewModelProfile : SelfProfiler<User,UserViewModel>
{
    protected override void DescribeConfiguration(IMappingExpression<User, UserViewModel> map)
    {
    //This maps by convention, so no configuration needed
    }
}

public class UserWithAgeViewModelProfile : SelfProfiler<User, UserWithAgeViewModel>
{
    protected override void DescribeConfiguration(IMappingExpression<User, UserWithAgeViewModel> map)
    {
    //This map needs a little configuration
        map.ForMember(d => d.Age, o => o.MapFrom(s => DateTime.Now.Year - s.BirthDate.Year));
    }
}

Para inicializar no seu aplicativo, crie esta classe

 public class AutoMapperConfiguration
 {
      public static void Initialize()
      {
          Mapper.Initialize(x=>
          {
              x.SelfConfigure(typeof (UserViewModel).Assembly);
              // add assemblies as necessary
          });
      }
 }

Adicione esta linha ao seu arquivo global.asax.cs: AutoMapperConfiguration.Initialize()

Agora você pode colocar suas classes de mapeamento onde elas fazem sentido para você e não se preocupar com uma classe de mapeamento monolítico.

codeprogression
fonte
3
Apenas para sua informação, a classe SelfProfiler desapareceu desde o Automapper v2.
Matt Honeycutt
15

Para aqueles que seguem o seguinte:

  1. usando um contêiner ioc
  2. não gosto de abrir fechado para isso
  3. não gosta de um arquivo de configuração monolítico

Fiz uma combinação entre perfis e alavancando meu contêiner ioc:

Configuração IoC:

public class Automapper : IWindsorInstaller
{
    public void Install(IWindsorContainer container, IConfigurationStore store)
    {
        container.Register(Classes.FromThisAssembly().BasedOn<Profile>().WithServiceBase());

        container.Register(Component.For<IMappingEngine>().UsingFactoryMethod(k =>
        {
            Profile[] profiles = k.ResolveAll<Profile>();

            Mapper.Initialize(cfg =>
            {
                foreach (var profile in profiles)
                {
                    cfg.AddProfile(profile);
                }
            });

            profiles.ForEach(k.ReleaseComponent);

            return Mapper.Engine;
        }));
    }
}

Exemplo de configuração:

public class TagStatusViewModelMappings : Profile
{
    protected override void Configure()
    {
        Mapper.CreateMap<Service.Contracts.TagStatusViewModel, TagStatusViewModel>();
    }
}

Exemplo de uso:

public class TagStatusController : ApiController
{
    private readonly IFooService _service;
    private readonly IMappingEngine _mapper;

    public TagStatusController(IFooService service, IMappingEngine mapper)
    {
        _service = service;
        _mapper = mapper;
    }

    [Route("")]
    public HttpResponseMessage Get()
    {
        var response = _service.GetTagStatus();

        return Request.CreateResponse(HttpStatusCode.Accepted, _mapper.Map<List<ViewModels.TagStatusViewModel>>(response)); 
    }
}

A desvantagem é que você precisa fazer referência ao Mapper pela interface IMappingEngine em vez do Mapper estático, mas é uma convenção com a qual posso conviver.

Marius
fonte
14

Todas as soluções acima fornecem um método estático para chamar (de app_start ou qualquer outro local) que deve chamar outros métodos para configurar partes da configuração de mapeamento. Mas, se você tiver um aplicativo modular, esses módulos poderão se conectar e sair do aplicativo a qualquer momento, essas soluções não funcionarão. Eu sugiro usar a WebActivatorbiblioteca que pode registrar alguns métodos para executar app_pre_starte em app_post_startqualquer lugar:

// in MyModule1.dll
public class InitMapInModule1 {
    static void Init() {
        Mapper.CreateMap<User, UserViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule1), "Init")]

// in MyModule2.dll
public class InitMapInModule2 {
    static void Init() {
        Mapper.CreateMap<Blog, BlogViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]

// in MyModule3.dll
public class InitMapInModule3 {
    static void Init() {
        Mapper.CreateMap<Comment, CommentViewModel>();
        // other stuffs
    }
}
[assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")]

// and in other libraries...

Você pode instalar WebActivatorvia NuGet.

ruivo amiry
fonte
2
Recentemente, cheguei à mesma conclusão. Ele mantém seu código de criação de mapa próximo ao código que o consome. Este método torna um controlador MVC muito mais sustentável.
mfras3r
Como inicio em qualquer lugar, você pode fornecer um exemplo? Os links do seu blog não funcionam ...
Vyache
1
@ Vyache é bem claro! no MyModule1projeto (ou seja qual for o nome do seu projeto), basta criar uma classe chamada InitMapInModule1e colocar o código dentro do arquivo; para outros módulos, faça o mesmo.
Ravy amiry
Entendi, eu realmente tentei. Adicionei o WebActivator do Nuget à minha biblioteca de classes (DAL) e criei uma classe estática AutoMapperDalConfiguration. Lá, criei a implementação @ RPM1984 para configurar e inicializar os mapas. Não estou usando o perfil. Obrigado.
Vyache
10

Além da melhor resposta, uma boa maneira é usar o Autofac IoC liberary para adicionar alguma automação. Com isso, você apenas define seus perfis, independentemente das iniciações.

   public static class MapperConfig
    {
        internal static void Configure()
        {

            var myAssembly = Assembly.GetExecutingAssembly();

            var builder = new ContainerBuilder();

            builder.RegisterAssemblyTypes(myAssembly)
                .Where(t => t.IsSubclassOf(typeof(Profile))).As<Profile>();

            var container = builder.Build();

            using (var scope = container.BeginLifetimeScope())
            {
                var profiles = container.Resolve<IEnumerable<Profile>>();

                foreach (var profile in profiles)
                {
                    Mapper.Initialize(cfg =>
                    {
                        cfg.AddProfile(profile);
                    });                    
                }

            }

        }
    }

e chamando esta linha no Application_Startmétodo:

MapperConfig.Configure();

O código acima localiza todas as subclasses de perfil e as inicia automaticamente.

Mahmoud Moravej
fonte
7

Colocar toda a lógica de mapeamento em um local não é uma boa prática para mim. Porque a classe de mapeamento será extremamente grande e muito difícil de manter.

Eu recomendo colocar o material de mapeamento junto com a classe ViewModel no mesmo arquivo cs. Você pode navegar facilmente para a definição de mapeamento desejada após esta convenção. Além disso, ao criar a classe de mapeamento, você pode fazer referência às propriedades do ViewModel mais rapidamente, pois estão no mesmo arquivo.

Portanto, sua classe de modelo de exibição será semelhante a:

public class UserViewModel
{
    public ObjectId Id { get; set; }

    public string Firstname { get; set; }

    public string Lastname { get; set; }

    public string Email { get; set; }

    public string Password { get; set; }
}

public class UserViewModelMapping : IBootStrapper // Whatever
{
    public void Start()
    {
        Mapper.CreateMap<User, UserViewModel>();
    }
}
Van Thoai Nguyen
fonte
9
Como você chama isso?
precisa
1
Eu seguiria uma classe por regra de arquivo: stackoverflow.com/q/2434990/1158845
Umair
Soultion semelhante é descrita no blog do Velir Configurações Mapa do Organizador AutoMapper no MVC
xmedeko
5

A partir da nova versão do AutoMapper, o método estático Mapper.Map () foi preterido. Portanto, você pode adicionar o MapperConfiguration como propriedade estática ao MvcApplication (Global.asax.cs) e usá-lo para criar uma instância do Mapper.

App_Start

public class MapperConfig
{
    public static MapperConfiguration MapperConfiguration()
    {
        return new MapperConfiguration(_ =>
        {
            _.AddProfile(new FileProfile());
            _.AddProfile(new ChartProfile());
        });
    }
}

Global.asax.cs

public class MvcApplication : System.Web.HttpApplication
{
    internal static MapperConfiguration MapperConfiguration { get; private set; }

    protected void Application_Start()
    {
        MapperConfiguration = MapperConfig.MapperConfiguration();
        ...
    }
}

BaseController.cs

    public class BaseController : Controller
    {
        //
        // GET: /Base/
        private IMapper _mapper = null;
        protected IMapper Mapper
        {
            get
            {
                if (_mapper == null) _mapper = MvcApplication.MapperConfiguration.CreateMapper();
                return _mapper;
            }
        }
    }

https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API

Andrey Burykin
fonte
3

Para aqueles que estão (perdidos) usando:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (com perfis)

Veja como eu consegui integrar o AutoMapper da " nova maneira ". Além disso, um enorme agradecimento a esta resposta (e pergunta)

1 - Criou uma pasta no projeto WebAPI chamada "ProfileMappers". Nesta pasta, coloco todas as minhas classes de perfis que criam meus mapeamentos:

public class EntityToViewModelProfile : Profile
{
    protected override void Configure()
    {
        CreateMap<User, UserViewModel>();
    }

    public override string ProfileName
    {
        get
        {
            return this.GetType().Name;
        }
    }
}

2 - No meu App_Start, tenho um SimpleInjectorApiInitializer que configura meu contêiner SimpleInjector:

public static Container Initialize(HttpConfiguration httpConfig)
{
    var container = new Container();

    container.Options.DefaultScopedLifestyle = new WebApiRequestLifestyle();

    //Register Installers
    Register(container);

    container.RegisterWebApiControllers(GlobalConfiguration.Configuration);

    //Verify container
    container.Verify();

    //Set SimpleInjector as the Dependency Resolver for the API
    GlobalConfiguration.Configuration.DependencyResolver =
       new SimpleInjectorWebApiDependencyResolver(container);

    httpConfig.DependencyResolver = new SimpleInjectorWebApiDependencyResolver(container);

    return container;
}

private static void Register(Container container)
{
     container.Register<ISingleton, Singleton>(Lifestyle.Singleton);

    //Get all my Profiles from the assembly (in my case was the webapi)
    var profiles =  from t in typeof(SimpleInjectorApiInitializer).Assembly.GetTypes()
                    where typeof(Profile).IsAssignableFrom(t)
                    select (Profile)Activator.CreateInstance(t);

    //add all profiles found to the MapperConfiguration
    var config = new MapperConfiguration(cfg =>
    {
        foreach (var profile in profiles)
        {
            cfg.AddProfile(profile);
        }
    });

    //Register IMapper instance in the container.
    container.Register<IMapper>(() => config.CreateMapper(container.GetInstance));

    //If you need the config for LinqProjections, inject also the config
    //container.RegisterSingleton<MapperConfiguration>(config);
}

3 - Startup.cs

//Just call the Initialize method on the SimpleInjector class above
var container = SimpleInjectorApiInitializer.Initialize(configuration);

4 - Em seguida, no seu controlador, basta injetar como normalmente uma interface IMapper:

private readonly IMapper mapper;

public AccountController( IMapper mapper)
{
    this.mapper = mapper;
}

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
jpgrassi
fonte
Com um pouco de ajustes em alguns detalhes, essa abordagem também funciona muito bem com o MVC - obrigado, cara!
perfil completo de Nick Coad
por favor, adicione um exemplo de demonstração no github
Mohammad Daliri 6/19
3

Para programadores do vb.net que usam a nova versão (5.x) do AutoMapper.

Global.asax.vb:

Public Class MvcApplication
    Inherits System.Web.HttpApplication

    Protected Sub Application_Start()
        AutoMapperConfiguration.Configure()
    End Sub
End Class

Configuração do AutoMapper:

Imports AutoMapper

Module AutoMapperConfiguration
    Public MapperConfiguration As IMapper
    Public Sub Configure()
        Dim config = New MapperConfiguration(
            Sub(cfg)
                cfg.AddProfile(New UserProfile())
                cfg.AddProfile(New PostProfile())
            End Sub)
        MapperConfiguration = config.CreateMapper()
    End Sub
End Module

Perfis:

Public Class UserProfile
    Inherits AutoMapper.Profile
    Protected Overrides Sub Configure()
        Me.CreateMap(Of User, UserViewModel)()
    End Sub
End Class

Mapeamento:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)
Roland
fonte
Eu tentei sua resposta, mas ele está mostrando erro nesta linha: Dim config = New MapperConfiguration (// Falha na resolução da sobrecarga porque nenhum 'Novo' acessível pode ser chamado com estes argumentos: 'Public Overloads Sub New (configurationExpression As MapperConfigurationExpression) Can por favor me ajude nisso? #
barsan
@ Bararsan: Você configurou todas as classes de perfil corretamente (UserProfile e PostProfile)? Para mim, funciona com o Automapper versão 5.2.0.
Roland
A nova versão 6.0 é lançada. Portanto, o Protected Overrides Sub Configure()está obsoleto. Estadias Tudo o mesmo, mas esta linha deve ser:Public Sub New()
roland