Dónde colocar AutoMapper.¿CreateMaps?


Estoy usando AutoMapper en una aplicación ASP.NET MVC. Me dijeron que debería mover el AutoMapper.CreateMap a otra parte, ya que tienen una gran cantidad de 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, una capa de servicio y una capa de datos. Cada uno un proyecto propio. Yo uso Ninject para DI todo. Utilizaré AutoMapper tanto en capas web como de servicio.

Entonces, ¿cuál es su configuración para el CreateMap de AutoMapper? ¿Dónde lo pones? ¿Cómo lo llamas?

Author: Leniel Maccaferri, 2011-07-26

10 answers

No importa, siempre y cuando sea una clase estática. Se trata de convención .

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

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

   // ... etc
}

Creamos un método para cada "agregado" (Usuario, Publicación), para que las cosas se separen bien.

Entonces su Global.asax:

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

Es algo así como una "interfaz de palabras" - no se puede hacer cumplir, pero se espera, por lo que puede codificar (y refactorizar) si es necesario.

EDITAR:

Solo quería mencionar que ahora uso AutoMapper profiles , 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.

 210
Author: RPM1984,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-18 08:19:08

Realmente puede ponerlo en cualquier lugar siempre y cuando su proyecto web haga referencia al ensamblado en el que se encuentra. En su situación lo pondría en la capa de servicio, ya que será accesible por 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 de unidad, la configuración de asignación estará disponible desde esos proyectos también.

En su Global.asax entonces llamarás al método que establece todos tus mapas. Véase a continuación:

Archivo AutoMapperBootStrapper.cs

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


     }
}

Global.asax al iniciar la aplicación

Solo llama

AutoMapperBootStrapper.BootStrap();

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

¿Configurar Automapper en Bootstrapper viola el Principio Abierto-Cerrado?

 31
Author: Brett Allred,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 11:54:55

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

Adoptarí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; }
}

Puedes 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 esto 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 global.asax.archivo cs: AutoMapperConfiguration.Initialize()

Ahora puede colocar sus clases de asignación donde tengan sentido para usted y no preocuparse por una clase de asignación monolítica.

 15
Author: codeprogression,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-24 17:00:19

Para aquellos de ustedes que se adhieren a lo siguiente:

  1. utilizando un contenedor coi
  2. no me gusta romper abierto cerrado para esto
  3. no me gusta un archivo de configuración monolítico

Hice un combo entre perfiles y aprovechar mi contenedor ioc:

Configuración del CoI:

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 compensación es que tiene que hacer referencia al Mapeador por la interfaz IMappingEngine en lugar del Mapeador estático, pero es una convención con la que puedo vivir.

 15
Author: Marius,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-08-24 17:01:23

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

 14
Author: javad amiry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-12-14 16:50:59

Además de la mejor respuesta, una buena manera es usar Autofac Ioc liberary para agregar algo de automatización. Con esto solo define 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<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 el método Application_Start:

MapperConfig.Configure();

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

 10
Author: Mahmoud Moravej,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2014-07-27 13:39:25

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

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

Así que su punto de vista la clase modelo se verá como:

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>();
    }
}
 7
Author: Van Thoai Nguyen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-07-27 04:23:58

De la nueva versión de AutoMapper usando el método estático Mapper.Map() está obsoleto. Por lo tanto, puede agregar MapperConfiguration como propiedad estática a MvcApplication (Global.asax.cs) y usarlo para crear 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

 5
Author: Andrey Burykin,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-02-20 11:56:26

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

Configuración automática:

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

Mapeo:

Dim ViewUser = MapperConfiguration.Map(Of UserViewModel)(User)
 3
Author: roland,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-03-21 11:27:41

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". También, un Enorme gracias a esta respuesta (y pregunta)

1 - Se 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, yo tener 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-Inicio.cs

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

4-Luego, en su controlador simplemente inyecte como suele ser una interfaz IMapper:

private readonly IMapper mapper;

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

//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
 2
Author: jpgrassi,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-23 12:10:35