asp.net-mvc - mvc - use automapper net core
¿Dónde colocar AutoMapper.CreateMaps? (10)
Además de la mejor respuesta, una buena manera es usar Autofac IoC liberary para agregar automatización. Con esto solo definas 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 de perfil y las inicia automáticamente.
Estoy usando AutoMapper
en una 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 colocar estas llamadas en solo 1 lugar.
Tengo una capa web, una capa de servicio y una capa de datos. Cada uno es 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 el AutoMapper
de AutoMapper? ¿Dónde lo pusiste? ¿Como lo llamas?
Desde la nueva versión de AutoMapper que usa 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
No importa, siempre y cuando sea una clase estática. Se trata de la convención .
Nuestra convención es que cada "capa" (web, servicios, datos) tiene un solo archivo llamado AutoMapperXConfiguration.cs
, con un solo 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<User,UserViewModel>();
}
// ... etc
}
Creamos un método para cada "agregado" (Usuario, Publicación), por lo que las cosas están bien separadas.
Entonces su Global.asax
:
AutoMapperWebConfiguration.Configure();
AutoMapperServicesConfiguration.Configure();
AutoMapperDomainConfiguration.Configure();
// etc
Es algo así como una "interfaz de palabras": no se puede imponer, pero usted lo espera, por lo que puede codificar (y refactorizar) si es necesario.
EDITAR:
Solo pensé en mencionar que ahora uso los profiles 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.
Para aquellos de ustedes que se adhieren a lo siguiente:
- usando un contenedor ioc
- no me gusta abrirse para esto
- no me gusta un archivo de configuración monolítica
Hice un combo entre perfiles y aprovechando mi contenedor ioc:
Configuracion 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 compensación es que tienes que hacer referencia al Mapper por la interfaz IMappingEngine en lugar del Mapeador estático, pero esa es una convención con la que puedo vivir.
Para aquellos que están (perdidos) usando:
- Webapi 2
- SimpleInjector 3.1
- AutoMapper 4.2.1 (con perfiles)
Así es como gestioné la integración de AutoMapper en la " https://github.com/AutoMapper/AutoMapper/wiki/Migrating-from-static-API ". También, un enorme 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<User, UserViewModel>();
}
public override string ProfileName
{
get
{
return this.GetType().Name;
}
}
}
2 - En mi App_Start, tengo un SimpleInjectorApiInitializer que configura mi contenedor de 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 usualmente una interfaz IMapper:
private readonly IMapper mapper;
public AccountController( IMapper mapper)
{
this.mapper = mapper;
}
//Using..
var userEntity = mapper.Map<UserViewModel, User>(entity);
Para programadores 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)
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 asignación 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ápido, ya que están en el mismo archivo.
Así que tu 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>();
}
}
Realmente puede colocarlo 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 la capa web y la capa de servicio podrán acceder a ella, y más adelante si decide haga una aplicación de consola o está haciendo un proyecto de prueba unitaria; la configuración de mapeo también estará disponible desde 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 en el inicio de la aplicación
solo llama
AutoMapperBootStrapper.BootStrap();
Ahora, algunas personas argumentarán que este método viola algunos principios SÓLIDOS, que tienen argumentos válidos. Aquí están para la lectura.
¿Configurando Automapper en Bootstrapper viola el Principio Abierto-Cerrado?
Todas las soluciones anteriores brindan un método estático para llamar (desde app_start o desde cualquier lugar) que debería llamar a otros métodos para configurar partes de 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 la biblioteca WebActivator
que puede registrar algunos métodos para ejecutarse en app_pre_start
y app_post_start
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
través de NuGet.
Actualización: El enfoque publicado aquí ya no es válido ya que SelfProfiler
se eliminó a partir de AutoMapper v2.
Tomaría un enfoque similar al de Thoai. Pero usaría la clase 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 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 tu aplicación, crea 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ítica.