From 178d3f56d42b4e5acb7e349470f4a644d4c5214e Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 9 Jul 2025 17:41:28 +0800 Subject: [PATCH 1/9] feat: Add `Volo.Abp.Mapperly` module --- Directory.Packages.props | 1 + framework/Volo.Abp.sln | 14 ++ ...bpAutoMapperServiceCollectionExtensions.cs | 22 ++ .../Volo.Abp.Mapperly.csproj | 29 +++ .../Volo/Abp/Mapperly/AbpMapperlyBase.cs | 19 ++ .../AbpMapperlyConventionalRegistrar.cs | 26 +++ .../Volo/Abp/Mapperly/AbpMapperlyModule.cs | 25 +++ .../Volo/Abp/Mapperly/IAbpMapperly.cs | 12 ++ .../Mapperly/MapExtraPropertiesAttribute.cs | 14 ++ .../Abp/Mapperly/MapExtraPropertiesInvoker.cs | 71 +++++++ .../MapperlyAutoObjectMappingProvider.cs | 123 +++++++++++ .../ObjectExtending/ExtensibleObjectMapper.cs | 2 +- .../MappingPropertyDefinitionChecks.cs | 11 +- ...AutoMapperExtensibleDtoExtensions_Tests.cs | 82 ++++++++ .../Volo.Abp.Mapperly.Tests.csproj | 18 ++ ...apperModule_Specific_ObjectMapper_Tests.cs | 191 ++++++++++++++++++ .../AbpMapperlyBeforeAndAfterMethod_Tests.cs | 59 ++++++ .../Mapperly/AbpMapperlyModule_Basic_Tests.cs | 38 ++++ .../Volo/Abp/Mapperly/MapperlyTestModule.cs | 13 ++ .../Mapperly/ObjectMapperExtensions_Tests.cs | 32 +++ .../Mapperly/SampleClasses/MapperlyMappers.cs | 44 ++++ .../Abp/Mapperly/SampleClasses/MyEntity.cs | 10 + .../Abp/Mapperly/SampleClasses/MyEntityDto.cs | 10 + .../Mapperly/SampleClasses/MyEntityDto2.cs | 10 + .../MyEntityDtoWithMappingMethods.cs | 43 ++++ .../MyEntityToMyEntityDto2Mapper.cs | 39 ++++ .../Volo/Abp/Mapperly/SampleClasses/MyEnum.cs | 8 + .../Abp/Mapperly/SampleClasses/MyEnumDto.cs | 9 + .../Mapperly/SampleClasses/MyNotMappedDto.cs | 10 + nupkg/common.ps1 | 1 + 30 files changed, 982 insertions(+), 4 deletions(-) create mode 100644 framework/src/Volo.Abp.Mapperly/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs create mode 100644 framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj create mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs create mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs create mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyModule.cs create mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs create mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesAttribute.cs create mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs create mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Mapperly/AbpAutoMapperExtensibleDtoExtensions_Tests.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/MapperlyTestModule.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntity.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto2.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDtoWithMappingMethods.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityToMyEntityDto2Mapper.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnum.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnumDto.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyNotMappedDto.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index 5d9885ddbd..679149e4e9 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -144,6 +144,7 @@ + diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln index 78f7ae0011..7f992078e2 100644 --- a/framework/Volo.Abp.sln +++ b/framework/Volo.Abp.sln @@ -491,6 +491,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.BlobStoring.Bunny. EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Timing.Tests", "test\Volo.Abp.Timing.Tests\Volo.Abp.Timing.Tests.csproj", "{58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Mapperly", "src\Volo.Abp.Mapperly\Volo.Abp.Mapperly.csproj", "{AF556046-54CD-48BC-9740-3E926DB8B510}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.Mapperly.Tests", "test\Volo.Abp.Mapperly.Tests\Volo.Abp.Mapperly.Tests.csproj", "{C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -1465,6 +1469,14 @@ Global {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Debug|Any CPU.Build.0 = Debug|Any CPU {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Release|Any CPU.ActiveCfg = Release|Any CPU {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64}.Release|Any CPU.Build.0 = Release|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF556046-54CD-48BC-9740-3E926DB8B510}.Release|Any CPU.Build.0 = Release|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -1712,6 +1724,8 @@ Global {1BBCBA72-CDB6-4882-96EE-D4CD149433A2} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} {BC4BB2D6-DFD8-4190-AAC3-32C0A7A8E915} = {447C8A77-E5F0-4538-8687-7383196D04EA} {58FCF22D-E8DB-4EB8-B586-9BB6E9899D64} = {447C8A77-E5F0-4538-8687-7383196D04EA} + {AF556046-54CD-48BC-9740-3E926DB8B510} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6} + {C38926D5-C1E7-47D6-BD0B-D36BE4C19AE7} = {447C8A77-E5F0-4538-8687-7383196D04EA} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5} diff --git a/framework/src/Volo.Abp.Mapperly/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs b/framework/src/Volo.Abp.Mapperly/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs new file mode 100644 index 0000000000..516e7b969f --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Microsoft/Extensions/DependencyInjection/AbpAutoMapperServiceCollectionExtensions.cs @@ -0,0 +1,22 @@ +using Microsoft.Extensions.DependencyInjection.Extensions; +using Volo.Abp.Mapperly; +using Volo.Abp.ObjectMapping; + +namespace Microsoft.Extensions.DependencyInjection; + +public static class AbpAutoMapperServiceCollectionExtensions +{ + public static IServiceCollection AddMapperlyObjectMapper(this IServiceCollection services) + { + return services.Replace( + ServiceDescriptor.Transient() + ); + } + + public static IServiceCollection AddMapperlyObjectMapper(this IServiceCollection services) + { + return services.Replace( + ServiceDescriptor.Transient, MapperlyAutoObjectMappingProvider>() + ); + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj b/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj new file mode 100644 index 0000000000..672936d4f7 --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj @@ -0,0 +1,29 @@ + + + + + + + net9.0 + enable + Nullable + Volo.Abp.Mapperly + Volo.Abp.Mapperly + $(AssetTargetFallback);portable-net45+win8+wp8+wpa81; + false + false + false + + + + + + + + + + + + + + diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs new file mode 100644 index 0000000000..14165be56e --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs @@ -0,0 +1,19 @@ +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Mapperly; + +public abstract class AbpMapperlyBase : IAbpMapperly, ITransientDependency +{ + public abstract TDestination Map(TSource source); + + public abstract void Map(TSource source, TDestination destination); + + public virtual void BeforeMap(TSource source) + { + + } + public virtual void AfterMap(TSource source, TDestination destination) + { + + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs new file mode 100644 index 0000000000..fa209810c2 --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using Volo.Abp.DependencyInjection; + +namespace Volo.Abp.Mapperly; + +public class AbpMapperlyConventionalRegistrar : DefaultConventionalRegistrar +{ + protected override bool IsConventionalRegistrationDisabled(Type type) + { + return !type.GetInterfaces().Any(x => x.IsGenericType && typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition()) || + base.IsConventionalRegistrationDisabled(type); + } + + protected override List GetExposedServiceTypes(Type type) + { + var exposedServiceTypes = base.GetExposedServiceTypes(type); + var mapperlyInterfaces = type.GetInterfaces() + .Where(x => x.IsGenericType && typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition()).ToList(); + return exposedServiceTypes + .Union(mapperlyInterfaces) + .Distinct() + .ToList(); + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyModule.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyModule.cs new file mode 100644 index 0000000000..60381163eb --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyModule.cs @@ -0,0 +1,25 @@ +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Auditing; +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Mapperly; + +[DependsOn( + typeof(AbpObjectMappingModule), + typeof(AbpObjectExtendingModule), + typeof(AbpAuditingModule) +)] +public class AbpMapperlyModule : AbpModule +{ + public override void PreConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddConventionalRegistrar(new AbpMapperlyConventionalRegistrar()); + } + + public override void ConfigureServices(ServiceConfigurationContext context) + { + context.Services.AddMapperlyObjectMapper(); + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs new file mode 100644 index 0000000000..8de26067ef --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs @@ -0,0 +1,12 @@ +namespace Volo.Abp.Mapperly; + +public interface IAbpMapperly +{ + TDestination Map(TSource source); + + void Map(TSource source, TDestination destination); + + void BeforeMap(TSource source); + + void AfterMap(TSource source, TDestination destination); +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesAttribute.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesAttribute.cs new file mode 100644 index 0000000000..705a1af72d --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesAttribute.cs @@ -0,0 +1,14 @@ +using System; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.Mapperly; + +[AttributeUsage(AttributeTargets.Class)] +public class MapExtraPropertiesAttribute : Attribute +{ + public MappingPropertyDefinitionChecks DefinitionChecks { get; set; } = MappingPropertyDefinitionChecks.Null; + + public string[]? IgnoredProperties { get; set; } + + public bool MapToRegularProperties { get; set; } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs new file mode 100644 index 0000000000..62b7346197 --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs @@ -0,0 +1,71 @@ +using System; +using System.Collections.Concurrent; +using System.Linq.Expressions; +using System.Reflection; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.Mapperly; + +public static class MapExtraPropertiesInvoker +{ + private delegate void MapMethodDelegate( + object targetInstance, + IHasExtraProperties source, + IHasExtraProperties destination, + ExtraPropertyDictionary destinationExtraProperty, + MappingPropertyDefinitionChecks? definitionChecks, + string[]? ignoredProperties, + bool mapToRegularProperties); + + private readonly static ConcurrentDictionary<(Type, Type), MapMethodDelegate> Cache = new(); + + private readonly static MethodInfo MethodDefinition = typeof(MapperlyAutoObjectMappingProvider).GetMethod("MapExtraProperties", BindingFlags.Instance | BindingFlags.NonPublic)!; + + public static void Invoke( + object targetInstance, + IHasExtraProperties source, + IHasExtraProperties destination, + ExtraPropertyDictionary destinationExtraProperty, + MappingPropertyDefinitionChecks? definitionChecks = null, + string[]? ignoredProperties = null, + bool mapToRegularProperties = false) + { + var key = (typeof(TSource), typeof(TDestination)); + + var action = Cache.GetOrAdd(key, static key => + { + var genericMethod = MethodDefinition.MakeGenericMethod(key.Item1, key.Item2); + + var targetParam = Expression.Parameter(typeof(object), "target"); + var sourceParam = Expression.Parameter(typeof(IHasExtraProperties), "source"); + var destParam = Expression.Parameter(typeof(IHasExtraProperties), "destination"); + var checksParam = Expression.Parameter(typeof(MappingPropertyDefinitionChecks?), "checks"); + var destinationExtraPropertyParam = Expression.Parameter(typeof(ExtraPropertyDictionary), "destinationExtraProperty"); + var ignoredParam = Expression.Parameter(typeof(string[]), "ignored"); + var mapFlagParam = Expression.Parameter(typeof(bool), "mapFlag"); + + var instanceCast = Expression.Convert(targetParam, typeof(MapperlyAutoObjectMappingProvider)); + + var call = Expression.Call( + instanceCast, + genericMethod, + sourceParam, + destParam, + destinationExtraPropertyParam, + checksParam, + ignoredParam, + mapFlagParam + ); + + var lambda = Expression.Lambda( + call, + targetParam, sourceParam, destParam, destinationExtraPropertyParam, checksParam, ignoredParam, mapFlagParam + ); + + return lambda.Compile(); + }); + + action(targetInstance, source, destination, destinationExtraProperty, definitionChecks, ignoredProperties, mapToRegularProperties); + } +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs new file mode 100644 index 0000000000..550590f269 --- /dev/null +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Reflection; +using Microsoft.Extensions.DependencyInjection; +using Volo.Abp.Data; +using Volo.Abp.ObjectExtending; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Mapperly; + +public class MapperlyAutoObjectMappingProvider : MapperlyAutoObjectMappingProvider, IAutoObjectMappingProvider +{ + public MapperlyAutoObjectMappingProvider(IServiceProvider serviceProvider) + : base(serviceProvider) + { + } +} + +public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider +{ + protected IServiceProvider ServiceProvider { get; } + + public MapperlyAutoObjectMappingProvider(IServiceProvider serviceProvider) + { + ServiceProvider = serviceProvider; + } + + public virtual TDestination Map(object source) + { + var mapper = ServiceProvider.GetService>(); + if (mapper != null) + { + mapper.BeforeMap((TSource)source); + var destination = mapper.Map((TSource)source); + TryMapExtraProperties(mapper, (TSource)source, destination, new ExtraPropertyDictionary()); + mapper.AfterMap((TSource)source, destination); + return destination; + } + + throw new AbpException($"No {nameof(IAbpMapperly)} mapper found for {typeof(TSource).FullName} to {typeof(TDestination).FullName}"); + } + + public virtual TDestination Map(TSource source, TDestination destination) + { + var mapper = ServiceProvider.GetService>(); + if (mapper != null) + { + mapper.BeforeMap(source); + var destinationExtraProperties = GetExtraProperties(destination); + mapper.Map(source, destination); + TryMapExtraProperties(mapper, source, destination, destinationExtraProperties); + mapper.AfterMap(source, destination); + return destination; + } + + throw new AbpException($"No {nameof(IAbpMapperly)} mapper found for {typeof(TSource).FullName} to {typeof(TDestination).FullName}"); + } + + protected virtual ExtraPropertyDictionary GetExtraProperties(TDestination destination) + { + var extraProperties = new ExtraPropertyDictionary(); + if (destination is not IHasExtraProperties hasExtraProperties) + { + return extraProperties; + } + + foreach (var property in hasExtraProperties.ExtraProperties) + { + extraProperties.Add(property.Key, property.Value); + } + return extraProperties; + } + + protected virtual void TryMapExtraProperties(IAbpMapperly mapper, TSource source, TDestination destination, ExtraPropertyDictionary destinationExtraProperty) + { + var mapToRegularPropertiesAttribute = mapper.GetType().GetSingleAttributeOrNull(); + if (mapToRegularPropertiesAttribute != null && + typeof(IHasExtraProperties).IsAssignableFrom(typeof(TDestination)) && + typeof(IHasExtraProperties).IsAssignableFrom(typeof(TSource))) + { + MapExtraPropertiesInvoker.Invoke(this, + source!.As(), + destination!.As(), + destinationExtraProperty, + mapToRegularPropertiesAttribute.DefinitionChecks, + mapToRegularPropertiesAttribute.IgnoredProperties, + mapToRegularPropertiesAttribute.MapToRegularProperties + ); + } + } + + protected virtual void MapExtraProperties( + IHasExtraProperties source, + IHasExtraProperties destination, + ExtraPropertyDictionary destinationExtraProperty, + MappingPropertyDefinitionChecks? definitionChecks = null, + string[]? ignoredProperties = null, + bool mapToRegularProperties = false) + where TSource : IHasExtraProperties + where TDestination : IHasExtraProperties + { + var result = destinationExtraProperty.IsNullOrEmpty() + ? new Dictionary() + : new Dictionary(destinationExtraProperty); + + if (source.ExtraProperties != null && destination.ExtraProperties != null) + { + ExtensibleObjectMapper + .MapExtraPropertiesTo( + source.ExtraProperties, + result, + definitionChecks, + ignoredProperties + ); + } + + ObjectHelper.TrySetProperty(destination, x => x.ExtraProperties, () => new ExtraPropertyDictionary(result)); + if (mapToRegularProperties) + { + destination.SetExtraPropertiesToRegularProperties(); + } + } +} diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs index eae85f77d7..48a3d37916 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObjectMapper.cs @@ -188,7 +188,7 @@ public static class ExtensibleObjectMapper return false; } - if (definitionChecks != null) + if (definitionChecks != null && definitionChecks.Value != MappingPropertyDefinitionChecks.Null) { if (definitionChecks.Value.HasFlag(MappingPropertyDefinitionChecks.Source)) { diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs index 04668476a0..f717e97714 100644 --- a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs +++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs @@ -5,20 +5,25 @@ namespace Volo.Abp.ObjectExtending; [Flags] public enum MappingPropertyDefinitionChecks : byte { + /// + /// Same as Null, We need to use this in Attribute to avoid null checks. + /// + Null = 0, + /// /// No check. Copy all extra properties from the source to the destination. /// - None = 0, + None = 1 << 0, /// /// Copy the extra properties defined for the source class. /// - Source = 1, + Source = 1 << 1, /// /// Copy the extra properties defined for the destination class. /// - Destination = 2, + Destination = 1 << 2, /// /// Copy extra properties defined for both of the source and destination classes. diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Mapperly/AbpAutoMapperExtensibleDtoExtensions_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Mapperly/AbpAutoMapperExtensibleDtoExtensions_Tests.cs new file mode 100644 index 0000000000..9e5fe4f8cf --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Mapperly/AbpAutoMapperExtensibleDtoExtensions_Tests.cs @@ -0,0 +1,82 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Data; +using Volo.Abp.Mapperly; +using Volo.Abp.ObjectExtending.TestObjects; +using Volo.Abp.Testing; +using Xunit; + +namespace Mapperly; + +public class AbpAutoMapperExtensibleDtoExtensions_Tests : AbpIntegratedTest +{ + private readonly Volo.Abp.ObjectMapping.IObjectMapper _objectMapper; + + public AbpAutoMapperExtensibleDtoExtensions_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default() + { + var person = new ExtensibleTestPerson() + .SetProperty("Name", "John") + .SetProperty("Age", 42) + .SetProperty("ChildCount", 2) + .SetProperty("Sex", "male") + .SetProperty("CityName", "Adana"); + + var personDto = new ExtensibleTestPersonDto() + .SetProperty("ExistingDtoProperty", "existing-value"); + + _objectMapper.Map(person, personDto); + + personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes + personDto.GetProperty("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values + personDto.GetProperty("ChildCount").ShouldBe(0); //Not defined in the source, but was set to the default value by ExtensibleTestPersonDto constructor + personDto.GetProperty("CityName").ShouldBeNull(); //Ignored, but was set to the default value by ExtensibleTestPersonDto constructor + personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination + personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes + } + + [Fact] + public void MapExtraProperties_Also_Should_Map_To_RegularProperties() + { + var person = new ExtensibleTestPerson() + .SetProperty("Name", "John") + .SetProperty("Age", 42); + + var personDto = new ExtensibleTestPersonWithRegularPropertiesDto() + .SetProperty("IsActive", true); + + _objectMapper.Map(person, personDto); + + //Defined in both classes + personDto.HasProperty("Name").ShouldBe(false); + personDto.Name.ShouldBe("John"); + + //Defined in both classes + personDto.HasProperty("Age").ShouldBe(false); + personDto.Age.ShouldBe(42); + + //Should not clear existing values + personDto.HasProperty("IsActive").ShouldBe(false); + personDto.IsActive.ShouldBe(true); + } + + [Fact(Skip = "Mapperly requires IHasExtraProperties.ExtraPropertyDictionary to be marked as nullable")] + public void MapExtraPropertiesTo_Should_Ignored_If_ExtraProperties_Is_Null() + { + var person = new ExtensibleTestPerson(); + person.SetExtraPropertiesAsNull(); + + var personDto = new ExtensibleTestPersonDto(); + personDto.SetExtraPropertiesAsNull(); + + Should.NotThrow(() => _objectMapper.Map(person, personDto)); + + person.ExtraProperties.ShouldBe(null); + personDto.ExtraProperties.ShouldBeEmpty(); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj b/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj new file mode 100644 index 0000000000..f58c4cf848 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj @@ -0,0 +1,18 @@ + + + + + + net9.0 + Volo.Abp.Mapperly.Tests + Volo.Abp.Mapperly.Tests + + + + + + + + + + diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs new file mode 100644 index 0000000000..0080517c36 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs @@ -0,0 +1,191 @@ +using System; +using System.Collections.Generic; +using System.Collections.ObjectModel; +using System.Linq; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Mapperly.SampleClasses; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class AbpAutoMapperModule_Specific_ObjectMapper_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpAutoMapperModule_Specific_ObjectMapper_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Should_Use_Specific_Object_Mapper_If_Registered() + { + var dto = _objectMapper.Map(new MyEntity { Number = 42 }); + dto.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + } + + [Fact] + public void Specific_Object_Mapper_Should_Be_Used_For_Collections_If_Registered() + { + // IEnumerable + _objectMapper.Map, IEnumerable>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var destination = new List() + { + new MyEntityDto2 { Number = 44 } + }; + var returnIEnumerable = _objectMapper.Map, IEnumerable>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnIEnumerable.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnIEnumerable).ShouldBeTrue(); + + // ICollection + _objectMapper.Map, ICollection>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var returnICollection = _objectMapper.Map, ICollection>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnICollection.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnICollection).ShouldBeTrue(); + + // Collection + _objectMapper.Map, Collection>(new Collection() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var destination2 = new Collection() + { + new MyEntityDto2 { Number = 44 } + }; + var returnCollection = _objectMapper.Map, Collection>( + new Collection() + { + new MyEntity { Number = 42 } + }, destination2); + returnCollection.First().Number.ShouldBe(43); + ReferenceEquals(destination2, returnCollection).ShouldBeTrue(); + + // IList + _objectMapper.Map, IList>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var returnIList = _objectMapper.Map, IList>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnIList.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnIList).ShouldBeTrue(); + + // List + _objectMapper.Map, List>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var returnList = _objectMapper.Map, List>( + new List() + { + new MyEntity { Number = 42 } + }, destination); + returnList.First().Number.ShouldBe(43); + ReferenceEquals(destination, returnList).ShouldBeTrue(); + + // Array + _objectMapper.Map(new MyEntity[] + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var destinationArray = new MyEntityDto2[] + { + new MyEntityDto2 { Number = 40 } + }; + var returnArray = _objectMapper.Map(new MyEntity[] + { + new MyEntity { Number = 42 } + }, destinationArray); + + returnArray.First().Number.ShouldBe(43); + + // array should not be changed. Same as Mapperly. + destinationArray.First().Number.ShouldBe(40); + ReferenceEquals(returnArray, destinationArray).ShouldBeFalse(); + } + + [Fact] + public void Specific_Object_Mapper_Should_Support_Multiple_IObjectMapper_Interfaces() + { + var myEntityDto2 = _objectMapper.Map(new MyEntity { Number = 42 }); + myEntityDto2.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + var myEntity = _objectMapper.Map(new MyEntityDto2 { Number = 42 }); + myEntity.Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + // IEnumerable + _objectMapper.Map, IEnumerable>(new List() + { + new MyEntity { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + + _objectMapper.Map, IEnumerable>(new List() + { + new MyEntityDto2 { Number = 42 } + }).First().Number.ShouldBe(43); //MyEntityToMyEntityDto2Mapper adds 1 to number of the source. + } + + [Fact] + public void Should_Use_Destination_Object_Constructor_If_Available() + { + var id = Guid.NewGuid(); + var dto = _objectMapper.Map(new MyEntity { Number = 42, Id = id }); + dto.Key.ShouldBe(id); + dto.No.ShouldBe(42); + } + + [Fact] + public void Should_Use_Destination_Object_MapFrom_Method_If_Available() + { + var id = Guid.NewGuid(); + var dto = new MyEntityDtoWithMappingMethods(); + _objectMapper.Map(new MyEntity { Number = 42, Id = id }, dto); + dto.Key.ShouldBe(id); + dto.No.ShouldBe(42); + } + + [Fact] + public void Should_Use_Source_Object_Method_If_Available_To_Create_New_Object() + { + var id = Guid.NewGuid(); + var entity = _objectMapper.Map(new MyEntityDtoWithMappingMethods { Key = id, No = 42 }); + entity.Id.ShouldBe(id); + entity.Number.ShouldBe(42); + } + + [Fact] + public void Should_Use_Source_Object_Method_If_Available_To_Map_Existing_Object() + { + var id = Guid.NewGuid(); + var entity = new MyEntity(); + _objectMapper.Map(new MyEntityDtoWithMappingMethods { Key = id, No = 42 }, entity); + entity.Id.ShouldBe(id); + entity.Number.ShouldBe(42); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs new file mode 100644 index 0000000000..f8d39985d8 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs @@ -0,0 +1,59 @@ +using Microsoft.Extensions.DependencyInjection; +using Riok.Mapperly.Abstractions; +using Shouldly; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class MyClass +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +public class MyClassDto +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +[Mapper] +public partial class MyClassMapper : AbpMapperlyBase +{ + public override partial MyClassDto Map(MyClass source); + + public override partial void Map(MyClass source, MyClassDto destination); + + public override void BeforeMap(MyClass source) + { + source.Name = "BeforeMap " + source.Name; + } + + public override void AfterMap(MyClass source, MyClassDto destination) + { + destination.Name = source.Name + " AfterMap"; + } +} + +public class AbpMapperlyBeforeAndAfterMethod_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpMapperlyBeforeAndAfterMethod_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void BeforeAndAfterMethods_Should_Be_Called_When_Mapping() + { + var myClass = new MyClass { Id = "1", Name = "Test" }; + + var myClassDto = _objectMapper.Map(myClass); + myClassDto.Name.ShouldBe("BeforeMap Test AfterMap"); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs new file mode 100644 index 0000000000..5edad59b1b --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs @@ -0,0 +1,38 @@ +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Mapperly.SampleClasses; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class AbpMapperlyModule_Basic_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpMapperlyModule_Basic_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Should_Replace_IAutoObjectMappingProvider() + { + Assert.True(ServiceProvider.GetRequiredService() is MapperlyAutoObjectMappingProvider); + } + + [Fact] + public void Should_Map_Objects_With_AutoMap_Attributes() + { + var dto = _objectMapper.Map(new MyEntity { Number = 42 }); + dto.Number.ShouldBe(42); + } + + [Fact] + public void Should_Map_Enum() + { + var dto = _objectMapper.Map(MyEnum.Value3); + dto.ShouldBe(MyEnumDto.Value2); //Value2 is same as Value3 + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/MapperlyTestModule.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/MapperlyTestModule.cs new file mode 100644 index 0000000000..8ff5ca2eb1 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/MapperlyTestModule.cs @@ -0,0 +1,13 @@ +using Volo.Abp.Modularity; +using Volo.Abp.ObjectExtending; + +namespace Volo.Abp.Mapperly; + +[DependsOn( + typeof(AbpMapperlyModule), + typeof(AbpObjectExtendingTestModule) +)] +public class MapperlyTestModule : AbpModule +{ + +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs new file mode 100644 index 0000000000..4cfd58a760 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs @@ -0,0 +1,32 @@ +using System; +using Microsoft.Extensions.DependencyInjection; +using Shouldly; +using Volo.Abp.Mapperly.SampleClasses; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class ObjectMapperExtensions_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public ObjectMapperExtensions_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Should_Map_Objects_With_AutoMap_Attributes() + { + var dto = _objectMapper.Map( + new MyEntity + { + Number = 42 + } + ); + + dto.As().Number.ShouldBe(42); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs new file mode 100644 index 0000000000..8d4666a184 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs @@ -0,0 +1,44 @@ +using Riok.Mapperly.Abstractions; +using Volo.Abp.Mapperly; +using Volo.Abp.Mapperly.SampleClasses; +using Volo.Abp.ObjectExtending.TestObjects; + +[Mapper] +public partial class MyEntityMapper : AbpMapperlyBase +{ + public override partial MyEntityDto Map(MyEntity source); + + public override partial void Map(MyEntity source, MyEntityDto destination); +} + +[Mapper] +public partial class MyEnumMapper : AbpMapperlyBase +{ + public override partial MyEnumDto Map(MyEnum source); + + public override void Map(MyEnum source, MyEnumDto destination) + { + destination = Map(source); + } +} + +[Mapper] +[MapExtraProperties(IgnoredProperties = ["CityName"])] +public partial class ExtensibleTestPersonMapper : AbpMapperlyBase +{ + public override partial ExtensibleTestPersonDto Map(ExtensibleTestPerson source); + + public override partial void Map(ExtensibleTestPerson source, ExtensibleTestPersonDto destination); +} + +[Mapper] +[MapExtraProperties(MapToRegularProperties = true)] +public partial class ExtensibleTestPersonWithRegularPropertiesDtoMapper : AbpMapperlyBase +{ + [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Name))] + [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Age))] + [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.IsActive))] + public override partial ExtensibleTestPersonWithRegularPropertiesDto Map(ExtensibleTestPerson source); + + public override partial void Map(ExtensibleTestPerson source, ExtensibleTestPersonWithRegularPropertiesDto destination); +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntity.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntity.cs new file mode 100644 index 0000000000..f137e476f1 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntity.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntity +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto.cs new file mode 100644 index 0000000000..7630b2d14e --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntityDto +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto2.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto2.cs new file mode 100644 index 0000000000..9d00eff9c4 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDto2.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntityDto2 +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDtoWithMappingMethods.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDtoWithMappingMethods.cs new file mode 100644 index 0000000000..bf8c774599 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityDtoWithMappingMethods.cs @@ -0,0 +1,43 @@ +using System; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Mapperly.SampleClasses; + +//TODO: Move tests to Volo.Abp.ObjectMapping test project +public class MyEntityDtoWithMappingMethods : IMapFrom, IMapTo +{ + public Guid Key { get; set; } + + public int No { get; set; } + + public MyEntityDtoWithMappingMethods() + { + + } + + public MyEntityDtoWithMappingMethods(MyEntity entity) + { + MapFrom(entity); + } + + public void MapFrom(MyEntity source) + { + Key = source.Id; + No = source.Number; + } + + MyEntity IMapTo.MapTo() + { + return new MyEntity + { + Id = Key, + Number = No + }; + } + + void IMapTo.MapTo(MyEntity destination) + { + destination.Id = Key; + destination.Number = No; + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityToMyEntityDto2Mapper.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityToMyEntityDto2Mapper.cs new file mode 100644 index 0000000000..cd73c98747 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEntityToMyEntityDto2Mapper.cs @@ -0,0 +1,39 @@ +using Volo.Abp.DependencyInjection; +using Volo.Abp.ObjectMapping; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyEntityToMyEntityDto2Mapper : IObjectMapper, IObjectMapper, ITransientDependency +{ + public MyEntityDto2 Map(MyEntity source) + { + return new MyEntityDto2 + { + Id = source.Id, + Number = source.Number + 1 + }; + } + + public MyEntityDto2 Map(MyEntity source, MyEntityDto2 destination) + { + destination.Id = source.Id; + destination.Number = source.Number + 1; + return destination; + } + + public MyEntity Map(MyEntityDto2 source) + { + return new MyEntity + { + Id = source.Id, + Number = source.Number + 1 + }; + } + + public MyEntity Map(MyEntityDto2 source, MyEntity destination) + { + destination.Id = source.Id; + destination.Number = source.Number + 1; + return destination; + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnum.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnum.cs new file mode 100644 index 0000000000..fe2b72300c --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnum.cs @@ -0,0 +1,8 @@ +namespace Volo.Abp.Mapperly.SampleClasses; + +public enum MyEnum +{ + Value1 = 1, + Value2, + Value3 +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnumDto.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnumDto.cs new file mode 100644 index 0000000000..40d28d0501 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyEnumDto.cs @@ -0,0 +1,9 @@ +namespace Volo.Abp.Mapperly.SampleClasses; + +public enum MyEnumDto +{ + Value1 = 2, + Value2, + Value3, + Value +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyNotMappedDto.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyNotMappedDto.cs new file mode 100644 index 0000000000..9fd2c556fb --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MyNotMappedDto.cs @@ -0,0 +1,10 @@ +using System; + +namespace Volo.Abp.Mapperly.SampleClasses; + +public class MyNotMappedDto +{ + public Guid Id { get; set; } + + public int Number { get; set; } +} diff --git a/nupkg/common.ps1 b/nupkg/common.ps1 index e916a379d2..83bbeb7bde 100644 --- a/nupkg/common.ps1 +++ b/nupkg/common.ps1 @@ -220,6 +220,7 @@ $projects = ( "framework/src/Volo.Abp.Ldap", "framework/src/Volo.Abp.Localization.Abstractions", "framework/src/Volo.Abp.MailKit", + "framework/src/Volo.Abp.Mapperly", "framework/src/Volo.Abp.Maui.Client", "framework/src/Volo.Abp.Localization", "framework/src/Volo.Abp.MemoryDb", From 3daa6c51c3c9bc6f98934890b077d77de8876428 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 9 Jul 2025 20:04:58 +0800 Subject: [PATCH 2/9] feat: Implement reverse mapping functionality and add related tests --- .../Volo/Abp/Mapperly/AbpMapperlyBase.cs | 15 +++- .../AbpMapperlyConventionalRegistrar.cs | 3 +- .../Volo/Abp/Mapperly/IAbpMapperly.cs | 13 +++- .../Abp/Mapperly/MapExtraPropertiesInvoker.cs | 8 +-- .../MapperlyAutoObjectMappingProvider.cs | 10 +++ .../AbpMapperly_Dependency_Injection_Tests.cs | 68 ++++++++++++++++++ .../Abp/Mapperly/AbpReverseMapperly_Tests.cs | 72 +++++++++++++++++++ 7 files changed, 182 insertions(+), 7 deletions(-) create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs create mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs index 14165be56e..cc86dd8be8 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs @@ -10,10 +10,23 @@ public abstract class AbpMapperlyBase : IAbpMapperly : AbpMapperlyBase, IAbpReverseMapperly +{ + public abstract TSource ReverseMap(TDestination destination); + + public abstract void ReverseMap(TDestination destination, TSource source); + + public virtual void BeforeReverseMap(TDestination destination) + { + } + public virtual void AfterReverseMap(TDestination destination, TSource source) + { } } diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs index fa209810c2..a4af516425 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs @@ -17,7 +17,8 @@ public class AbpMapperlyConventionalRegistrar : DefaultConventionalRegistrar { var exposedServiceTypes = base.GetExposedServiceTypes(type); var mapperlyInterfaces = type.GetInterfaces() - .Where(x => x.IsGenericType && typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition()).ToList(); + .Where(x => x.IsGenericType && typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition() || + x.IsGenericType && typeof(IAbpReverseMapperly<,>) == x.GetGenericTypeDefinition()).ToList(); return exposedServiceTypes .Union(mapperlyInterfaces) .Distinct() diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs index 8de26067ef..e64bc38e85 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs @@ -1,6 +1,6 @@ namespace Volo.Abp.Mapperly; -public interface IAbpMapperly +public interface IAbpMapperly { TDestination Map(TSource source); @@ -10,3 +10,14 @@ public interface IAbpMapperly void AfterMap(TSource source, TDestination destination); } + +public interface IAbpReverseMapperly : IAbpMapperly +{ + TSource ReverseMap(TDestination destination); + + void ReverseMap(TDestination destination, TSource source); + + void BeforeReverseMap(TDestination destination); + + void AfterReverseMap(TDestination destination, TSource source); +} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs index 62b7346197..f1eb60ec9f 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs @@ -31,10 +31,10 @@ public static class MapExtraPropertiesInvoker string[]? ignoredProperties = null, bool mapToRegularProperties = false) { - var key = (typeof(TSource), typeof(TDestination)); - - var action = Cache.GetOrAdd(key, static key => + var mapExtraProperties = Cache.GetOrAdd((typeof(TSource), typeof(TDestination)), static key => { + Check.NotNull(MethodDefinition, nameof(MethodDefinition)); + var genericMethod = MethodDefinition.MakeGenericMethod(key.Item1, key.Item2); var targetParam = Expression.Parameter(typeof(object), "target"); @@ -66,6 +66,6 @@ public static class MapExtraPropertiesInvoker return lambda.Compile(); }); - action(targetInstance, source, destination, destinationExtraProperty, definitionChecks, ignoredProperties, mapToRegularProperties); + mapExtraProperties(targetInstance, source, destination, destinationExtraProperty, definitionChecks, ignoredProperties, mapToRegularProperties); } } diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index 550590f269..289f5d2c68 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -37,6 +37,16 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider return destination; } + var reverseMapper = ServiceProvider.GetService>(); + if (reverseMapper != null) + { + reverseMapper.BeforeReverseMap((TSource)source); + var destination = reverseMapper.ReverseMap((TSource)source); + TryMapExtraProperties(reverseMapper.As>(), destination, (TSource)source, GetExtraProperties(destination)); + reverseMapper.AfterReverseMap((TSource)source, destination); + return destination; + } + throw new AbpException($"No {nameof(IAbpMapperly)} mapper found for {typeof(TSource).FullName} to {typeof(TDestination).FullName}"); } diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs new file mode 100644 index 0000000000..cecd4d5272 --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs @@ -0,0 +1,68 @@ +using System; +using Riok.Mapperly.Abstractions; +using Shouldly; +using Volo.Abp.DependencyInjection; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class MyDIClass +{ + public string Id { get; set; } + + public DateTime Birthday { get; set; } +} + +public class MyDIClassDto +{ + public string Id { get; set; } + + public DateTime Birthday { get; set; } +} + +public class BirthdayCalculatorService : ITransientDependency +{ + public DateTime Birthday => DateTime.Parse("2025-01-01"); +} + +[Mapper] +public partial class MyDIClassMapper : AbpMapperlyBase +{ + private readonly BirthdayCalculatorService _birthdayCalculatorService; + + public MyDIClassMapper(BirthdayCalculatorService birthdayCalculatorService) + { + _birthdayCalculatorService = birthdayCalculatorService; + } + + public override partial MyDIClassDto Map(MyDIClass source); + + public override partial void Map(MyDIClass source, MyDIClassDto destination); + + public override void AfterMap(MyDIClass source, MyDIClassDto destination) + { + destination.Birthday = _birthdayCalculatorService.Birthday; + } +} + +public class AbpMapperly_Dependency_Injection_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + private readonly BirthdayCalculatorService _birthdayCalculatorService; + + public AbpMapperly_Dependency_Injection_Tests() + { + _objectMapper = GetRequiredService(); + _birthdayCalculatorService = GetRequiredService(); + } + + [Fact] + public void DI_Test() + { + var myClass = new MyDIClass { Id = "1", Birthday = DateTime.Now }; + var myClassDto = _objectMapper.Map(myClass); + myClassDto.Birthday.ShouldBe(_birthdayCalculatorService.Birthday); + } +} diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs new file mode 100644 index 0000000000..c0604f726b --- /dev/null +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs @@ -0,0 +1,72 @@ +using Microsoft.Extensions.DependencyInjection; +using Riok.Mapperly.Abstractions; +using Shouldly; +using Volo.Abp.ObjectMapping; +using Volo.Abp.Testing; +using Xunit; + +namespace Volo.Abp.Mapperly; + +public class MyReverseClass +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +public class MyReverseClassDto +{ + public string Id { get; set; } + + public string Name { get; set; } +} + +[Mapper] +public partial class MyReverseClassMapper : AbpReverseMapperlyBase +{ + public override partial MyReverseClassDto Map(MyReverseClass source); + + public override partial void Map(MyReverseClass source, MyReverseClassDto destination); + + public override partial MyReverseClass ReverseMap(MyReverseClassDto destination); + + public override partial void ReverseMap(MyReverseClassDto destination, MyReverseClass source); + + public override void BeforeReverseMap(MyReverseClassDto destination) + { + destination.Name = "BeforeReverseMap " + destination.Name; + } + + public override void AfterReverseMap(MyReverseClassDto destination, MyReverseClass source) + { + source.Name = destination.Name + " AfterReverseMap"; + } +} + +public class AbpReverseMapperly_Tests : AbpIntegratedTest +{ + private readonly IObjectMapper _objectMapper; + + public AbpReverseMapperly_Tests() + { + _objectMapper = ServiceProvider.GetRequiredService(); + } + + [Fact] + public void Map_Test() + { + var myClass = new MyReverseClass { Id = "1", Name = "Test" }; + var myClassDto = _objectMapper.Map(myClass); + myClassDto.Name.ShouldBe("Test"); + } + + [Fact] + public void ReverseMap_Test() + { + var myClassDto = new MyReverseClassDto { Id = "1", Name = "Test" }; + + var myClass = _objectMapper.Map(myClassDto); + + myClass.Name.ShouldBe("BeforeReverseMap Test AfterReverseMap"); + } +} From 715b25ed7c815c10ab7d4291bec53a310e883743 Mon Sep 17 00:00:00 2001 From: maliming Date: Wed, 9 Jul 2025 20:14:59 +0800 Subject: [PATCH 3/9] Update Riok.Mapperly package references --- framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj | 2 +- .../test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj b/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj index 672936d4f7..7c085a146e 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj +++ b/framework/src/Volo.Abp.Mapperly/Volo.Abp.Mapperly.csproj @@ -23,7 +23,7 @@ - + diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj b/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj index f58c4cf848..2ea799a4c9 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo.Abp.Mapperly.Tests.csproj @@ -13,6 +13,5 @@ - From 1f95fb511e0ff6cbc5c1aecd60532bd0d0118570 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 10 Jul 2025 13:23:00 +0800 Subject: [PATCH 4/9] Enhance Mapperly mapping and update related tests --- .../AbpMapperlyConventionalRegistrar.cs | 6 ++-- .../Volo/Abp/Mapperly/IAbpMapperly.cs | 2 +- .../MapperlyAutoObjectMappingProvider.cs | 18 +++++++++-- .../Mapperly/AbpMapperlyModule_Basic_Tests.cs | 24 +++++++++++++- ...erlyModule_Specific_ObjectMapper_Tests.cs} | 4 +-- .../Mapperly/ObjectMapperExtensions_Tests.cs | 32 ------------------- 6 files changed, 45 insertions(+), 41 deletions(-) rename framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/{AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs => AbpMapperlyModule_Specific_ObjectMapper_Tests.cs} (97%) delete mode 100644 framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs index a4af516425..5c1627b0a2 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs @@ -16,9 +16,9 @@ public class AbpMapperlyConventionalRegistrar : DefaultConventionalRegistrar protected override List GetExposedServiceTypes(Type type) { var exposedServiceTypes = base.GetExposedServiceTypes(type); - var mapperlyInterfaces = type.GetInterfaces() - .Where(x => x.IsGenericType && typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition() || - x.IsGenericType && typeof(IAbpReverseMapperly<,>) == x.GetGenericTypeDefinition()).ToList(); + var mapperlyInterfaces = type.GetInterfaces().Where(x => + x.IsGenericType && (typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition() || + typeof(IAbpReverseMapperly<,>) == x.GetGenericTypeDefinition())); return exposedServiceTypes .Union(mapperlyInterfaces) .Distinct() diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs index e64bc38e85..5132fc980e 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs @@ -1,6 +1,6 @@ namespace Volo.Abp.Mapperly; -public interface IAbpMapperly +public interface IAbpMapperly { TDestination Map(TSource source); diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index 289f5d2c68..28385da5f5 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -5,6 +5,7 @@ using Microsoft.Extensions.DependencyInjection; using Volo.Abp.Data; using Volo.Abp.ObjectExtending; using Volo.Abp.ObjectMapping; +using Volo.Abp.Reflection; namespace Volo.Abp.Mapperly; @@ -47,7 +48,8 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider return destination; } - throw new AbpException($"No {nameof(IAbpMapperly)} mapper found for {typeof(TSource).FullName} to {typeof(TDestination).FullName}"); + throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperly))} or" + + $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperly))} was found"); } public virtual TDestination Map(TSource source, TDestination destination) @@ -63,7 +65,19 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider return destination; } - throw new AbpException($"No {nameof(IAbpMapperly)} mapper found for {typeof(TSource).FullName} to {typeof(TDestination).FullName}"); + var reverseMapper = ServiceProvider.GetService>(); + if (reverseMapper != null) + { + reverseMapper.BeforeReverseMap(source); + var destinationExtraProperties = GetExtraProperties(destination); + reverseMapper.ReverseMap(source, destination); + TryMapExtraProperties(reverseMapper.As>(), source, destination, destinationExtraProperties); + reverseMapper.AfterReverseMap(source, destination); + return destination; + } + + throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperly))} or" + + $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperly))} was found"); } protected virtual ExtraPropertyDictionary GetExtraProperties(TDestination destination) diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs index 5edad59b1b..96573fca52 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs @@ -1,4 +1,5 @@ -using Microsoft.Extensions.DependencyInjection; +using System; +using Microsoft.Extensions.DependencyInjection; using Shouldly; using Volo.Abp.Mapperly.SampleClasses; using Volo.Abp.ObjectMapping; @@ -29,10 +30,31 @@ public class AbpMapperlyModule_Basic_Tests : AbpIntegratedTest(new MyEntity { Id = Guid.NewGuid(), Number = 43 }, dto); + + dto.Number.ShouldBe(43); + dto.Id.ShouldNotBe(Guid.Empty); + } + [Fact] public void Should_Map_Enum() { var dto = _objectMapper.Map(MyEnum.Value3); dto.ShouldBe(MyEnumDto.Value2); //Value2 is same as Value3 } + + [Fact] + public void Should_Throw_Exception_If_Mapper_Is_Not_Found() + { + var exception = Assert.Throws(() =>_objectMapper.Map(new MyEntity())); + exception.Message.ShouldBe("No " + + "Volo.Abp.Mapperly.IAbpMapperly or " + + "Volo.Abp.Mapperly.IAbpReverseMapperly" + + " was found"); + } } diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Specific_ObjectMapper_Tests.cs similarity index 97% rename from framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs rename to framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Specific_ObjectMapper_Tests.cs index 0080517c36..60f4294aeb 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpAutoMapperModule_Specific_ObjectMapper_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Specific_ObjectMapper_Tests.cs @@ -11,11 +11,11 @@ using Xunit; namespace Volo.Abp.Mapperly; -public class AbpAutoMapperModule_Specific_ObjectMapper_Tests : AbpIntegratedTest +public class AbpMapperlyModule_Specific_ObjectMapper_Tests : AbpIntegratedTest { private readonly IObjectMapper _objectMapper; - public AbpAutoMapperModule_Specific_ObjectMapper_Tests() + public AbpMapperlyModule_Specific_ObjectMapper_Tests() { _objectMapper = ServiceProvider.GetRequiredService(); } diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs deleted file mode 100644 index 4cfd58a760..0000000000 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/ObjectMapperExtensions_Tests.cs +++ /dev/null @@ -1,32 +0,0 @@ -using System; -using Microsoft.Extensions.DependencyInjection; -using Shouldly; -using Volo.Abp.Mapperly.SampleClasses; -using Volo.Abp.ObjectMapping; -using Volo.Abp.Testing; -using Xunit; - -namespace Volo.Abp.Mapperly; - -public class ObjectMapperExtensions_Tests : AbpIntegratedTest -{ - private readonly IObjectMapper _objectMapper; - - public ObjectMapperExtensions_Tests() - { - _objectMapper = ServiceProvider.GetRequiredService(); - } - - [Fact] - public void Should_Map_Objects_With_AutoMap_Attributes() - { - var dto = _objectMapper.Map( - new MyEntity - { - Number = 42 - } - ); - - dto.As().Number.ShouldBe(42); - } -} From de946d6b72d20360fc0cdc5b711da493a681e88a Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 10 Jul 2025 14:03:05 +0800 Subject: [PATCH 5/9] feat: Refactor mapping interfaces and classes for improved consistency and functionality --- .../AbpMapperlyConventionalRegistrar.cs | 6 ++--- ...pperlyBase.cs => AbpMapperlyMapperBase.cs} | 4 ++-- ...{IAbpMapperly.cs => IAbpMapperlyMapper.cs} | 4 ++-- .../MapperlyAutoObjectMappingProvider.cs | 22 +++++++++---------- .../AbpMapperlyBeforeAndAfterMethod_Tests.cs | 2 +- .../Mapperly/AbpMapperlyModule_Basic_Tests.cs | 4 ++-- .../AbpMapperly_Dependency_Injection_Tests.cs | 2 +- .../Abp/Mapperly/AbpReverseMapperly_Tests.cs | 2 +- .../Mapperly/SampleClasses/MapperlyMappers.cs | 8 +++---- 9 files changed, 27 insertions(+), 27 deletions(-) rename framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/{AbpMapperlyBase.cs => AbpMapperlyMapperBase.cs} (68%) rename framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/{IAbpMapperly.cs => IAbpMapperlyMapper.cs} (73%) diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs index 5c1627b0a2..028a72bb67 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyConventionalRegistrar.cs @@ -9,7 +9,7 @@ public class AbpMapperlyConventionalRegistrar : DefaultConventionalRegistrar { protected override bool IsConventionalRegistrationDisabled(Type type) { - return !type.GetInterfaces().Any(x => x.IsGenericType && typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition()) || + return !type.GetInterfaces().Any(x => x.IsGenericType && typeof(IAbpMapperlyMapper<,>) == x.GetGenericTypeDefinition()) || base.IsConventionalRegistrationDisabled(type); } @@ -17,8 +17,8 @@ public class AbpMapperlyConventionalRegistrar : DefaultConventionalRegistrar { var exposedServiceTypes = base.GetExposedServiceTypes(type); var mapperlyInterfaces = type.GetInterfaces().Where(x => - x.IsGenericType && (typeof(IAbpMapperly<,>) == x.GetGenericTypeDefinition() || - typeof(IAbpReverseMapperly<,>) == x.GetGenericTypeDefinition())); + x.IsGenericType && (typeof(IAbpMapperlyMapper<,>) == x.GetGenericTypeDefinition() || + typeof(IAbpReverseMapperlyMapper<,>) == x.GetGenericTypeDefinition())); return exposedServiceTypes .Union(mapperlyInterfaces) .Distinct() diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs similarity index 68% rename from framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs rename to framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs index cc86dd8be8..008c3635ea 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyBase.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs @@ -2,7 +2,7 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Mapperly; -public abstract class AbpMapperlyBase : IAbpMapperly, ITransientDependency +public abstract class AbpMapperlyMapperBase : IAbpMapperlyMapper, ITransientDependency { public abstract TDestination Map(TSource source); @@ -16,7 +16,7 @@ public abstract class AbpMapperlyBase : IAbpMapperly : AbpMapperlyBase, IAbpReverseMapperly +public abstract class AbpReverseMapperlyMapperBase : AbpMapperlyMapperBase, IAbpReverseMapperlyMapper { public abstract TSource ReverseMap(TDestination destination); diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs similarity index 73% rename from framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs rename to framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs index 5132fc980e..3d391cfe1f 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperly.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs @@ -1,6 +1,6 @@ namespace Volo.Abp.Mapperly; -public interface IAbpMapperly +public interface IAbpMapperlyMapper { TDestination Map(TSource source); @@ -11,7 +11,7 @@ public interface IAbpMapperly void AfterMap(TSource source, TDestination destination); } -public interface IAbpReverseMapperly : IAbpMapperly +public interface IAbpReverseMapperlyMapper : IAbpMapperlyMapper { TSource ReverseMap(TDestination destination); diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index 28385da5f5..6e0e36d071 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -28,7 +28,7 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider public virtual TDestination Map(object source) { - var mapper = ServiceProvider.GetService>(); + var mapper = ServiceProvider.GetService>(); if (mapper != null) { mapper.BeforeMap((TSource)source); @@ -38,23 +38,23 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider return destination; } - var reverseMapper = ServiceProvider.GetService>(); + var reverseMapper = ServiceProvider.GetService>(); if (reverseMapper != null) { reverseMapper.BeforeReverseMap((TSource)source); var destination = reverseMapper.ReverseMap((TSource)source); - TryMapExtraProperties(reverseMapper.As>(), destination, (TSource)source, GetExtraProperties(destination)); + TryMapExtraProperties(reverseMapper.As>(), destination, (TSource)source, GetExtraProperties(destination)); reverseMapper.AfterReverseMap((TSource)source, destination); return destination; } - throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperly))} or" + - $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperly))} was found"); + throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperlyMapper))} or" + + $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperlyMapper))} was found"); } public virtual TDestination Map(TSource source, TDestination destination) { - var mapper = ServiceProvider.GetService>(); + var mapper = ServiceProvider.GetService>(); if (mapper != null) { mapper.BeforeMap(source); @@ -65,19 +65,19 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider return destination; } - var reverseMapper = ServiceProvider.GetService>(); + var reverseMapper = ServiceProvider.GetService>(); if (reverseMapper != null) { reverseMapper.BeforeReverseMap(source); var destinationExtraProperties = GetExtraProperties(destination); reverseMapper.ReverseMap(source, destination); - TryMapExtraProperties(reverseMapper.As>(), source, destination, destinationExtraProperties); + TryMapExtraProperties(reverseMapper.As>(), source, destination, destinationExtraProperties); reverseMapper.AfterReverseMap(source, destination); return destination; } - throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperly))} or" + - $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperly))} was found"); + throw new AbpException($"No {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpMapperlyMapper))} or" + + $" {TypeHelper.GetFullNameHandlingNullableAndGenerics(typeof(IAbpReverseMapperlyMapper))} was found"); } protected virtual ExtraPropertyDictionary GetExtraProperties(TDestination destination) @@ -95,7 +95,7 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider return extraProperties; } - protected virtual void TryMapExtraProperties(IAbpMapperly mapper, TSource source, TDestination destination, ExtraPropertyDictionary destinationExtraProperty) + protected virtual void TryMapExtraProperties(IAbpMapperlyMapper mapper, TSource source, TDestination destination, ExtraPropertyDictionary destinationExtraProperty) { var mapToRegularPropertiesAttribute = mapper.GetType().GetSingleAttributeOrNull(); if (mapToRegularPropertiesAttribute != null && diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs index f8d39985d8..94e2b4ef7e 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs @@ -22,7 +22,7 @@ public class MyClassDto } [Mapper] -public partial class MyClassMapper : AbpMapperlyBase +public partial class MyClassMapper : AbpMapperlyMapperBase { public override partial MyClassDto Map(MyClass source); diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs index 96573fca52..89861f6947 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyModule_Basic_Tests.cs @@ -53,8 +53,8 @@ public class AbpMapperlyModule_Basic_Tests : AbpIntegratedTest(() =>_objectMapper.Map(new MyEntity())); exception.Message.ShouldBe("No " + - "Volo.Abp.Mapperly.IAbpMapperly or " + - "Volo.Abp.Mapperly.IAbpReverseMapperly" + + "Volo.Abp.Mapperly.IAbpMapperlyMapper or " + + "Volo.Abp.Mapperly.IAbpReverseMapperlyMapper" + " was found"); } } diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs index cecd4d5272..ecc0f7ecde 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs @@ -28,7 +28,7 @@ public class BirthdayCalculatorService : ITransientDependency } [Mapper] -public partial class MyDIClassMapper : AbpMapperlyBase +public partial class MyDIClassMapper : AbpMapperlyMapperBase { private readonly BirthdayCalculatorService _birthdayCalculatorService; diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs index c0604f726b..cb0b493f5a 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs @@ -22,7 +22,7 @@ public class MyReverseClassDto } [Mapper] -public partial class MyReverseClassMapper : AbpReverseMapperlyBase +public partial class MyReverseClassMapper : AbpReverseMapperlyMapperBase { public override partial MyReverseClassDto Map(MyReverseClass source); diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs index 8d4666a184..22ba9be77e 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs @@ -4,7 +4,7 @@ using Volo.Abp.Mapperly.SampleClasses; using Volo.Abp.ObjectExtending.TestObjects; [Mapper] -public partial class MyEntityMapper : AbpMapperlyBase +public partial class MyEntityMapper : AbpMapperlyMapperBase { public override partial MyEntityDto Map(MyEntity source); @@ -12,7 +12,7 @@ public partial class MyEntityMapper : AbpMapperlyBase } [Mapper] -public partial class MyEnumMapper : AbpMapperlyBase +public partial class MyEnumMapper : AbpMapperlyMapperBase { public override partial MyEnumDto Map(MyEnum source); @@ -24,7 +24,7 @@ public partial class MyEnumMapper : AbpMapperlyBase [Mapper] [MapExtraProperties(IgnoredProperties = ["CityName"])] -public partial class ExtensibleTestPersonMapper : AbpMapperlyBase +public partial class ExtensibleTestPersonMapper : AbpMapperlyMapperBase { public override partial ExtensibleTestPersonDto Map(ExtensibleTestPerson source); @@ -33,7 +33,7 @@ public partial class ExtensibleTestPersonMapper : AbpMapperlyBase +public partial class ExtensibleTestPersonWithRegularPropertiesDtoMapper : AbpMapperlyMapperBase { [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Name))] [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Age))] From 74589a9323a83dd996bd63cf0c251b92ced4db41 Mon Sep 17 00:00:00 2001 From: maliming Date: Thu, 10 Jul 2025 15:18:46 +0800 Subject: [PATCH 6/9] Remove MapExtraPropertiesInvoker and update mapping logic --- .../Abp/Mapperly/MapExtraPropertiesInvoker.cs | 71 ------------------- .../MapperlyAutoObjectMappingProvider.cs | 8 +-- 2 files changed, 4 insertions(+), 75 deletions(-) delete mode 100644 framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs deleted file mode 100644 index f1eb60ec9f..0000000000 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapExtraPropertiesInvoker.cs +++ /dev/null @@ -1,71 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Linq.Expressions; -using System.Reflection; -using Volo.Abp.Data; -using Volo.Abp.ObjectExtending; - -namespace Volo.Abp.Mapperly; - -public static class MapExtraPropertiesInvoker -{ - private delegate void MapMethodDelegate( - object targetInstance, - IHasExtraProperties source, - IHasExtraProperties destination, - ExtraPropertyDictionary destinationExtraProperty, - MappingPropertyDefinitionChecks? definitionChecks, - string[]? ignoredProperties, - bool mapToRegularProperties); - - private readonly static ConcurrentDictionary<(Type, Type), MapMethodDelegate> Cache = new(); - - private readonly static MethodInfo MethodDefinition = typeof(MapperlyAutoObjectMappingProvider).GetMethod("MapExtraProperties", BindingFlags.Instance | BindingFlags.NonPublic)!; - - public static void Invoke( - object targetInstance, - IHasExtraProperties source, - IHasExtraProperties destination, - ExtraPropertyDictionary destinationExtraProperty, - MappingPropertyDefinitionChecks? definitionChecks = null, - string[]? ignoredProperties = null, - bool mapToRegularProperties = false) - { - var mapExtraProperties = Cache.GetOrAdd((typeof(TSource), typeof(TDestination)), static key => - { - Check.NotNull(MethodDefinition, nameof(MethodDefinition)); - - var genericMethod = MethodDefinition.MakeGenericMethod(key.Item1, key.Item2); - - var targetParam = Expression.Parameter(typeof(object), "target"); - var sourceParam = Expression.Parameter(typeof(IHasExtraProperties), "source"); - var destParam = Expression.Parameter(typeof(IHasExtraProperties), "destination"); - var checksParam = Expression.Parameter(typeof(MappingPropertyDefinitionChecks?), "checks"); - var destinationExtraPropertyParam = Expression.Parameter(typeof(ExtraPropertyDictionary), "destinationExtraProperty"); - var ignoredParam = Expression.Parameter(typeof(string[]), "ignored"); - var mapFlagParam = Expression.Parameter(typeof(bool), "mapFlag"); - - var instanceCast = Expression.Convert(targetParam, typeof(MapperlyAutoObjectMappingProvider)); - - var call = Expression.Call( - instanceCast, - genericMethod, - sourceParam, - destParam, - destinationExtraPropertyParam, - checksParam, - ignoredParam, - mapFlagParam - ); - - var lambda = Expression.Lambda( - call, - targetParam, sourceParam, destParam, destinationExtraPropertyParam, checksParam, ignoredParam, mapFlagParam - ); - - return lambda.Compile(); - }); - - mapExtraProperties(targetInstance, source, destination, destinationExtraProperty, definitionChecks, ignoredProperties, mapToRegularProperties); - } -} diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index 6e0e36d071..19f677515b 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -102,7 +102,7 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider typeof(IHasExtraProperties).IsAssignableFrom(typeof(TDestination)) && typeof(IHasExtraProperties).IsAssignableFrom(typeof(TSource))) { - MapExtraPropertiesInvoker.Invoke(this, + MapExtraProperties( source!.As(), destination!.As(), destinationExtraProperty, @@ -120,8 +120,6 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider MappingPropertyDefinitionChecks? definitionChecks = null, string[]? ignoredProperties = null, bool mapToRegularProperties = false) - where TSource : IHasExtraProperties - where TDestination : IHasExtraProperties { var result = destinationExtraProperty.IsNullOrEmpty() ? new Dictionary() @@ -130,7 +128,9 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider if (source.ExtraProperties != null && destination.ExtraProperties != null) { ExtensibleObjectMapper - .MapExtraPropertiesTo( + .MapExtraPropertiesTo( + typeof(TSource), + typeof(TDestination), source.ExtraProperties, result, definitionChecks, From 53710b76b6e8c79e3cc5c1a625cf0392f901d764 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 13 Jul 2025 15:14:26 +0800 Subject: [PATCH 7/9] Fix generic type parameters in reverse mapper interfaces and classes for consistency --- .../Abp/Mapperly/AbpMapperlyMapperBase.cs | 2 +- .../Volo/Abp/Mapperly/IAbpMapperlyMapper.cs | 2 +- .../MapperlyAutoObjectMappingProvider.cs | 24 +++++++++---------- .../Abp/Mapperly/AbpReverseMapperly_Tests.cs | 10 +++++++- 4 files changed, 22 insertions(+), 16 deletions(-) diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs index 008c3635ea..6b7e9928ba 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs @@ -16,7 +16,7 @@ public abstract class AbpMapperlyMapperBase : IAbpMapperl } } -public abstract class AbpReverseMapperlyMapperBase : AbpMapperlyMapperBase, IAbpReverseMapperlyMapper +public abstract class AbpReverseMapperlyMapperBase : AbpMapperlyMapperBase, IAbpReverseMapperlyMapper { public abstract TSource ReverseMap(TDestination destination); diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs index 3d391cfe1f..eac0f6644c 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/IAbpMapperlyMapper.cs @@ -11,7 +11,7 @@ public interface IAbpMapperlyMapper void AfterMap(TSource source, TDestination destination); } -public interface IAbpReverseMapperlyMapper : IAbpMapperlyMapper +public interface IAbpReverseMapperlyMapper : IAbpMapperlyMapper { TSource ReverseMap(TDestination destination); diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index 19f677515b..34fa1b2c53 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -33,17 +33,17 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider { mapper.BeforeMap((TSource)source); var destination = mapper.Map((TSource)source); - TryMapExtraProperties(mapper, (TSource)source, destination, new ExtraPropertyDictionary()); + TryMapExtraProperties(mapper.GetType().GetSingleAttributeOrNull(), (TSource)source, destination, new ExtraPropertyDictionary()); mapper.AfterMap((TSource)source, destination); return destination; } - var reverseMapper = ServiceProvider.GetService>(); + var reverseMapper = ServiceProvider.GetService>(); if (reverseMapper != null) { reverseMapper.BeforeReverseMap((TSource)source); var destination = reverseMapper.ReverseMap((TSource)source); - TryMapExtraProperties(reverseMapper.As>(), destination, (TSource)source, GetExtraProperties(destination)); + TryMapExtraProperties(reverseMapper.GetType().GetSingleAttributeOrNull(), destination, (TSource)source, GetExtraProperties(destination)); reverseMapper.AfterReverseMap((TSource)source, destination); return destination; } @@ -60,18 +60,18 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider mapper.BeforeMap(source); var destinationExtraProperties = GetExtraProperties(destination); mapper.Map(source, destination); - TryMapExtraProperties(mapper, source, destination, destinationExtraProperties); + TryMapExtraProperties(mapper.GetType().GetSingleAttributeOrNull(), source, destination, destinationExtraProperties); mapper.AfterMap(source, destination); return destination; } - var reverseMapper = ServiceProvider.GetService>(); + var reverseMapper = ServiceProvider.GetService>(); if (reverseMapper != null) { reverseMapper.BeforeReverseMap(source); var destinationExtraProperties = GetExtraProperties(destination); reverseMapper.ReverseMap(source, destination); - TryMapExtraProperties(reverseMapper.As>(), source, destination, destinationExtraProperties); + TryMapExtraProperties(reverseMapper.GetType().GetSingleAttributeOrNull(), source, destination, destinationExtraProperties); reverseMapper.AfterReverseMap(source, destination); return destination; } @@ -95,10 +95,9 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider return extraProperties; } - protected virtual void TryMapExtraProperties(IAbpMapperlyMapper mapper, TSource source, TDestination destination, ExtraPropertyDictionary destinationExtraProperty) + protected virtual void TryMapExtraProperties(MapExtraPropertiesAttribute? mapExtraPropertiesAttribute, TSource source, TDestination destination, ExtraPropertyDictionary destinationExtraProperty) { - var mapToRegularPropertiesAttribute = mapper.GetType().GetSingleAttributeOrNull(); - if (mapToRegularPropertiesAttribute != null && + if (mapExtraPropertiesAttribute != null && typeof(IHasExtraProperties).IsAssignableFrom(typeof(TDestination)) && typeof(IHasExtraProperties).IsAssignableFrom(typeof(TSource))) { @@ -106,13 +105,12 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider source!.As(), destination!.As(), destinationExtraProperty, - mapToRegularPropertiesAttribute.DefinitionChecks, - mapToRegularPropertiesAttribute.IgnoredProperties, - mapToRegularPropertiesAttribute.MapToRegularProperties + mapExtraPropertiesAttribute.DefinitionChecks, + mapExtraPropertiesAttribute.IgnoredProperties, + mapExtraPropertiesAttribute.MapToRegularProperties ); } } - protected virtual void MapExtraProperties( IHasExtraProperties source, IHasExtraProperties destination, diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs index cb0b493f5a..0e5dbc8c11 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs @@ -22,7 +22,7 @@ public class MyReverseClassDto } [Mapper] -public partial class MyReverseClassMapper : AbpReverseMapperlyMapperBase +public partial class MyReverseClassMapper : AbpReverseMapperlyMapperBase { public override partial MyReverseClassDto Map(MyReverseClass source); @@ -68,5 +68,13 @@ public class AbpReverseMapperly_Tests : AbpIntegratedTest var myClass = _objectMapper.Map(myClassDto); myClass.Name.ShouldBe("BeforeReverseMap Test AfterReverseMap"); + + myClassDto.Id = "2"; + myClassDto.Name = "Test2"; + + _objectMapper.Map(myClassDto, myClass); + + myClass.Id.ShouldBe("2"); + myClass.Name.ShouldBe("BeforeReverseMap Test2 AfterReverseMap"); } } From 9e0d0413d0cf6c23d7fb4db17a9e8183bcb88d35 Mon Sep 17 00:00:00 2001 From: maliming Date: Sun, 13 Jul 2025 15:24:36 +0800 Subject: [PATCH 8/9] Fix parameter order in TryMapExtraProperties call --- .../Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs | 2 +- .../Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs | 10 ++++++++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs index 34fa1b2c53..7685aa03ac 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperlyAutoObjectMappingProvider.cs @@ -43,7 +43,7 @@ public class MapperlyAutoObjectMappingProvider : IAutoObjectMappingProvider { reverseMapper.BeforeReverseMap((TSource)source); var destination = reverseMapper.ReverseMap((TSource)source); - TryMapExtraProperties(reverseMapper.GetType().GetSingleAttributeOrNull(), destination, (TSource)source, GetExtraProperties(destination)); + TryMapExtraProperties(reverseMapper.GetType().GetSingleAttributeOrNull(), (TSource)source, destination, GetExtraProperties(destination)); reverseMapper.AfterReverseMap((TSource)source, destination); return destination; } diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs index 0e5dbc8c11..49c3c0ff4c 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs @@ -58,15 +58,21 @@ public class AbpReverseMapperly_Tests : AbpIntegratedTest var myClass = new MyReverseClass { Id = "1", Name = "Test" }; var myClassDto = _objectMapper.Map(myClass); myClassDto.Name.ShouldBe("Test"); + + myClass.Id = "2"; + myClass.Name = "Test2"; + + _objectMapper.Map(myClass, myClassDto); + + myClassDto.Id.ShouldBe("2"); + myClassDto.Name.ShouldBe("Test2"); } [Fact] public void ReverseMap_Test() { var myClassDto = new MyReverseClassDto { Id = "1", Name = "Test" }; - var myClass = _objectMapper.Map(myClassDto); - myClass.Name.ShouldBe("BeforeReverseMap Test AfterReverseMap"); myClassDto.Id = "2"; From 9a72a79c67de156f859eea9d621f0e485b3a7faf Mon Sep 17 00:00:00 2001 From: maliming Date: Mon, 21 Jul 2025 21:12:25 +0800 Subject: [PATCH 9/9] Rename mapper base classes --- .../Mapperly/{AbpMapperlyMapperBase.cs => MapperBase.cs} | 4 ++-- .../Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs | 2 +- .../Mapperly/AbpMapperly_Dependency_Injection_Tests.cs | 2 +- .../Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs | 2 +- .../Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs | 8 ++++---- 5 files changed, 9 insertions(+), 9 deletions(-) rename framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/{AbpMapperlyMapperBase.cs => MapperBase.cs} (68%) diff --git a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperBase.cs similarity index 68% rename from framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs rename to framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperBase.cs index 6b7e9928ba..39d9dce995 100644 --- a/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/AbpMapperlyMapperBase.cs +++ b/framework/src/Volo.Abp.Mapperly/Volo/Abp/Mapperly/MapperBase.cs @@ -2,7 +2,7 @@ using Volo.Abp.DependencyInjection; namespace Volo.Abp.Mapperly; -public abstract class AbpMapperlyMapperBase : IAbpMapperlyMapper, ITransientDependency +public abstract class MapperBase : IAbpMapperlyMapper, ITransientDependency { public abstract TDestination Map(TSource source); @@ -16,7 +16,7 @@ public abstract class AbpMapperlyMapperBase : IAbpMapperl } } -public abstract class AbpReverseMapperlyMapperBase : AbpMapperlyMapperBase, IAbpReverseMapperlyMapper +public abstract class TwoWayMapperBase : MapperBase, IAbpReverseMapperlyMapper { public abstract TSource ReverseMap(TDestination destination); diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs index 94e2b4ef7e..1c07bff75b 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperlyBeforeAndAfterMethod_Tests.cs @@ -22,7 +22,7 @@ public class MyClassDto } [Mapper] -public partial class MyClassMapper : AbpMapperlyMapperBase +public partial class MyClassMapper : MapperBase { public override partial MyClassDto Map(MyClass source); diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs index ecc0f7ecde..1aa6288f3e 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpMapperly_Dependency_Injection_Tests.cs @@ -28,7 +28,7 @@ public class BirthdayCalculatorService : ITransientDependency } [Mapper] -public partial class MyDIClassMapper : AbpMapperlyMapperBase +public partial class MyDIClassMapper : MapperBase { private readonly BirthdayCalculatorService _birthdayCalculatorService; diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs index 49c3c0ff4c..5eeebe6b82 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/AbpReverseMapperly_Tests.cs @@ -22,7 +22,7 @@ public class MyReverseClassDto } [Mapper] -public partial class MyReverseClassMapper : AbpReverseMapperlyMapperBase +public partial class MyReverseClassMapper : TwoWayMapperBase { public override partial MyReverseClassDto Map(MyReverseClass source); diff --git a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs index 22ba9be77e..49be64b61a 100644 --- a/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs +++ b/framework/test/Volo.Abp.Mapperly.Tests/Volo/Abp/Mapperly/SampleClasses/MapperlyMappers.cs @@ -4,7 +4,7 @@ using Volo.Abp.Mapperly.SampleClasses; using Volo.Abp.ObjectExtending.TestObjects; [Mapper] -public partial class MyEntityMapper : AbpMapperlyMapperBase +public partial class MyEntityMapper : MapperBase { public override partial MyEntityDto Map(MyEntity source); @@ -12,7 +12,7 @@ public partial class MyEntityMapper : AbpMapperlyMapperBase +public partial class MyEnumMapper : MapperBase { public override partial MyEnumDto Map(MyEnum source); @@ -24,7 +24,7 @@ public partial class MyEnumMapper : AbpMapperlyMapperBase [Mapper] [MapExtraProperties(IgnoredProperties = ["CityName"])] -public partial class ExtensibleTestPersonMapper : AbpMapperlyMapperBase +public partial class ExtensibleTestPersonMapper : MapperBase { public override partial ExtensibleTestPersonDto Map(ExtensibleTestPerson source); @@ -33,7 +33,7 @@ public partial class ExtensibleTestPersonMapper : AbpMapperlyMapperBase +public partial class ExtensibleTestPersonWithRegularPropertiesDtoMapper : MapperBase { [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Name))] [MapperIgnoreTarget(nameof(ExtensibleTestPersonWithRegularPropertiesDto.Age))]