¿Dónde colocar AutoMapper.CreateMaps?

216

Estoy usando AutoMapperen una ASP.NET MVCaplicación. Me dijeron que debía mudarme a AutoMapper.CreateMapotra parte ya que tienen mucha sobrecarga. No estoy muy seguro de cómo diseñar mi aplicación para poner estas llamadas en solo 1 lugar.

Tengo una capa web, una capa de servicio y una capa de datos. Cada uno un proyecto propio. Yo uso Ninjectpara DI todo. Lo utilizaré AutoMappertanto en la web como en las capas de servicio.

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

Shawn Mclean
fuente

Respuestas:

219

No importa, siempre y cuando 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 Xestá la capa.

El Configure()método luego llama a privatemétodos 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<User,UserViewModel>();
   } 

   // ... etc
}

Creamos un método para cada "agregado" (Usuario, Publicar), para que las cosas estén bien separadas.

Entonces tu Global.asax:

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

Es algo así como una "interfaz de palabras": no puede imponerla, 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<User,UserViewModel>();
    }
}

Mucho más limpio / más robusto.

RPM1984
fuente
2
@ AliRızaAdıyahşi Ambos proyectos deben tener un archivo de mapeo. Core debería tener AutoMapperCoreConfiguration, y la interfaz de usuario debería tener AutoMapperWebConfiguration. La configuración web debe agregar los perfiles de la configuración Core.
RPM1984
77
¿Llamar Mapper.Initializeen cada clase de configuración sobrescribe los perfiles anteriores agregados? Si es así, ¿qué se debe usar en lugar de Inicializar?
Cody
44
¿No hace esto que su proyecto de API web tenga una referencia a sus capas de servicio y dominio?
Chazt3n
3
Si tengo Web -> Servicio -> BLL -> DAL. Mis entidades están en mi DAL. No quiero dar una referencia a mi DAL desde la web o el Servicio. ¿Cómo lo inicializo?
Vyache
19
A partir de AutoMapper 4.2 Mapper.CreateMap()ahora está obsoleto. '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.'. ¿Cómo actualizaría su ejemplo para cumplir con los nuevos requisitos?
ᴍᴀᴛᴛ ʙᴀᴋᴇʀ
34

Realmente puede ponerlo en cualquier lugar siempre que su proyecto web haga referencia al ensamblaje en el que se encuentra. En su situación, lo pondría en la capa de servicio, ya que será accesible para la capa web y la capa de servicio y más tarde si decide hacer una aplicación de consola o está haciendo un proyecto de prueba unitaria, la configuración de mapeo 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<Object1, Object2>();
         // 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.

¿La configuración de Automapper en Bootstrapper viola el principio abierto-cerrado?

Brett Allred
fuente
13
Esta. Cada paso hacia una arquitectura "hardcore" adecuada parece implicar exponencialmente más código. Esto es facil; será suficiente para el 99.9% de los codificadores por ahí; y sus compañeros de trabajo apreciarán la simplicidad. Sí, todos deberían leer el tema relacionado con el principio de Abierto-Cerrado, pero todos también deberían pensar en la compensación.
anon
¿Dónde has creado la clase AutoMapperBootStrapper?
user6395764
16

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

Adoptaría un enfoque similar al de Thoai. Pero usaría la SelfProfiler<>clase incorporada para manejar los mapas, luego usaría la Mapper.SelfConfigurefunción 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 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<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 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 global.asax.cs: AutoMapperConfiguration.Initialize()

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

código de progresión
fuente
3
Solo para su información, la clase SelfProfiler se ha ido desde Automapper v2.
Matt Honeycutt
15

Para aquellos de ustedes que se adhieren a lo siguiente:

  1. usando un contenedor ioc
  2. no me gusta abrirme para esto
  3. no me gusta un archivo de configuración monolítico

Hice un combo entre perfiles y aproveché mi contenedor ioc:

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

Ejemplo de configuración:

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

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<List<ViewModels.TagStatusViewModel>>(response)); 
    }
}

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

Mario
fuente
14

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

Puede instalar a WebActivatortravés de NuGet.

ravy amiry
fuente
2
Recientemente llegué a la misma conclusión. Mantiene su código de creación de mapas cerca del código que lo consume. Este método hace que un controlador MVC sea mucho más fácil de mantener.
mfras3r
¿Cómo lo comienzo en cualquier lugar? ¿Puedes dar un ejemplo? Los enlaces de tu blog no funcionan ...
Vyache
1
@Vyache está bastante claro! en el MyModule1proyecto (o como se llame el nombre de su proyecto) simplemente cree una clase llamada InitMapInModule1y coloque el código dentro del archivo; para otros módulos, haga lo mismo.
ravy amiry
Te tengo, en realidad solo lo intenté. Agregué WebActivator de Nuget a mi biblioteca de clases (DAL) y creé una clase estática AutoMapperDalConfiguration allí creé la implementación @ RPM1984 para configurar e inicializar los mapas. No estoy usando el perfil a través de. Gracias.
Vyache
10

Además de la mejor respuesta, una buena manera es usar Autofac IoC liberary para agregar algo de automatización. Con esto simplemente define sus 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<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);
                    });                    
                }

            }

        }
    }

y llamando a esta línea en Application_Startmétodo:

MapperConfig.Configure();

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

Mahmoud Moravej
fuente
7

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 desea siguiendo esta convención. Además, al crear la clase de mapeo, puede hacer referencia a las propiedades de ViewModel más rápido 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<User, UserViewModel>();
    }
}
Van Thoai Nguyen
fuente
99
¿Como lo llamas?
Shawn Mclean
1
Seguiría una regla de clase por archivo: stackoverflow.com/q/2434990/1158845
Umair
Una descripción similar se describe en el blog de Velir Organizando configuraciones de mapas de AutoMapper en MVC
xmedeko
5

Desde la nueva versión de AutoMapper que utiliza el método estático Mapper.Map () está en desuso. Por lo tanto, 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

Andrey Burykin
fuente
3

Para aquellos que están (perdidos) usando:

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

Así es como logré integrar AutoMapper en la " nueva forma ". Además, un enorme gracias 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<User, UserViewModel>();
    }

    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<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 - Luego, en su controlador, simplemente inyecte como generalmente una interfaz IMapper:

private readonly IMapper mapper;

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

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
jpgrassi
fuente
Con un pequeño ajuste a algunos de los detalles, este enfoque también funciona excelentemente con MVC, ¡gracias, muchacho!
Nick Coad
agregue un ejemplo de demostración en github
Mohammad Daliri
3

Para programadores de vb.net que usan 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)
Roland
fuente
Intenté su respuesta, pero muestra un error en esta línea: Dim config = New MapperConfiguration (// La resolución de sobrecarga falló porque no se puede llamar a 'Nuevo' accesible con estos argumentos: 'Public Overloads Sub New (configurationExpression As MapperConfigurationExpression) Can por favor, ¿me ayudas en eso?
barsan
@barsan: ¿Ha configurado correctamente todas las clases de perfil (UserProfile y PostProfile)? Para mí funciona con Automapper versión 5.2.0.
roland
Se lanza la nueva versión 6.0. Entonces el Protected Overrides Sub Configure()está en desuso. Todo sigue igual pero esta línea debería ser:Public Sub New()
roland