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?
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.
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?
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.
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:
- utilizando un contenedor coi
- no me gusta romper abierto cerrado para esto
- 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.
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.
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.
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>();
}
}
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
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)
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);
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