Skip to content

Commit b00fae0

Browse files
authored
Merge pull request #23438 from abpframework/Mapperly
Add Mapperly integration documentation
2 parents 646eeb7 + b19e2e7 commit b00fae0

File tree

3 files changed

+326
-3
lines changed

3 files changed

+326
-3
lines changed

docs/en/framework/fundamentals/object-extensions.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,22 @@ public class MyProfile : Profile
394394

395395
It has the same parameters with the `MapExtraPropertiesTo` method.
396396

397+
#### Mapperly Integration
398+
399+
If you're using the [Mapperly](https://github.com/riok/mapperly) library, the ABP also provides an extension method to utilize the `MapExtraPropertiesTo` method defined above.
400+
401+
You can use the `MapExtraProperties` attribute to Mapperly class:
402+
403+
````csharp
404+
[Mapper]
405+
[MapExtraProperties]
406+
public partial class IdentityUserToProfileDtoMapper : MapperBase<IdentityUser, IdentityUserDto>
407+
{
408+
public override partial IdentityUserDto Map(IdentityUser source);
409+
public override partial void Map(IdentityUser source, IdentityUserDto destination);
410+
}
411+
````
412+
397413
## Entity Framework Core Database Mapping
398414

399415
If you're using the EF Core, you can map an extra property to a table field in the database. Example:

docs/en/framework/infrastructure/object-to-object-mapping.md

Lines changed: 126 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public class UserAppService : ApplicationService
8484
}
8585
````
8686

87-
You should have defined the mappings before to be able to map objects. See the AutoMapper integration section to learn how to define mappings.
87+
You should have defined the mappings before to be able to map objects. See the AutoMapper/Mapperly integration section to learn how to define mappings.
8888

8989
## AutoMapper Integration
9090

@@ -217,13 +217,120 @@ public class MyProfile : Profile
217217
}
218218
````
219219

