Onde colocar o AutoMapper.CreateMaps?

Estou usando o AutoMapper em um ASP.NET MVC . Foi-me dito que eu deveria mover o AutoMapper.CreateMap outro lugar, pois eles têm muita sobrecarga. Não tenho certeza de como projetar meu aplicativo para colocar essas chamadas em apenas um lugar.

Eu tenho uma camada web, camada de serviço e uma camada de dados. Cada um é um projeto próprio. Eu uso o Ninject para tudo. Utilizarei o AutoMapper nas camadas da Web e de serviço.

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

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

Nossa convenção é que cada “camada” (web, serviços, dados) tem um único arquivo chamado AutoMapperXConfiguration.cs , com um único método chamado Configure() , onde X é a camada.

O método Configure() então chama methods private para cada área.

Veja um exemplo da nossa configuração da camada da web:

 public static class AutoMapperWebConfiguration { public static void Configure() { ConfigureUserMapping(); ConfigurePostMapping(); } private static void ConfigureUserMapping() { Mapper.CreateMap(); } // ... etc } 

Criamos um método para cada “agregado” (Usuário, Post), então as coisas são separadas bem.

Então seu Global.asax :

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

É como uma “interface de palavras” – não é possível aplicá-lo, mas você espera, então você pode codificar (e refatorar) se necessário.

EDITAR:

Apenas pensei em mencionar que agora uso os 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(); } } 

Muito mais limpo / mais robusto.

Você pode realmente colocá-lo em qualquer lugar desde que o seu projeto referencia o conjunto em que ele se encontra. Na sua situação, eu o colocaria na camada de serviço, pois ele seria acessível pela camada da Web e pela camada de serviço e, posteriormente, se você decidir fazer um aplicativo de console ou você está fazendo um projeto de teste de unidade a configuração de mapeamento estará disponível a partir desses projetos também.

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

Arquivo AutoMapperBootStrapper.cs

 public static class AutoMapperBootStrapper { public static void BootStrap() { AutoMapper.CreateMap(); // So on... } } 

Global.asax no início do aplicativo

apenas ligue

 AutoMapperBootStrapper.BootStrap(); 

Agora, algumas pessoas argumentarão contra esse método, violando alguns princípios do SOLID, que eles têm argumentos válidos. Aqui estão eles para a leitura.

Configurar o Automapper no Bootstrapper viola o Princípio Aberto-Fechado?

Atualização: A abordagem postada aqui não é mais válida, pois o SelfProfiler foi removido a partir do AutoMapper v2.

Eu tomaria uma abordagem semelhante como Thoai. Mas eu usaria a class SelfProfiler<> para manipular os mapas e, em seguida, usar a function Mapper.SelfConfigure para inicializar.

Usando este object como a 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 { protected override void DescribeConfiguration(IMappingExpression map) { //This maps by convention, so no configuration needed } } public class UserWithAgeViewModelProfile : SelfProfiler { protected override void DescribeConfiguration(IMappingExpression map) { //This map needs a little configuration map.ForMember(d => d.Age, o => o.MapFrom(s => DateTime.Now.Year - s.BirthDate.Year)); } } 

Para inicializar em seu aplicativo, crie esta class

  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 classs de mapeamento onde elas fazem sentido para você e não se preocupar com uma class de mapeamento monolítica.

Para aqueles de vocês que seguem o seguinte:

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

Eu fiz um combo entre perfis e aproveitando meu container ioc:

Configuração IoC:

 public class Automapper : IWindsorInstaller { public void Install(IWindsorContainer container, IConfigurationStore store) { container.Register(Classes.FromThisAssembly().BasedOn().WithServiceBase()); container.Register(Component.For().UsingFactoryMethod(k => { Profile[] profiles = k.ResolveAll(); 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(); } } 

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>(response)); } } 

A desvantagem é que você deve referenciar o Mapeador pela interface IMappingEngine em vez do Mapper estático, mas essa é uma convenção com a qual posso conviver.

Todas as soluções acima fornecem um método estático para chamar (de app_start ou qualquer outro) que ele deve chamar outros methods para configurar partes da configuração de mapeamento. Mas, se você tiver um aplicativo modular, que os módulos podem ser plugados e desconectados do aplicativo a qualquer momento, essas soluções não funcionam. Sugiro usar a biblioteca do WebActivator que pode registrar alguns methods para serem executados em app_pre_start e app_post_start qualquer lugar:

 // in MyModule1.dll public class InitMapInModule1 { static void Init() { Mapper.CreateMap(); // other stuffs } } [assembly: PreApplicationStartMethod(typeof(InitMapInModule1), "Init")] // in MyModule2.dll public class InitMapInModule2 { static void Init() { Mapper.CreateMap(); // other stuffs } } [assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")] // in MyModule3.dll public class InitMapInModule3 { static void Init() { Mapper.CreateMap(); // other stuffs } } [assembly: PreApplicationStartMethod(typeof(InitMapInModule2), "Init")] // and in other libraries... 

Você pode instalar o WebActivator via NuGet.

Além da melhor resposta, uma boa maneira é usar o Autofac IoC para liberar alguma automação. Com isso, você apenas define seus perfis, independentemente de 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(); var container = builder.Build(); using (var scope = container.BeginLifetimeScope()) { var profiles = container.Resolve>(); foreach (var profile in profiles) { Mapper.Initialize(cfg => { cfg.AddProfile(profile); }); } } } } 

e chamando esta linha no método Application_Start :

 MapperConfig.Configure(); 

O código acima encontra todas as subclasss de perfil e as inicia automaticamente.

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

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

Assim, sua class de modelo de visualizaçã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(); } } 

A partir da nova versão do AutoMapper usando o método estático, Mapper.Map () está obsoleto. Assim, você pode adicionar 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

Para programadores vb.net usando 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 

AutoMapperConfiguration:

 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) 

Para aqueles que estão (perdidos) usando:

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

Aqui está como eu consegui integrar o AutoMapper na ” nova maneira “. Além disso, um enorme graças a esta resposta (e pergunta)

1 – Criei uma pasta no projeto WebAPI chamado “ProfileMappers”. Nesta pasta eu coloco todas as minhas classs de perfis que criam meus mapeamentos:

 public class EntityToViewModelProfile : Profile { protected override void Configure() { CreateMap(); } public override string ProfileName { get { return this.GetType().Name; } } } 

2 – No meu App_Start, eu tenho um SimpleInjectorApiInitializer que configura meu container 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(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(() => config.CreateMapper(container.GetInstance)); //If you need the config for LinqProjections, inject also the config //container.RegisterSingleton(config); } 

3 – Startup.cs

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

4 – Então, no seu controlador, basta injetar normalmente uma interface IMapper:

 private readonly IMapper mapper; public AccountController( IMapper mapper) { this.mapper = mapper; } //Using.. var userEntity = mapper.Map(entity);