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",