220+
## Mapperly Integration
221+
222+
[Mapperly](https://github.com/riok/mapperly) is a .NET source generator for generating object mappings. [Volo.Abp.Mapperly](https://www.nuget.org/packages/Volo.Abp.Mapperly) package defines the Mapperly integration for the `IObjectMapper`.
223+
224+
Once you define mappings class as below, you can use the `IObjectMapper` interface just like explained before.
225+
226+
### Define Mapping Classes
227+
228+
You can define a mapper class by using the `Mapper` attribute. The class and methods must be `partial` to allow the Mapperly to generate the implementation during the build process:
229+
230+
````csharp
231+
[Mapper]
232+
public partial class UserToUserDtoMapper : MapperBase<User, UserDto>
233+
{
234+
public override partial UserDto Map(User source);
235+
public override partial void Map(User source, UserDto destination);
236+
}
237+
````
238+
239+
If you also want to map `UserDto` to `User`, you can inherit from the `TwoWayMapperBase<User, UserDto>` class:
240+
241+
````csharp
242+
[Mapper]
243+
public partial class UserToUserDtoMapper : TwoWayMapperBase<User, UserDto>
244+
{
245+
public override partial UserDto Map(User source);
246+
public override partial void Map(User source, UserDto destination);
247+
248+
public override partial User ReverseMap(UserDto destination);
249+
public override partial void ReverseMap(UserDto destination, User source);
250+
}
251+
````
252+
253+
### Before and After Mapping Methods
254+
255+
The base class provides `BeforeMap` and `AfterMap` methods that can be overridden to perform actions before and after the mapping:
256+
257+
````csharp
258+
[Mapper]
259+
public partial class UserToUserDtoMapper : TwoWayMapperBase<User, UserDto>
260+
{
261+
public override partial UserDto Map(User source);
262+
public override partial void Map(User source, UserDto destination);
263+
264+
public override partial void BeforeMap(User source)
265+
{
266+
//TODO: Perform actions before the mapping
267+
}
268+
269+
public override partial void AfterMap(User source, UserDto destination)
270+
{
271+
//TODO: Perform actions after the mapping
272+
}
273+
274+
public override partial User ReverseMap(UserDto destination);
275+
public override partial void ReverseMap(UserDto destination, User source);
276+
277+
public override partial void BeforeReverseMap(UserDto destination)
278+
{
279+
//TODO: Perform actions before the reverse mapping
280+
}
281+
282+
public override partial void AfterReverseMap(UserDto destination, User source)
283+
{
284+
//TODO: Perform actions after the reverse mapping
285+
}
286+
}
287+
````
288+
289+
### Mapping the Object Extensions
290+
291+
[Object extension system](../fundamentals/object-extensions.md) allows to define extra properties for existing classes. ABP provides a mapping definition extension to properly map extra properties of two objects:
292+
293+
````csharp
294+
[Mapper]
295+
[MapExtraProperties]
296+
public partial class UserToUserDtoMapper : MapperBase<User, UserDto>
297+
{
298+
public override partial UserDto Map(User source);
299+
public override partial void Map(User source, UserDto destination);
300+
}
301+
````
302+
303+
It is suggested to use the `MapExtraPropertiesAttribute` attribute if both classes are extensible objects (implement the `IHasExtraProperties` interface). See the [object extension document](../fundamentals/object-extensions.md) for more.
304+
305+
### Lists and Arrays Support
306+
307+
ABP Mapperly integration also supports mapping lists and arrays as explained in the [IObjectMapper<TSource, TDestination> Interface](#iobjectmappertsource-tdestination-interface) section.
308+
309+
**Example**:
310+
311+
````csharp
312+
[Mapper]
313+
public partial class UserToUserDtoMapper : MapperBase<User, UserDto>
314+
{
315+
public override partial UserDto Map(User source);
316+
public override partial void Map(User source, UserDto destination);
317+
}
318+
319+
var users = await _userRepository.GetListAsync(); // returns List<User>
320+
var dtos = ObjectMapper.Map<List<User>, List<UserDto>>(users); // creates List<UserDto>
321+
````
322+
323+
### More Mapperly Features
324+
325+
Most of Mapperly's features such as `Ignore` can be configured through its attributes. See the [Mapperly documentation](https://mapperly.riok.app/docs/intro/) for more details.
326+
220327
## Advanced Topics
221328

222329
### IObjectMapper<TContext> Interface
223330

224-
Assume that you have created a **reusable module** which defines AutoMapper profiles and uses `IObjectMapper` when it needs to map objects. Your module then can be used in different applications, by nature of the [modularity](../architecture/modularity/basics.md).
331+
Assume that you have created a **reusable module** which defines AutoMapper/Mapperly profiles and uses `IObjectMapper` when it needs to map objects. Your module then can be used in different applications, by nature of the [modularity](../architecture/modularity/basics.md).
225332

226-
`IObjectMapper` is an abstraction and can be replaced by the final application to use another mapping library. The problem here that your reusable module is designed to use the AutoMapper library, because it only defines mappings for it. In such a case, you will want to guarantee that your module always uses AutoMapper even if the final application uses another default object mapping library.
333+
`IObjectMapper` is an abstraction and can be replaced by the final application to use another mapping library. The problem here that your reusable module is designed to use the AutoMapper/Mapperly library, because it only defines mappings for it. In such a case, you will want to guarantee that your module always uses AutoMapper/Mapperly even if the final application uses another default object mapping library.
227334

228335
`IObjectMapper<TContext>` is used to contextualize the object mapper, so you can use different libraries for different modules/contexts.
229336

@@ -281,6 +388,8 @@ public class UserAppService : ApplicationService
281388

282389
While using the contextualized object mapper is same as the normal object mapper, you should register the contextualized mapper in your module's `ConfigureServices` method:
283390

391+
When using AutoMapper:
392+
284393
````csharp
285394
[DependsOn(typeof(AbpAutoMapperModule))]
286395
public class MyModule : AbpModule
@@ -298,6 +407,20 @@ public class MyModule : AbpModule
298407
}
299408
````
300409

410+
When using Mapperly:
411+
412+
````csharp
413+
[DependsOn(typeof(AbpMapperlyModule))]
414+
public class MyModule : AbpModule
415+
{
416+
public override void ConfigureServices(ServiceConfigurationContext context)
417+
{
418+
//Use Mapperly for MyModule
419+
context.Services.AddMapperlyObjectMapper<MyModule>();
420+
}
421+
}
422+
````
423+
301424
`IObjectMapper<MyModule>` is an essential feature for a reusable module where it can be used in multiple applications each may use a different library for object to object mapping. All pre-built ABP modules are using it. But, for the final application, you can ignore this interface and always use the default `IObjectMapper` interface.
302425

303426
### IObjectMapper<TSource, TDestination> Interface
Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
# Migrating from AutoMapper to Mapperly
2+
3+
## Introduction
4+
5+
The AutoMapper library is **no longer free for commercial use**. For more details, you can refer to [this announcement post](https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/).
6+
7+
ABP Framework provides both AutoMapper and Mapperly integrations. If your project currently uses AutoMapper and you don't have a commercial license, you can switch to Mapperly by following the steps outlined below.
8+
9+
## Migration Steps
10+
11+
Please open your project in an IDE(`Visual Studio`, `VS Code` or `JetBrains Rider`), then perform the following global search and replace operations:
12+
13+
1. Replace `Volo.Abp.AutoMapper` with `Volo.Abp.Mapperly` in all `*.csproj` files.
14+
2. Replace `using Volo.Abp.AutoMapper;` with `using Volo.Abp.Mapperly;` in all `*.cs` files.
15+
3. Replace `AbpAutoMapperModule` with `AbpMapperlyModule` in all `*.cs` files.
16+
4. Replace `AddAutoMapperObjectMapper` with `AddMapperlyObjectMapper` in all `*.cs` files.
17+
5. Remove any code sections that configure `Configure<AbpAutoMapperOptions>`.
18+
6. Review any existing AutoMapper `Profile` classes and ensure all newly created Mapperly mapping classes are registered appropriately. (You can refer to the example below for guidance)
19+
20+
**Example:**
21+
22+
Here is an AutoMapper's `Profile` class:
23+
24+
```csharp
25+
public class ExampleAutoMapper : Profile
26+
{
27+
public AbpIdentityApplicationModuleAutoMapperProfile()
28+
{
29+
CreateMap<IdentityUser, IdentityUserDto>()
30+
.MapExtraProperties()
31+
.Ignore(x => x.IsLockedOut)
32+
.Ignore(x => x.SupportTwoFactor)
33+
.Ignore(x => x.RoleNames);
34+
35+
CreateMap<IdentityUserClaim, IdentityUserClaimDto>();
36+
37+
CreateMap<OrganizationUnit, OrganizationUnitDto>()
38+
.MapExtraProperties();
39+
40+
CreateMap<OrganizationUnitRole, OrganizationUnitRoleDto>()
41+
.ReverseMap();
42+
43+
CreateMap<IdentityRole, OrganizationUnitRoleDto>()
44+
.ForMember(dest => dest.RoleId, src => src.MapFrom(r => r.Id));
45+
46+
CreateMap<IdentityUser, IdentityUserExportDto>()
47+
.ForMember(dest => dest.Active, src => src.MapFrom(r => r.IsActive ? "Yes" : "No"))
48+
.ForMember(dest => dest.EmailConfirmed, src => src.MapFrom(r => r.EmailConfirmed ? "Yes" : "No"))
49+
.ForMember(dest => dest.TwoFactorEnabled, src => src.MapFrom(r => r.TwoFactorEnabled ? "Yes" : "No"))
50+
.ForMember(dest => dest.AccountLookout, src => src.MapFrom(r => r.LockoutEnd != null && r.LockoutEnd > DateTime.UtcNow ? "Yes" : "No"))
51+
.Ignore(x => x.Roles);
52+
}
53+
}
54+
```
55+
56+
And the Mapperly mapping class:
57+
58+
```csharp
59+
[Mapper]
60+
[MapExtraProperties]
61+
public partial class IdentityUserToIdentityUserDtoMapper : MapperBase<IdentityUser, IdentityUserDto>
62+
{
63+
[MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))]
64+
[MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))]
65+
[MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))]
66+
public override partial IdentityUserDto Map(IdentityUser source);
67+
68+
[MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))]
69+
[MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))]
70+
[MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))]
71+
public override partial void Map(IdentityUser source, IdentityUserDto destination);
72+
}
73+
74+
[Mapper]
75+
public partial class IdentityUserClaimToIdentityUserClaimDtoMapper : MapperBase<IdentityUserClaim, IdentityUserClaimDto>
76+
{
77+
public override partial IdentityUserClaimDto Map(IdentityUserClaim source);
78+
79+
public override partial void Map(IdentityUserClaim source, IdentityUserClaimDto destination);
80+
}
81+
82+
[Mapper]
83+
[MapExtraProperties]
84+
public partial class OrganizationUnitToOrganizationUnitDtoMapper : MapperBase<OrganizationUnit, OrganizationUnitDto>
85+
{
86+
public override partial OrganizationUnitDto Map(OrganizationUnit source);
87+
public override partial void Map(OrganizationUnit source, OrganizationUnitDto destination);
88+
}
89+
90+
[Mapper]
91+
public partial class OrganizationUnitRoleToOrganizationUnitRoleDtoMapper : TwoWayMapperBase<OrganizationUnitRole, OrganizationUnitRoleDto>
92+
{
93+
public override partial OrganizationUnitRoleDto Map(OrganizationUnitRole source);
94+
public override partial void Map(OrganizationUnitRole source, OrganizationUnitRoleDto destination);
95+
96+
public override partial OrganizationUnitRole ReverseMap(OrganizationUnitRoleDto destination);
97+
public override partial void ReverseMap(OrganizationUnitRoleDto destination, OrganizationUnitRole source);
98+
}
99+
100+
[Mapper]
101+
[MapExtraProperties]
102+
public partial class OrganizationUnitToOrganizationUnitWithDetailsDtoMapper : MapperBase<OrganizationUnit, OrganizationUnitWithDetailsDto>
103+
{
104+
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))]
105+
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))]
106+
public override partial OrganizationUnitWithDetailsDto Map(OrganizationUnit source);
107+
108+
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))]
109+
[MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))]
110+
public override partial void Map(OrganizationUnit source, OrganizationUnitWithDetailsDto destination);
111+
}
112+
113+
[Mapper]
114+
public partial class IdentityRoleToOrganizationUnitRoleDtoMapper : MapperBase<IdentityRole, OrganizationUnitRoleDto>
115+
{
116+
[MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))]
117+
public override partial OrganizationUnitRoleDto Map(IdentityRole source);
118+
119+
[MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))]
120+
public override partial void Map(IdentityRole source, OrganizationUnitRoleDto destination);
121+
}
122+
123+
[Mapper]
124+
public partial class IdentityUserToIdentityUserExportDtoMapper : MapperBase<IdentityUser, IdentityUserExportDto>
125+
{
126+
[MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))]
127+
public override partial IdentityUserExportDto Map(IdentityUser source);
128+
129+
[MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))]
130+
public override partial void Map(IdentityUser source, IdentityUserExportDto destination);
131+
132+
public override void AfterMap(IdentityUser source, IdentityUserExportDto destination)
133+
{
134+
destination.Active = source.IsActive ? "Yes" : "No";
135+
destination.EmailConfirmed = source.EmailConfirmed ? "Yes" : "No";
136+
destination.TwoFactorEnabled = source.TwoFactorEnabled ? "Yes" : "No";
137+
destination.AccountLookout = source.LockoutEnd != null && source.LockoutEnd > DateTime.UtcNow ? "Yes" : "No";
138+
}
139+
}
140+
```
141+
142+
## Mapperly Mapping Class
143+
144+
To use Mapperly, you'll need to create a dedicated mapping class for each source and destination types.
145+
146+
* Use the `[Mapper]` attribute to designate the class as a Mapperly mapper.
147+
* Replace AutoMapper's `Ignore()` method with the `[MapperIgnoreTarget]` attribute.
148+
* Replace the `MapExtraProperties()` method with the `[MapExtraProperties]` attribute.
149+
* Use the `TwoWayMapperBase` class as an alternative to AutoMapper’s `ReverseMap()` functionality.
150+
* Implement the `AfterMap()` method to execute logic after the mapping is completed.
151+
152+
### Dependency Injection in Mapper Class
153+
154+
All Mapperly mapping classes automatically registered in the the [dependency injection (DI)](../../framework/fundamentals/dependency-injection.md) container. To use a service within a Mapper class, simply add it to the constructor, Mapperly will inject it automatically.
155+
156+
**Example:**
157+
158+
```csharp
159+
public partial class IdentityUserToIdentityUserDtoMapper : MapperBase<IdentityUser, IdentityUserDto>
160+
{
161+
public IdentityUserToIdentityUserDtoMapper(MyService myService)
162+
{
163+
_myService = myService;
164+
}
165+
166+
public override partial IdentityUserDto Map(IdentityUser source);
167+
public override partial void Map(IdentityUser source, IdentityUserDto destination);
168+
169+
public override void AfterMap(IdentityUser source, IdentityUserDto destination)
170+
{
171+
destination.MyProperty = _myService.GetMyProperty(source.MyProperty);
172+
}
173+
}
174+
```
175+
176+
## Mapperly Documentation
177+
178+
Please refer to the [Mapperly documentation](https://mapperly.riok.app/docs/intro/) for more details.
179+
180+
**Key points:**
181+
182+
- [Mapperly Configuration](https://mapperly.riok.app/docs/configuration/mapper/)
183+
- [Mapperly Enums](https://mapperly.riok.app/docs/configuration/enum/)
184+
- [Mapperly Flattening and unflattening](https://mapperly.riok.app/docs/configuration/flattening/)

0 commit comments

Comments
 (0)