« Automapper » : différence entre les versions
De Banane Atomic
Aller à la navigationAller à la recherche
(23 versions intermédiaires par le même utilisateur non affichées) | |||
Ligne 1 : | Ligne 1 : | ||
[[Category: | [[Category:.NET Application]] | ||
= Description = | = Description = | ||
Permet le mapping d'un objet de type A vers un objet de type B.<br /> | Permet le mapping d'un objet de type A vers un objet de type B.<br /> | ||
Ligne 6 : | Ligne 6 : | ||
* [https://github.com/AutoMapper/AutoMapper/wiki/Flattening Flattening] | * [https://github.com/AutoMapper/AutoMapper/wiki/Flattening Flattening] | ||
* Ignore les références null | * Ignore les références null | ||
{{info | [[Mapster]] is a faster alternative.}} | |||
= Liens = | = Liens = | ||
Ligne 24 : | Ligne 25 : | ||
// load the configuration from all classes who inherits from Profile in the current assembly | // load the configuration from all classes who inherits from Profile in the current assembly | ||
cfg. | cfg.AddMaps(typeof(ContainerConfig).Assembly); | ||
}); | }); | ||
Ligne 38 : | Ligne 39 : | ||
public AProfile() | public AProfile() | ||
{ | { | ||
CreateMap<A, B>() | |||
.ForMember(dest => dest.B1, opt => opt.MapFrom(src => src.A1)); | |||
CreateMap<B, A>() | |||
.ForMember(dest => dest.A1, opt => opt.MapFrom(src => src.B1)); | |||
.ForAllOtherMembers(opt => opt.Ignore()); // ignore all other members | |||
CreateMap<A, C[]>() | |||
.ConvertUsing<AToCsConverter>(); | |||
. | |||
} | } | ||
} | |||
public class AToCsConverter : ITypeConverter<A, C[]> | |||
{ | |||
public C[] Convert(A source, C[] destination, ResolutionContext context) | |||
{ | { | ||
return new C[]; | |||
} | } | ||
} | } | ||
Ligne 58 : | Ligne 61 : | ||
== [http://docs.automapper.org/en/stable/Dependency-injection.html Dependency Injection] == | == [http://docs.automapper.org/en/stable/Dependency-injection.html Dependency Injection] == | ||
<filebox fn='Program.cs'> | |||
appBuilder.Services.AddAutoMapper(typeof(Program)); | |||
</filebox> | |||
<filebox fn='Controllers/ItemController.cs'> | |||
private readonly IMapper mapper; | |||
public ItemController(IMapper mapper) | |||
{ | |||
this.mapper = mapper; | |||
} | |||
</filebox> | |||
=== AutoMapper 12- === | |||
{{info | Add the nuget package {{boxx|AutoMapper.Extensions.Microsoft.DependencyInjection}}}} | {{info | Add the nuget package {{boxx|AutoMapper.Extensions.Microsoft.DependencyInjection}}}} | ||
<filebox fn='Startup.cs'> | <filebox fn='Startup.cs'> | ||
public void ConfigureServices(IServiceCollection services) | public void ConfigureServices(IServiceCollection services) | ||
{ | { | ||
// if all the profiles are in the current assembly | |||
services.AddAutoMapper(typeof(Startup)); | |||
// add as parameter one type for each assembly | // add as parameter one type for each assembly | ||
services.AddAutoMapper(typeof(ProfileTypeFromAssembly1), typeof(ProfileTypeFromAssembly2)); | services.AddAutoMapper(typeof(ProfileTypeFromAssembly1), typeof(ProfileTypeFromAssembly2)); | ||
</filebox> | |||
<filebox fn='Controllers/ItemController.cs'> | |||
private readonly IMapper mapper; | |||
public ItemController(ItemDbContext context, IMapper mapper) | |||
{ | |||
this.mapper = mapper; | |||
} | |||
</filebox> | |||
=== WPF === | |||
<filebox fn='App.xaml.cs'> | |||
protected override void RegisterTypes(IContainerRegistry containerRegistry) | |||
{ | |||
var config = new MapperConfiguration(cfg => | |||
{ | |||
cfg.AddMaps(typeof(App).Assembly); | |||
}); | |||
containerRegistry.RegisterSingleton<IMapper>(() => config.CreateMapper()); | |||
} | |||
</filebox> | </filebox> | ||
Ligne 102 : | Ligne 144 : | ||
* [http://docs.automapper.org/en/stable/Flattening.html Flattening] | * [http://docs.automapper.org/en/stable/Flattening.html Flattening] | ||
{{info | If the mapping to a destination property has not been configured, you will get an {{boxx|AutoMapper.AutoMapperMappingException}}}} | {{info | If the mapping to a destination property has not been configured, you will get an {{boxx|AutoMapper.AutoMapperMappingException}}}} | ||
= [https://docs.automapper.org/en/stable/Flattening.html Flattening] = | |||
<kode lang='cs'> | |||
var config = new MapperConfiguration(cfg => | |||
{ | |||
// default flattening | |||
// naming convention: SpecificItem.Category → SpecificItemDto.Category | |||
// flatening conventing: SpecificItem.Item.Id → SpecificItemDto.ItemId | |||
// configuration: SpecificItem.Item.Code → SpecificItemDto.Code | |||
cfg.CreateMap<SpecificItem, SpecificItemDto>() | |||
.ForMember(dest => dest.Code, opt => opt.MapFrom(src => src.Item.Code)); | |||
// flattening from composition to inheritance | |||
// naming convention: SpecificItem.Category → SpecificItemDtoV2.Category | |||
// configuration: explicitly call the mapping of Item → SpecificItemDtoV2 for the Item property | |||
cfg.CreateMap<SpecificItem, SpecificItemDtoV2>() | |||
.IncludeMembers(x => x.Item); | |||
// naming convention: Item.Id → ItemDtoV2.Id | |||
// naming convention: Item.Code → ItemDtoV2.Code | |||
cfg.CreateMap<Item, ItemDtoV2>(); | |||
// MemberList.None to skip validation and ignore unmapped destination properties like Category | |||
cfg.CreateMap<Item, SpecificItemDtoV2>(MemberList.None) | |||
.IncludeBase<Item, ItemDtoV2>(); | |||
}); | |||
config.AssertConfigurationIsValid(); | |||
IMapper mapper = config.CreateMapper(); | |||
var specificItem = new SpecificItem | |||
{ | |||
Item = new Item | |||
{ | |||
Id = 1, | |||
Code = 2 | |||
}, | |||
Category = 3 | |||
}; | |||
var specificItemDto = mapper.Map<SpecificItemDto>(specificItem); | |||
var specificItemDtoV2 = mapper.Map<SpecificItemDtoV2>(specificItem); | |||
// source | |||
class Item | |||
{ | |||
public int Id { get; set; } | |||
public int Code { get; set; } | |||
} | |||
class SpecificItem | |||
{ | |||
public int Category { get; set; } | |||
public Item Item { get; set; } | |||
} | |||
// destination | |||
class SpecificItemDto | |||
{ | |||
public int ItemId { get; set; } | |||
public int Code { get; set; } | |||
public int Category { get; set; } | |||
} | |||
class ItemDtoV2 | |||
{ | |||
public int Id { get; set; } | |||
public int Code { get; set; } | |||
} | |||
class SpecificItemDtoV2 : ItemDtoV2 | |||
{ | |||
public int Category { get; set; } | |||
} | |||
</kode> | |||
= Unflattening = | = Unflattening = | ||
Ligne 156 : | Ligne 272 : | ||
cfg.CreateMap<A, B>() | cfg.CreateMap<A, B>() | ||
.ForMember(dest => dest.P1, | .ForMember(dest => dest.P1, | ||
opt => opt.MapFrom((src, | opt => opt.MapFrom((src, _, _, context) => src.P1 + (int)context.Items["+"])); | ||
}); | }); | ||
Ligne 250 : | Ligne 366 : | ||
</kode> | </kode> | ||
= [https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions Conditional | = [https://docs.automapper.org/en/stable/Conditional-mapping.html#preconditions Conditional mapping] = | ||
<kode lang='cs'> | <kode lang='cs'> | ||
cfg.CreateMap<A, B>() | cfg.CreateMap<A, B>() | ||
Ligne 262 : | Ligne 378 : | ||
}); | }); | ||
</kode> | </kode> | ||
= [https://docs.automapper.org/en/stable/Before-and-after-map-actions.html Before and After Map Action] = | |||
<kode lang='cs'> | |||
cfg.CreateMap<A, B>() | |||
.BeforeMap((src, dest) => src.P1 += 1) | |||
.AfterMap((src, dest) => dest.P1 -= 1); | |||
</kode> | |||
= Inject a dependency during the mapping with a ValueResolver = | |||
<filebox fn='Program.cs'> | |||
// UserProvider is injected as a dependency, and AutoMapper has to be injected as a dependency too | |||
appBuilder.Services.AddScoped<IUserProvider, UserProvider>() | |||
.AddAutoMapper(typeof(AProfile)); | |||
</filebox> | |||
<filebox fn='AProfile.cs'> | |||
public class AProfile : Profile | |||
{ | |||
public AProfile() | |||
{ | |||
this.CreateMap<A, B>() | |||
.ForMember(x => x.User, opt => opt.MapFrom<UserValueResolver>()); | |||
} | |||
} | |||
// UserProvider is retrieved as a dependency and used to resolve the mapping | |||
public class UserValueResolver(IUserProvider userProvider) : IValueResolver<A, B, User> | |||
{ | |||
public User Resolve(A source, B destination, User member, ResolutionContext context) | |||
{ | |||
return userProvider.GetUser(); | |||
} | |||
} | |||
</filebox> | |||
= Errors = | = Errors = | ||
Ligne 300 : | Ligne 450 : | ||
Assert.Equal(item.Property1, mappedItem.Property1); | Assert.Equal(item.Property1, mappedItem.Property1); | ||
</kode> | |||
== Mock == | |||
<kode lang='cs'> | |||
// create a mock mapper | |||
var mockMapper = new Mock<IMapper>(); | |||
// mock the mapping | |||
this.mockMapper.Setup(x => x.Map<MyDto>(It.IsAny<MyEntity>())) | |||
.Returns(myDto); | |||
// or create a new simplified mapper | |||
var mapper = new Mapper( | |||
new MapperConfiguration( | |||
cfg => | |||
{ | |||
cfg.CreateMap<MyEntity, MyDto>() | |||
.ForMember(dest => dest.Property1, opt => opt.Ignore()); | |||
})); | |||
</kode> | </kode> | ||
Ligne 310 : | Ligne 478 : | ||
public void ConfigureServices(IServiceCollection services) | public void ConfigureServices(IServiceCollection services) | ||
{ | { | ||
services.AddAutoMapper(); | services.AddAutoMapper(typeof(Startup)); | ||
</filebox> | </filebox> | ||
<filebox fn='MyController.cs'> | <filebox fn='MyController.cs'> | ||
private readonly IMapper | private readonly IMapper mapper; | ||
public MyController(IMapper mapper) | public MyController(IMapper mapper) | ||
{ | { | ||
this.mapper = mapper; | |||
B b = | B b = this.mapper.Map<A, B>(a); | ||
</filebox> | </filebox> | ||
Configuration dans Profile | Configuration dans Profile |
Dernière version du 12 septembre 2024 à 12:12
Description
Permet le mapping d'un objet de type A vers un objet de type B.
AutoMapper utilise des convention de mapping afin de réduire le paramétrage:
- Propriétés de même nom
- Flattening
- Ignore les références null
Mapster is a faster alternative. |
Liens
- Getting Started
- Static and Instance API
- AutoMapper.Collection bibliothèque externe pour géré les collections
- Map 1 object to list of objects
Setup and configuration
var config = new MapperConfiguration(cfg => { // configure mapping from Source to Dest cfg.CreateMap<Source, Dest>(); // load the configuration from AppProfile cfg.AddProfile<AppProfile>(); // load the configuration from all classes who inherits from Profile in the current assembly cfg.AddMaps(typeof(ContainerConfig).Assembly); }); IMapper mapper = config.CreateMapper(); IMapper mapper = new Mapper(config); |
Profile
AProfile.cs |
internal sealed class AProfile : Profile { public AProfile() { CreateMap<A, B>() .ForMember(dest => dest.B1, opt => opt.MapFrom(src => src.A1)); CreateMap<B, A>() .ForMember(dest => dest.A1, opt => opt.MapFrom(src => src.B1)); .ForAllOtherMembers(opt => opt.Ignore()); // ignore all other members CreateMap<A, C[]>() .ConvertUsing<AToCsConverter>(); } } public class AToCsConverter : ITypeConverter<A, C[]> { public C[] Convert(A source, C[] destination, ResolutionContext context) { return new C[]; } } |
Dependency Injection
Program.cs |
appBuilder.Services.AddAutoMapper(typeof(Program)); |
Controllers/ItemController.cs |
private readonly IMapper mapper; public ItemController(IMapper mapper) { this.mapper = mapper; } |
AutoMapper 12-
Add the nuget package AutoMapper.Extensions.Microsoft.DependencyInjection |
Startup.cs |
public void ConfigureServices(IServiceCollection services) { // if all the profiles are in the current assembly services.AddAutoMapper(typeof(Startup)); // add as parameter one type for each assembly services.AddAutoMapper(typeof(ProfileTypeFromAssembly1), typeof(ProfileTypeFromAssembly2)); |
Controllers/ItemController.cs |
private readonly IMapper mapper; public ItemController(ItemDbContext context, IMapper mapper) { this.mapper = mapper; } |
WPF
App.xaml.cs |
protected override void RegisterTypes(IContainerRegistry containerRegistry) { var config = new MapperConfiguration(cfg => { cfg.AddMaps(typeof(App).Assembly); }); containerRegistry.RegisterSingleton<IMapper>(() => config.CreateMapper()); } |
Exemple
var config = new MapperConfiguration(cfg => { cfg.CreateMap<A, B>() .ForMember(dst => dst.Q2, opts => opts.MapFrom(src => src.P2)); // map A.P2 → B.Q2 .ReverseMap(); // map B → A }); IMapper mapper = config.CreateMapper(); C c = new C() { P1 = 3 }; A a = new A() { P1 = 1, C1 = c }; B b = mapper.Map<B>(a); // b.P1 = 1, b.Q2 = 2, b.C1P1 = 3 class A { public int P1 { get; set; } public int P2 { get; set; } public C C1 { get; set; } } class B { public int P1 { get; set; } // mapping par convention avec A.P1 public int Q2 { get; set; } // mapping par configuration avec A.P2 public int C1P1 { get; set; } // mapping par flattening avec A.C1.P1 } class C { public int P1 { get; set; } } |
If the mapping to a destination property has not been configured, you will get an AutoMapper.AutoMapperMappingException |
Flattening
var config = new MapperConfiguration(cfg => { // default flattening // naming convention: SpecificItem.Category → SpecificItemDto.Category // flatening conventing: SpecificItem.Item.Id → SpecificItemDto.ItemId // configuration: SpecificItem.Item.Code → SpecificItemDto.Code cfg.CreateMap<SpecificItem, SpecificItemDto>() .ForMember(dest => dest.Code, opt => opt.MapFrom(src => src.Item.Code)); // flattening from composition to inheritance // naming convention: SpecificItem.Category → SpecificItemDtoV2.Category // configuration: explicitly call the mapping of Item → SpecificItemDtoV2 for the Item property cfg.CreateMap<SpecificItem, SpecificItemDtoV2>() .IncludeMembers(x => x.Item); // naming convention: Item.Id → ItemDtoV2.Id // naming convention: Item.Code → ItemDtoV2.Code cfg.CreateMap<Item, ItemDtoV2>(); // MemberList.None to skip validation and ignore unmapped destination properties like Category cfg.CreateMap<Item, SpecificItemDtoV2>(MemberList.None) .IncludeBase<Item, ItemDtoV2>(); }); config.AssertConfigurationIsValid(); IMapper mapper = config.CreateMapper(); var specificItem = new SpecificItem { Item = new Item { Id = 1, Code = 2 }, Category = 3 }; var specificItemDto = mapper.Map<SpecificItemDto>(specificItem); var specificItemDtoV2 = mapper.Map<SpecificItemDtoV2>(specificItem); // source class Item { public int Id { get; set; } public int Code { get; set; } } class SpecificItem { public int Category { get; set; } public Item Item { get; set; } } // destination class SpecificItemDto { public int ItemId { get; set; } public int Code { get; set; } public int Category { get; set; } } class ItemDtoV2 { public int Id { get; set; } public int Code { get; set; } } class SpecificItemDtoV2 : ItemDtoV2 { public int Category { get; set; } } |
Unflattening
// unflattening UserDto.GroupId → User.Group.Id this.CreateMap<UserDto, User>() .ForPath(dest => dest.Group.Id, opt => opt.MapFrom(src => src.GroupId)); |
List and polymorphism
// avec une liste List<B> listB = Mapper.Map<List<A>, List<B>>(listA); // polymorphism cfg.CreateMap<Parent1, Parent2>() .Include<Child1, Child2>(); // IncludeAllDerived(); to include all the derived classes // without the include, the resulting list contains only Parent2 objects (no Child2 objects) cfg.CreateMap<Child1, Child2>(); var lp1 = new List<Parent1> { new Parent1 { P1 = 1}, new Child1 { P1 = 2, P2 = 22 } }; var lp2 = mapper.Map<List<Parent2>>(lp1); // Parent2, Child2 class Parent1 { public int P1 { get; set; } } class Child1 : Parent1 { public int P2 { get; set; } } class Parent2 { public int P1 { get; set; } } class Child2 : Parent2 { public int P2 { get; set; } } |
Pass parameter to the mapping
var config = new MapperConfiguration(cfg => { cfg.CreateMap<A, B>() .ForMember(dest => dest.P1, opt => opt.MapFrom((src, _, _, context) => src.P1 + (int)context.Items["+"])); }); IMapper mapper = config.CreateMapper(); var a = new A { P1 = 1 }; var b = mapper.Map<B>(a, opt => opt.Items["+"] = 9); |
Map 1 object to multiple objects
// map 1 Group to many User class Group { public int Id { get; set; } public ICollection<int> UserIds { get; } = new List<int>(); } class User { public int Id { get; set; } public int GroupId { get; set; } } |
Flatten to ValueType
var config = new MapperConfiguration(cfg => { cfg.CreateMap<(Group group, int userId), User>() .IncludeMembers(s => s.group) // call the mapping Group → User .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src.userId)); cfg.CreateMap<Group, User>() .ForMember(dest => dest.GroupId, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.Id, opt => opt.Ignore()); }); IMapper mapper = config.CreateMapper(); var group = new Group { Id = 9, UserIds = {1, 2, 3} }; IEnumerable<(int groupId, int userId)> groupAndUserList = group.UserIds.Select(x => ValueTuple.Create(group.Id, x)); var users = mapper.Map<IEnumerable<User>>(groupAndUserList).ToList(); |
Flatten with a ITypeConverter
var config = new MapperConfiguration(cfg => { cfg.CreateMap<int, User>() .ForMember(dest => dest.Id, opt => opt.MapFrom(src => src)); cfg.CreateMap<Group, User>() .ForMember(dest => dest.GroupId, opt => opt.MapFrom(src => src.Id)) .ForMember(dest => dest.Id, opt => opt.Ignore()); cfg.CreateMap<Group, IEnumerable<User>>() .ConvertUsing<GroupToUsersConverter>(); }); IMapper mapper = config.CreateMapper(); var group = new Group { Id = 9, UserIds = { 1, 2, 3 } }; var users = mapper.Map<IEnumerable<User>>(group).ToList(); // use a converter to flatten a Group class GroupToUsersConverter : ITypeConverter<Group, IEnumerable<User>> { public IEnumerable<User> Convert( Group source, IEnumerable<User> destination, ResolutionContext context) { foreach (var userId in source.UserIds) { var user = context.Mapper.Map<User>(userId); context.Mapper.Map(source, user); yield return user; } } } |
Conditional mapping
cfg.CreateMap<A, B>() // map A.P2 → B.Q2 if A.P2 > 0 // if A.P2 <= 0 → B.Q2 = 0 .ForMember(dest => dest.Q2, opt => { opt.Condition(src => src.P2 > 0); opt.MapFrom(src => src.P2); }); |
Before and After Map Action
cfg.CreateMap<A, B>() .BeforeMap((src, dest) => src.P1 += 1) .AfterMap((src, dest) => dest.P1 -= 1); |
Inject a dependency during the mapping with a ValueResolver
Program.cs |
// UserProvider is injected as a dependency, and AutoMapper has to be injected as a dependency too appBuilder.Services.AddScoped<IUserProvider, UserProvider>() .AddAutoMapper(typeof(AProfile)); |
AProfile.cs |
public class AProfile : Profile { public AProfile() { this.CreateMap<A, B>() .ForMember(x => x.User, opt => opt.MapFrom<UserValueResolver>()); } } // UserProvider is retrieved as a dependency and used to resolve the mapping public class UserValueResolver(IUserProvider userProvider) : IValueResolver<A, B, User> { public User Resolve(A source, B destination, User member, ResolutionContext context) { return userProvider.GetUser(); } } |
Errors
A lambda expression with a statement body cannot be converted to an expression tree
this.CreateMap<Class1, Class2>() .ForMember(dest => dest.Prop1, opt => opt.MapFrom(src => { // ... })); // use a new overload of MapFrom this.CreateMap<Class1, Class2>() .ForMember(dest => dest.Prop1, opt => opt.MapFrom((src, dest) => { // ... })); |
MOQ
var mapperMock = new Mock<IMapper>(); mapperMock.Setup(x => x.Map<ItemDto>(It.IsAny<Item>())) .Returns(new ItemDto()); |
Unit tests
var configuration = new MapperConfiguration(cfg => cfg.AddProfile<ItemProfile>()); configuration.AssertConfigurationIsValid(); var mapper = configuration.CreateMapper(); var item = new Item { /* ... */ }; var itemDto = mapper.Map<ItemDto>(item); var mappedItem = mapper.Map<Item>(itemDto); Assert.Equal(item.Property1, mappedItem.Property1); |
Mock
// create a mock mapper var mockMapper = new Mock<IMapper>(); // mock the mapping this.mockMapper.Setup(x => x.Map<MyDto>(It.IsAny<MyEntity>())) .Returns(myDto); // or create a new simplified mapper var mapper = new Mapper( new MapperConfiguration( cfg => { cfg.CreateMap<MyEntity, MyDto>() .ForMember(dest => dest.Property1, opt => opt.Ignore()); })); |
Installation
NuGet → AutoMapper
ASP.NET Core
NuGet → AutoMapper.Extensions.Microsoft.DependencyInjection
Startup.cs |
public void ConfigureServices(IServiceCollection services) { services.AddAutoMapper(typeof(Startup)); |
MyController.cs |
private readonly IMapper mapper; public MyController(IMapper mapper) { this.mapper = mapper; B b = this.mapper.Map<A, B>(a); |
Configuration dans Profile