From a75b8a2d6a1fa9da74fd9245c880c1f2dd3e8f02 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 3 Aug 2025 18:25:07 +0800 Subject: [PATCH 1/4] Add Mapperly integration documentation Resolve #23424 --- .../fundamentals/object-extensions.md | 16 +++ .../object-to-object-mapping.md | 111 +++++++++++++++++- 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/docs/en/framework/fundamentals/object-extensions.md b/docs/en/framework/fundamentals/object-extensions.md index b12be63dc0..2e01b3b1e3 100644 --- a/docs/en/framework/fundamentals/object-extensions.md +++ b/docs/en/framework/fundamentals/object-extensions.md @@ -394,6 +394,22 @@ public class MyProfile : Profile It has the same parameters with the `MapExtraPropertiesTo` method. +#### Mapperly Integration + +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. + +You can use the `MapExtraProperties` attribute to Mapperly class. + +````csharp +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToProfileDtoMapper : MapperBase +{ + public override partial IdentityUserDto Map(IdentityUser source); + public override partial void Map(IdentityUser source, IdentityUserDto destination); +} +```` + ## Entity Framework Core Database Mapping If you're using the EF Core, you can map an extra property to a table field in the database. Example: diff --git a/docs/en/framework/infrastructure/object-to-object-mapping.md b/docs/en/framework/infrastructure/object-to-object-mapping.md index 8961d804c7..f841907c05 100644 --- a/docs/en/framework/infrastructure/object-to-object-mapping.md +++ b/docs/en/framework/infrastructure/object-to-object-mapping.md @@ -84,7 +84,7 @@ public class UserAppService : ApplicationService } ```` -You should have defined the mappings before to be able to map objects. See the AutoMapper integration section to learn how to define mappings. +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. ## AutoMapper Integration @@ -217,13 +217,102 @@ public class MyProfile : Profile } ```` +## Mapperly Integration + +[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`. + +Once you define mappings class as below, you can use the `IObjectMapper` interface just like explained before. + +### Define Mapping Classes + +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. + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : MapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); +} +```` + +If you also want to map `UserDto` to `User`, you can inherit from the `TwoWayMapperBase` class. + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : TwoWayMapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); + + public override partial User ReverseMap(UserDto destination); + public override partial void ReverseMap(UserDto destination, User source); +} +```` + +### Before and After Mapping Methods + +The base class provides `BeforeMap` and `AfterMap` methods that can be overridden to perform actions before and after the mapping. + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : TwoWayMapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); + + public override partial void BeforeMap(User source) + { + //TODO: Perform actions before the mapping + } + + public override partial void AfterMap(User source, UserDto destination) + { + //TODO: Perform actions after the mapping + } + + public override partial User ReverseMap(UserDto destination); + public override partial void ReverseMap(UserDto destination, User source); + + public override partial void BeforeReverseMap(UserDto destination) + { + //TODO: Perform actions before the reverse mapping + } + + public override partial void AfterReverseMap(UserDto destination, User source) + { + //TODO: Perform actions after the reverse mapping + } +} +```` + +### Mapping the Object Extensions + +[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. + +````csharp +[Mapper] +[MapExtraProperties] +public partial class UserToUserDtoMapper : MapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); +} +```` + +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. + +### More Mapperly Features + +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. + ## Advanced Topics ### IObjectMapper Interface -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). +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). -`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. +`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. `IObjectMapper` is used to contextualize the object mapper, so you can use different libraries for different modules/contexts. @@ -281,6 +370,8 @@ public class UserAppService : ApplicationService 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: +When using AutoMapper: + ````csharp [DependsOn(typeof(AbpAutoMapperModule))] public class MyModule : AbpModule @@ -298,6 +389,20 @@ public class MyModule : AbpModule } ```` +When using Mapperly: + +````csharp +[DependsOn(typeof(AbpMapperlyModule))] +public class MyModule : AbpModule +{ + public override void ConfigureServices(ServiceConfigurationContext context) + { + //Use Mapperly for MyModule + context.Services.AddMapperlyObjectMapper(); + } +} +```` + `IObjectMapper` 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. ### IObjectMapper Interface From 981a014031e721c26952c74f646cd5084c97d3ba Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 3 Aug 2025 19:01:03 +0800 Subject: [PATCH 2/4] Add AutoMapper to Mapperly migration guide --- .../AutoMapper-To-Mapperly.md | 191 ++++++++++++++++++ 1 file changed, 191 insertions(+) create mode 100644 docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md diff --git a/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md b/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md new file mode 100644 index 0000000000..4311d00214 --- /dev/null +++ b/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md @@ -0,0 +1,191 @@ +# Migrating from AutoMapper to Mapperly + +## Introduction + +The AutoMapper library is no longer free for commercial use. See [this article](https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/) for more details. + +ABP framework provides the AutoMapper and Mapperly integration. If you have a project that uses AutoMapper and don't have any license for AutoMapper, you can migrate to Mapperly by following the steps below. + +## Migration Steps + +Please open your project with the IDE(`Visual Studio`, `VS Code` or `JetBrains Rider`), then perform a global search and replace. + +1. Replace `Volo.Abp.AutoMapper` to `Volo.Abp.Mapperly` in all `*.csproj` files. + +2. Replace `using Volo.Abp.AutoMapper;` to `using Volo.Abp.Mapperly;` in all `*.cs` files. + +3. Replace `AbpAutoMapperModule` with `AbpMapperlyModule` in all `*.cs` files. + +4. Replace `AddAutoMapperObjectMapper` to `AddMapperlyObjectMapper` in all `*.cs` files. + +5. Remove `Configure` code section. + +6. Check the AutoMapper's `Profile` class to add all new Mapperly mapping classes. + +## Example + +Here is an AutoMapper's `Profile` class: + +```csharp +public class ExampleAutoMapper : Profile +{ + public AbpIdentityApplicationModuleAutoMapperProfile() + { + CreateMap() + .MapExtraProperties() + .Ignore(x => x.IsLockedOut) + .Ignore(x => x.SupportTwoFactor) + .Ignore(x => x.RoleNames); + + CreateMap(); + + CreateMap() + .MapExtraProperties(); + + CreateMap() + .ReverseMap(); + + CreateMap() + .ForMember(dest => dest.RoleId, src => src.MapFrom(r => r.Id)); + + CreateMap() + .ForMember(dest => dest.Active, src => src.MapFrom(r => r.IsActive ? "Yes" : "No")) + .ForMember(dest => dest.EmailConfirmed, src => src.MapFrom(r => r.EmailConfirmed ? "Yes" : "No")) + .ForMember(dest => dest.TwoFactorEnabled, src => src.MapFrom(r => r.TwoFactorEnabled ? "Yes" : "No")) + .ForMember(dest => dest.AccountLookout, src => src.MapFrom(r => r.LockoutEnd != null && r.LockoutEnd > DateTime.UtcNow ? "Yes" : "No")) + .Ignore(x => x.Roles); + } +} +``` + +And the Mapperly mapping class: + +```csharp +[Mapper] +[MapExtraProperties] +public partial class IdentityUserToIdentityUserDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))] + [MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))] + [MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))] + public override partial IdentityUserDto Map(IdentityUser source); + + [MapperIgnoreTarget(nameof(IdentityUserDto.IsLockedOut))] + [MapperIgnoreTarget(nameof(IdentityUserDto.SupportTwoFactor))] + [MapperIgnoreTarget(nameof(IdentityUserDto.RoleNames))] + public override partial void Map(IdentityUser source, IdentityUserDto destination); +} + +[Mapper] +public partial class IdentityUserClaimToIdentityUserClaimDtoMapper : MapperBase +{ + public override partial IdentityUserClaimDto Map(IdentityUserClaim source); + + public override partial void Map(IdentityUserClaim source, IdentityUserClaimDto destination); +} + +[Mapper] +[MapExtraProperties] +public partial class OrganizationUnitToOrganizationUnitDtoMapper : MapperBase +{ + public override partial OrganizationUnitDto Map(OrganizationUnit source); + public override partial void Map(OrganizationUnit source, OrganizationUnitDto destination); +} + +[Mapper] +public partial class OrganizationUnitRoleToOrganizationUnitRoleDtoMapper : TwoWayMapperBase +{ + public override partial OrganizationUnitRoleDto Map(OrganizationUnitRole source); + public override partial void Map(OrganizationUnitRole source, OrganizationUnitRoleDto destination); + + public override partial OrganizationUnitRole ReverseMap(OrganizationUnitRoleDto destination); + public override partial void ReverseMap(OrganizationUnitRoleDto destination, OrganizationUnitRole source); +} + +[Mapper] +[MapExtraProperties] +public partial class OrganizationUnitToOrganizationUnitWithDetailsDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))] + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))] + public override partial OrganizationUnitWithDetailsDto Map(OrganizationUnit source); + + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.Roles))] + [MapperIgnoreTarget(nameof(OrganizationUnitWithDetailsDto.UserCount))] + public override partial void Map(OrganizationUnit source, OrganizationUnitWithDetailsDto destination); +} + +[Mapper] +public partial class IdentityRoleToOrganizationUnitRoleDtoMapper : MapperBase +{ + [MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))] + public override partial OrganizationUnitRoleDto Map(IdentityRole source); + + [MapProperty(nameof(IdentityRole.Id), nameof(OrganizationUnitRoleDto.RoleId))] + public override partial void Map(IdentityRole source, OrganizationUnitRoleDto destination); +} + +[Mapper] +public partial class IdentityUserToIdentityUserExportDtoMapper : MapperBase +{ + [MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))] + public override partial IdentityUserExportDto Map(IdentityUser source); + + [MapperIgnoreTarget(nameof(IdentityUserExportDto.Roles))] + public override partial void Map(IdentityUser source, IdentityUserExportDto destination); + + public override void AfterMap(IdentityUser source, IdentityUserExportDto destination) + { + destination.Active = source.IsActive ? "Yes" : "No"; + destination.EmailConfirmed = source.EmailConfirmed ? "Yes" : "No"; + destination.TwoFactorEnabled = source.TwoFactorEnabled ? "Yes" : "No"; + destination.AccountLookout = source.LockoutEnd != null && source.LockoutEnd > DateTime.UtcNow ? "Yes" : "No"; + } +} +``` + +## Mapperly Mapping Class + +You need to create a new Mapperly mapping class for each source and destination type. + +The `Mapper` attribute is used to mark the class as a Mapperly mapping class. + +The `MapperIgnoreTarget` attribute is used to replace the `Ignore` method. + +The `MapExtraProperties` attribute is used to replace the `MapExtraProperties` method. + +The `TwoWayMapperBase` class is used to replace the `ReverseMap` method. + +The `AfterMap` method is used to perform actions after the mapping. + +### Dependency Injection in Mapper Class + +All the Mapperly mapping classes will be added to the DI container. If you want to inject a service in your Mapper class, You just need to add the service to the constructor of the Mapper class. + +```csharp +public partial class IdentityUserToIdentityUserDtoMapper : MapperBase +{ + public IdentityUserToIdentityUserDtoMapper(MyService myService) + { + _myService = myService; + } + + public override partial IdentityUserDto Map(IdentityUser source); + public override partial void Map(IdentityUser source, IdentityUserDto destination); + + public override void AfterMap(IdentityUser source, IdentityUserDto destination) + { + destination.MyProperty = _myService.GetMyProperty(source.MyProperty); + } +} +``` + +## Mapperly Documentation + +Please refer to the [Mapperly documentation](https://mapperly.riok.app/docs/intro/) for more details. + +Key points: + +- [Mapperly Configuration](https://mapperly.riok.app/docs/configuration/mapper/) +- [Mapperly Enums](https://mapperly.riok.app/docs/configuration/enum/) +- [Mapperly Flattening and unflattening](https://mapperly.riok.app/docs/configuration/flattening/) From a9da3689c0226cdc551cab6e2e18ce57cad57fa2 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 6 Aug 2025 09:36:28 +0800 Subject: [PATCH 3/4] Add documentation for lists and arrays mapping --- .../infrastructure/object-to-object-mapping.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/en/framework/infrastructure/object-to-object-mapping.md b/docs/en/framework/infrastructure/object-to-object-mapping.md index f841907c05..52da7a258a 100644 --- a/docs/en/framework/infrastructure/object-to-object-mapping.md +++ b/docs/en/framework/infrastructure/object-to-object-mapping.md @@ -302,6 +302,23 @@ public partial class UserToUserDtoMapper : MapperBase 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. + +### Lists and Arrays Support + +ABP Mapperly integration also supports mapping lists and arrays. Same as [IObjectMapper Interface](#iobjectmappertsource-tdestination-interface) section. Example: + +````csharp +[Mapper] +public partial class UserToUserDtoMapper : MapperBase +{ + public override partial UserDto Map(User source); + public override partial void Map(User source, UserDto destination); +} + +var users = await _userRepository.GetListAsync(); // returns List +var dtos = ObjectMapper.Map, List>(users); // creates List +```` + ### More Mapperly Features 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. From b19e2e7a29050044e1cc622ea2b5ae001acd0656 Mon Sep 17 00:00:00 2001 From: Engincan VESKE Date: Wed, 6 Aug 2025 09:43:38 +0300 Subject: [PATCH 4/4] Revise mapperly docs --- .../fundamentals/object-extensions.md | 2 +- .../object-to-object-mapping.md | 13 +++--- .../AutoMapper-To-Mapperly.md | 45 ++++++++----------- 3 files changed, 27 insertions(+), 33 deletions(-) diff --git a/docs/en/framework/fundamentals/object-extensions.md b/docs/en/framework/fundamentals/object-extensions.md index 2e01b3b1e3..87ece27f75 100644 --- a/docs/en/framework/fundamentals/object-extensions.md +++ b/docs/en/framework/fundamentals/object-extensions.md @@ -398,7 +398,7 @@ It has the same parameters with the `MapExtraPropertiesTo` method. 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. -You can use the `MapExtraProperties` attribute to Mapperly class. +You can use the `MapExtraProperties` attribute to Mapperly class: ````csharp [Mapper] diff --git a/docs/en/framework/infrastructure/object-to-object-mapping.md b/docs/en/framework/infrastructure/object-to-object-mapping.md index 52da7a258a..5a9ea5f82a 100644 --- a/docs/en/framework/infrastructure/object-to-object-mapping.md +++ b/docs/en/framework/infrastructure/object-to-object-mapping.md @@ -225,7 +225,7 @@ Once you define mappings class as below, you can use the `IObjectMapper` interfa ### Define Mapping Classes -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. +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: ````csharp [Mapper] @@ -236,7 +236,7 @@ public partial class UserToUserDtoMapper : MapperBase } ```` -If you also want to map `UserDto` to `User`, you can inherit from the `TwoWayMapperBase` class. +If you also want to map `UserDto` to `User`, you can inherit from the `TwoWayMapperBase` class: ````csharp [Mapper] @@ -252,7 +252,7 @@ public partial class UserToUserDtoMapper : TwoWayMapperBase ### Before and After Mapping Methods -The base class provides `BeforeMap` and `AfterMap` methods that can be overridden to perform actions before and after the mapping. +The base class provides `BeforeMap` and `AfterMap` methods that can be overridden to perform actions before and after the mapping: ````csharp [Mapper] @@ -288,7 +288,7 @@ public partial class UserToUserDtoMapper : TwoWayMapperBase ### Mapping the Object Extensions -[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. +[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: ````csharp [Mapper] @@ -302,10 +302,11 @@ public partial class UserToUserDtoMapper : MapperBase 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. - ### Lists and Arrays Support -ABP Mapperly integration also supports mapping lists and arrays. Same as [IObjectMapper Interface](#iobjectmappertsource-tdestination-interface) section. Example: +ABP Mapperly integration also supports mapping lists and arrays as explained in the [IObjectMapper Interface](#iobjectmappertsource-tdestination-interface) section. + +**Example**: ````csharp [Mapper] diff --git a/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md b/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md index 4311d00214..2b0c5b623d 100644 --- a/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md +++ b/docs/en/release-info/migration-guides/AutoMapper-To-Mapperly.md @@ -2,27 +2,22 @@ ## Introduction -The AutoMapper library is no longer free for commercial use. See [this article](https://www.jimmybogard.com/automapper-and-mediatr-going-commercial/) for more details. +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/). -ABP framework provides the AutoMapper and Mapperly integration. If you have a project that uses AutoMapper and don't have any license for AutoMapper, you can migrate to Mapperly by following the steps below. +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. ## Migration Steps -Please open your project with the IDE(`Visual Studio`, `VS Code` or `JetBrains Rider`), then perform a global search and replace. - -1. Replace `Volo.Abp.AutoMapper` to `Volo.Abp.Mapperly` in all `*.csproj` files. - -2. Replace `using Volo.Abp.AutoMapper;` to `using Volo.Abp.Mapperly;` in all `*.cs` files. +Please open your project in an IDE(`Visual Studio`, `VS Code` or `JetBrains Rider`), then perform the following global search and replace operations: +1. Replace `Volo.Abp.AutoMapper` with `Volo.Abp.Mapperly` in all `*.csproj` files. +2. Replace `using Volo.Abp.AutoMapper;` with `using Volo.Abp.Mapperly;` in all `*.cs` files. 3. Replace `AbpAutoMapperModule` with `AbpMapperlyModule` in all `*.cs` files. +4. Replace `AddAutoMapperObjectMapper` with `AddMapperlyObjectMapper` in all `*.cs` files. +5. Remove any code sections that configure `Configure`. +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) -4. Replace `AddAutoMapperObjectMapper` to `AddMapperlyObjectMapper` in all `*.cs` files. - -5. Remove `Configure` code section. - -6. Check the AutoMapper's `Profile` class to add all new Mapperly mapping classes. - -## Example +**Example:** Here is an AutoMapper's `Profile` class: @@ -146,21 +141,19 @@ public partial class IdentityUserToIdentityUserExportDtoMapper : MapperBase @@ -184,7 +177,7 @@ public partial class IdentityUserToIdentityUserDtoMapper : MapperBase