diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln
index 16327abcad..3db0998855 100644
--- a/framework/Volo.Abp.sln
+++ b/framework/Volo.Abp.sln
@@ -275,7 +275,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.Http.Client.IdentityModel.Web.Tests", "test\Volo.Abp.Http.Client.IdentityModel.Web.Tests\Volo.Abp.Http.Client.IdentityModel.Web.Tests.csproj", "{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.ObjectExtending", "src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj", "{D1815C77-16D6-4F99-8814-69065CD89FB3}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Volo.Abp.ObjectExtending.Tests", "test\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj", "{17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -823,6 +825,10 @@ Global
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Release|Any CPU.Build.0 = Release|Any CPU
+ {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -963,6 +969,7 @@ Global
{0C498CF2-D052-4BF7-AD35-509A90F69707} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA} = {447C8A77-E5F0-4538-8687-7383196D04EA}
{D1815C77-16D6-4F99-8814-69065CD89FB3} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
+ {17F8CA89-D9A2-4863-A5BD-B8E4D2901FD5} = {447C8A77-E5F0-4538-8687-7383196D04EA}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {BB97ECF4-9A84-433F-A80B-2A3285BDD1D5}
diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj
new file mode 100644
index 0000000000..0a18e0c44b
--- /dev/null
+++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj
@@ -0,0 +1,16 @@
+
+
+
+
+
+ netcoreapp3.1
+
+
+
+
+
+
+
+
+
+
diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestBase.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestBase.cs
new file mode 100644
index 0000000000..b01ff201c7
--- /dev/null
+++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestBase.cs
@@ -0,0 +1,9 @@
+using Volo.Abp.Testing;
+
+namespace Volo.Abp.ObjectExtending
+{
+ public abstract class AbpObjectExtendingTestBase : AbpIntegratedTest
+ {
+
+ }
+}
diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs
new file mode 100644
index 0000000000..30db2aba71
--- /dev/null
+++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs
@@ -0,0 +1,24 @@
+using Volo.Abp.Modularity;
+using Volo.Abp.ObjectExtending.TestObjects;
+using Volo.Abp.Threading;
+
+namespace Volo.Abp.ObjectExtending
+{
+ [DependsOn(typeof(AbpObjectExtendingModule))]
+ public class AbpObjectExtendingTestModule : AbpModule
+ {
+ private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
+
+ public override void PreConfigureServices(ServiceConfigurationContext context)
+ {
+ OneTimeRunner.Run(() =>
+ {
+ ObjectExtensionManager.Instance
+ .AddOrUpdateProperty("Name")
+ .AddOrUpdateProperty("Age")
+ .AddOrUpdateProperty("Name")
+ .AddOrUpdateProperty("ChildCount");
+ });
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions_Tests.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions_Tests.cs
new file mode 100644
index 0000000000..727ec25646
--- /dev/null
+++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions_Tests.cs
@@ -0,0 +1,73 @@
+using Shouldly;
+using Volo.Abp.Data;
+using Volo.Abp.ObjectExtending.TestObjects;
+using Xunit;
+
+namespace Volo.Abp.ObjectExtending
+{
+ public class HasExtraPropertiesObjectExtendingExtensions_Tests : AbpObjectExtendingTestBase
+ {
+ private readonly ExtensibleTestPerson _person;
+
+ public HasExtraPropertiesObjectExtendingExtensions_Tests()
+ {
+ _person = new ExtensibleTestPerson()
+ .SetProperty("Name", "John")
+ .SetProperty("Age", 42)
+ .SetProperty("ChildCount", 2)
+ .SetProperty("Sex", "male");
+ }
+
+ [Fact]
+ public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default()
+ {
+ var personDto = new ExtensibleTestPersonDto();
+
+ _person.MapExtraPropertiesTo(personDto);
+
+ personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes
+ personDto.HasProperty("Age").ShouldBeFalse(); //Not defined on the destination
+ personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source
+ personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
+ }
+
+ [Fact]
+ public void MapExtraPropertiesTo_Should_Only_Map_Source_Defined_Properties_If_Requested()
+ {
+ var personDto = new ExtensibleTestPersonDto();
+
+ _person.MapExtraPropertiesTo(personDto, MappingPropertyDefinitionCheck.Source);
+
+ personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes
+ personDto.GetProperty("Age").ShouldBe(42); //Defined in source
+ personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source
+ personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
+ }
+
+ [Fact]
+ public void MapExtraPropertiesTo_Should_Only_Map_Destination_Defined_Properties_If_Requested()
+ {
+ var personDto = new ExtensibleTestPersonDto();
+
+ _person.MapExtraPropertiesTo(personDto, MappingPropertyDefinitionCheck.Destination);
+
+ personDto.GetProperty("Name").ShouldBe("John"); //Defined in both classes
+ personDto.GetProperty("ChildCount").ShouldBe(2); //Defined in destination
+ personDto.HasProperty("Age").ShouldBeFalse(); //Not defined in destination
+ personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
+ }
+
+ [Fact]
+ public void MapExtraPropertiesTo_Should_Copy_all_With_No_Property_Definition_Check()
+ {
+ var personDto = new ExtensibleTestPersonDto();
+
+ _person.MapExtraPropertiesTo(personDto, MappingPropertyDefinitionCheck.None);
+
+ personDto.GetProperty("Name").ShouldBe("John");
+ personDto.GetProperty("Age").ShouldBe(42);
+ personDto.GetProperty("ChildCount").ShouldBe(2);
+ personDto.GetProperty("Sex").ShouldBe("male");
+ }
+ }
+}
diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPerson.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPerson.cs
new file mode 100644
index 0000000000..bd24209212
--- /dev/null
+++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPerson.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.ObjectExtending.TestObjects
+{
+ public class ExtensibleTestPerson : ExtensibleObject
+ {
+
+ }
+}
diff --git a/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPersonDto.cs b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPersonDto.cs
new file mode 100644
index 0000000000..62f0f94866
--- /dev/null
+++ b/framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPersonDto.cs
@@ -0,0 +1,7 @@
+namespace Volo.Abp.ObjectExtending.TestObjects
+{
+ public class ExtensibleTestPersonDto : ExtensibleObject
+ {
+
+ }
+}