Dónde colocar AutoMapper.CreateMaps?

Estoy usando AutoMapper en una aplicación ASP.NET MVC . Me dijeron que debería mover el AutoMapper.CreateMap otro lugar ya que tienen muchos gastos generales. No estoy muy seguro de cómo diseñar mi aplicación para poner estas llamadas en solo 1 lugar.

Tengo una capa web, capa de servicio y una capa de datos. Cada uno un proyecto propio. Yo uso Ninject para DI todo. Utilizaré AutoMapper tanto en la web como en las capas de servicio.

Entonces, ¿cuál es su configuración para AutoMapper de AutoMapper? ¿Dónde lo pusiste? ¿Como lo llamas?

No importa, siempre que sea una clase estática. Se trata de convenciones .

Nuestra convención es que cada “capa” (web, servicios, datos) tiene un solo archivo llamado AutoMapperXConfiguration.cs , con un único método llamado Configure() , donde X es la capa.

El método Configure() luego llama a métodos private para cada área.

Aquí hay un ejemplo de nuestra configuración de nivel web:

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

Creamos un método para cada “agregado” (usuario, publicación), por lo que las cosas se separan muy bien.

Entonces tu Global.asax :

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

Es algo así como una “interfaz de palabras”: no se puede aplicar, pero lo espera, por lo que puede codificar (y refactorizar) si es necesario.

EDITAR:

Solo pensé en mencionar que ahora uso los perfiles de AutoMapper, por lo que el ejemplo anterior se convierte en:

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

Mucho más limpio / más robusto.

Puedes colocarlo en cualquier lugar siempre que tu proyecto web haga referencia al ensamblaje en el que se encuentra. En tu situación, lo pondría en la capa de servicio, ya que la capa web y la capa de servicio podrán acceder a él y, más adelante, si decides haga una aplicación de consola o está haciendo un proyecto de prueba de unidad, la configuración de asignación también estará disponible en esos proyectos.

En su Global.asax, llamará al método que establece todos sus mapas. Vea abajo:

Archivo AutoMapperBootStrapper.cs

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

Global.asax al inicio de la aplicación

solo llama

 AutoMapperBootStrapper.BootStrap(); 

Ahora, algunas personas argumentarán en contra de que este método viole algunos principios SÓLIDOS, que tienen argumentos válidos. Aquí están para la lectura.

Configurar Automapper en Bootstrapper viola el principio de Open-Closed?

Actualización: el enfoque publicado aquí no es más válido ya que SelfProfiler se ha eliminado a partir de AutoMapper v2.

Tomaría un enfoque similar al de Thoai. Pero usaría la clase incorporada SelfProfiler<> para manejar los mapas, luego usaría la función Mapper.SelfConfigure para inicializar.

Usando este objeto como fuente:

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

Y estos como el 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; } } 

Puede crear estos perfiles:

 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 en su aplicación, cree esta clase

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

Agregue esta línea a su archivo AutoMapperConfiguration.Initialize() : AutoMapperConfiguration.Initialize()

Ahora puede colocar sus clases de mapeo donde tengan sentido para usted y no preocuparse por una clase de mapeo monolítico.

Para aquellos de ustedes que se adhieren a lo siguiente:

  1. usando un contenedor ioc
  2. no me gusta romper abierto cerrado por esto
  3. no me gusta un archivo de configuración monolítico

Hice un combo entre perfiles y aprovechando mi contenedor ioc:

Configuración de 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; })); } } 

Ejemplo de configuración:

 public class TagStatusViewModelMappings : Profile { protected override void Configure() { Mapper.CreateMap(); } } 

Ejemplo 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)); } } 

La desventaja es que debes hacer referencia al asignador mediante la interfaz IMappingEngine en lugar del mapper estático, pero esa es una convención con la que puedo vivir.

Todas las soluciones anteriores proporcionan un método estático para llamar (desde app_start o en cualquier otro lugar) que debe llamar a otros métodos para configurar partes de la configuración de mapeo. Pero, si tiene una aplicación modular, que los módulos pueden conectarse y desconectarse en cualquier momento, estas soluciones no funcionan. Sugiero usar la biblioteca WebActivator que puede registrar algunos métodos para ejecutar en app_pre_start y app_post_start cualquier 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... 

Puede instalar WebActivator través de NuGet.

Además de la mejor respuesta, una buena forma es usar Autofac IoC liberary para agregar algo de automatización. Con esto, simplemente defines tus perfiles independientemente de las iniciaciones.

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

y llamando a esta línea en el método Application_Start :

 MapperConfig.Configure(); 

El código anterior encuentra todas las subclas de perfil y las inicia automáticamente.

Poner toda la lógica de mapeo en 1 ubicación no es una buena práctica para mí. Porque la clase de mapeo será extremadamente grande y muy difícil de mantener.

Recomiendo poner el material de mapeo junto con la clase ViewModel en el mismo archivo cs. Puede navegar fácilmente a la definición de mapeo que desee siguiendo esta convención. Además, al crear la clase de asignación, puede hacer referencia a las propiedades de ViewModel más rápidamente ya que están en el mismo archivo.

Entonces su clase de modelo de vista se verá así:

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

Desde la nueva versión de AutoMapper utilizando el método estático, Mapper.Map () está en desuso. Entonces puede agregar MapperConfiguration como propiedad estática a MvcApplication (Global.asax.cs) y usarlo para crear una instancia de 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 los progtwigdores de vb.net que utilizan la nueva versión (5.x) de 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 

Perfiles:

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

Cartografía:

 Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User) 

Para aquellos que están (perdidos) usando:

  • WebAPI 2
  • SimpleInjector 3.1
  • AutoMapper 4.2.1 (con perfiles)

Así es cómo logré integrar AutoMapper en la ” nueva forma “. Además, un gran agradecimiento a esta respuesta (y pregunta)

1 – Creó una carpeta en el proyecto WebAPI llamada “ProfileMappers”. En esta carpeta coloco todas mis clases de perfiles que crean mis asignaciones:

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

2 – En mi App_Start, tengo un SimpleInjectorApiInitializer que configura mi contenedor 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 – Luego, en su controlador simplemente inyecte como usualmente una interfaz IMapper:

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