Browse Source

Merge pull request #3401 from abpframework/object-extending

Implemented: Object Extending System
pull/3410/head
Halil İbrahim Kalkan 7 years ago
committed by GitHub
parent
commit
5c816d5498
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 11
      docs/en/Customizing-Application-Modules-Extending-Entities.md
  2. 2
      docs/en/Entities.md
  3. 15
      docs/en/Entity-Framework-Core-Migrations.md
  4. 41
      docs/en/Entity-Framework-Core.md
  5. 3
      docs/en/Object-Extensions.md
  6. 16
      framework/Volo.Abp.sln
  7. 36
      framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs
  8. 1
      framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj
  9. 5
      framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs
  10. 15
      framework/src/Volo.Abp.Core/Volo/Abp/Check.cs
  11. 1
      framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj
  12. 4
      framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs
  13. 2
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs
  14. 4
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs
  15. 35
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs
  16. 40
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs
  17. 35
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs
  18. 32
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs
  19. 28
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs
  20. 41
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs
  21. 46
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs
  22. 6
      framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs
  23. 35
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
  24. 14
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs
  25. 133
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs
  26. 17
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs
  27. 33
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
  28. 15
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
  29. 41
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs
  30. 80
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs
  31. 50
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs
  32. 27
      framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs
  33. 3
      framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml
  34. 30
      framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd
  35. 3
      framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs
  36. 21
      framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj
  37. 0
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
  38. 0
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs
  39. 9
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/AbpObjectExtendingModule.cs
  40. 17
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs
  41. 175
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs
  42. 28
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs
  43. 78
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs
  44. 66
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs
  45. 44
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs
  46. 33
      framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs
  47. 41
      framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperExtensibleDtoExtensions_Tests.cs
  48. 2
      framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj
  49. 9
      framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs
  50. 4
      framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs
  51. 11
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs
  52. 3
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs
  53. 3
      framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs
  54. 16
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj
  55. 9
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestBase.cs
  56. 27
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs
  57. 73
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions_Tests.cs
  58. 63
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs
  59. 7
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPerson.cs
  60. 7
      framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/TestObjects/ExtensibleTestPersonDto.cs
  61. 1
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs
  62. 3
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs
  63. 4
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleDto.cs
  64. 3
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs
  65. 2
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs
  66. 6
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ProfileDto.cs
  67. 3
      modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateProfileDto.cs
  68. 11
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs
  69. 22
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs
  70. 16
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs
  71. 3
      modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs
  72. 1
      modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs
  73. 8
      modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs
  74. 1
      nupkg/common.ps1
  75. 1
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj
  76. 9
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs
  77. 29
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameDtoExtensions.cs
  78. 15
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameEntityExtensions.cs
  79. 29
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MongoDb/MyProjectNameEntityExtensions.cs
  80. 5
      templates/app/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MongoDb/MyProjectNameMongoDbModule.cs

11
docs/en/Customizing-Application-Modules-Extending-Entities.md

@ -37,13 +37,14 @@ As mentioned above, all extra properties of an entity are stored as a single JSO
To overcome the difficulties described above, ABP Framework entity extension system for the Entity Framework Core that allows you to use the same extra properties API defined above, but store a desired property as a separate field in the database table.
Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `EntityExtensionManager` static class:
Assume that you want to add a `SocialSecurityNumber` to the `IdentityUser` entity of the [Identity Module](Modules/Identity.md). You can use the `ObjectExtensionManager`:
````csharp
EntityExtensionManager.AddProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
);
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityUser, string>(
"SocialSecurityNumber",
b => { b.HasMaxLength(32); }
);
````
* You provide the `IdentityUser` as the entity name, `string` as the type of the new property, `SocialSecurityNumber` as the property name (also, the field name in the database table).

2
docs/en/Entities.md

@ -375,7 +375,7 @@ The way to store this dictionary in the database depends on the database provide
* For [Entity Framework Core](Entity-Framework-Core.md), here are two type of configurations;
* By default, it is stored in a single `ExtraProperties` field as a `JSON` string (that means all extra properties stored in a single database table field). Serializing to `JSON` and deserializing from the `JSON` are automatically done by the ABP Framework using the [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) system of the EF Core.
* If you want, you can use the `EntityExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `EntityExtensionManager` will continue to use a single `JSON` field as described above. This feature is especially useful when you are using a pre-built [application module](Modules/Index.md) and want to [extend its entities](Customizing-Application-Modules-Extending-Entities.md). See the [EF Core integration document](Entity-Framework-Core.md) to learn how to use the `EntityExtensionManager`.
* If you want, you can use the `ObjectExtensionManager` to define a separate table field for a desired extra property. Properties those are not configured through the `ObjectExtensionManager` will continue to use a single `JSON` field as described above. This feature is especially useful when you are using a pre-built [application module](Modules/Index.md) and want to [extend its entities](Customizing-Application-Modules-Extending-Entities.md). See the [EF Core integration document](Entity-Framework-Core.md) to learn how to use the `ObjectExtensionManager`.
* For [MongoDB](MongoDB.md), it is stored as a **regular field**, since MongoDB naturally supports this kind of [extra elements](https://mongodb.github.io/mongo-csharp-driver/1.11/serialization/#supporting-extra-elements) system.
### Discussion for the Extra Properties

15
docs/en/Entity-Framework-Core-Migrations.md

@ -409,18 +409,19 @@ public static class MyProjectNameEntityExtensions
{
OneTimeRunner.Run(() =>
{
EntityExtensionManager.AddProperty<IdentityRole, string>(
"Title",
b => { b.HasMaxLength(128); }
);
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
);
});
}
}
````
> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)`.
> Instead of hard-coded "Title" string, we suggest to use `nameof(AppRole.Title)` or use a constant string.
`EntityExtensionManager` is used to add properties to existing entities. Since `EntityExtensionManager` is static, we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework.
`ObjectExtensionManager` is used to add properties to existing entities. Since `ObjectExtensionManager.Instance` is a static instance (singleton), we should call it once. `OneTimeRunner` is a simple utility class defined by the ABP Framework.
See the [EF Core integration documentation](Entity-Framework-Core.md) for more about the entity extension system.
@ -543,7 +544,7 @@ In this way, you can easily attach any type of value to an entity of a depended
Entity extension system solves the main problem of the extra properties: It can store an extra property in a **standard table field** in the database.
All you need to do is to use the `EntityExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
All you need to do is to use the `ObjectExtensionManager` to define the extra property as explained above, in the `AppRole` example. Then you can continue to use the same `GetProperty` and `SetProperty` methods defined above to get/set the related property on the entity, but this time stored as a separate field in the database.
###### Creating a New Table

41
docs/en/Entity-Framework-Core.md

@ -298,56 +298,57 @@ public class BookService
> Important: You must reference to the `Volo.Abp.EntityFrameworkCore` package from the project you want to access to the DbContext. This breaks encapsulation, but this is what you want in that case.
## Extra Properties & Entity Extension Manager
## Extra Properties & Object Extension Manager
Extra Properties system allows you to set/get dynamic properties to entities those implement the `IHasExtraProperties` interface. It is especially useful when you want to add custom properties to the entities defined in an [application module](Modules/Index.md), when you use the module as package reference.
By default, all the extra properties of an entity are stored as a single `JSON` object in the database. Entity extension system allows you to to store desired extra properties in separate fields in the related database table.
By default, all the extra properties of an entity are stored as a single `JSON` object in the database.
For more information about the extra properties & the entity extension system, see the following documents:
Entity extension system allows you to to store desired extra properties in separate fields in the related database table. For more information about the extra properties & the entity extension system, see the following documents:
* [Customizing the Application Modules: Extending Entities](Customizing-Application-Modules-Extending-Entities.md)
* [Entities](Entities.md)
This section only explains the `EntityExtensionManager` and its usage.
This section only explains the EF Core related usage of the `ObjectExtensionManager`.
### AddProperty Method
### ObjectExtensionManager.Instance
`AddProperty` method of the `EntityExtensionManager` allows you to define additional properties for an entity type.
`ObjectExtensionManager` implements the singleton pattern, so you need to use the static `ObjectExtensionManager.Instance` to perform all the operations.
### MapEfCoreProperty
`MapEfCoreProperty` is a shortcut extension method to define an extension property for an entity and map to the database.
**Example**: Add `Title` property (database field) to the `IdentityRole` entity:
````csharp
EntityExtensionManager.AddProperty<IdentityRole, string>(
"Title",
b => { b.HasMaxLength(128); }
);
ObjectExtensionManager.Instance
.MapEfCoreProperty<IdentityRole, string>(
"Title",
builder => { builder.HasMaxLength(64); }
);
````
If the related module has implemented this feature (by using the `ConfigureExtensions` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field.
If the related module has implemented this feature (by using the `ConfigureEfCoreEntity` explained below), then the new property is added to the model. Then you need to run the standard `Add-Migration` and `Update-Database` commands to update your database to add the new field.
>`AddProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEntityExtensions` class that is safe to use this method inside.
>`MapEfCoreProperty` method must be called before using the related `DbContext`. It is a static method. The best way is to use it in your application as earlier as possible. The application startup template has a `YourProjectNameEntityExtensions` class that is safe to use this method inside.
### ConfigureExtensions
### ConfigureEfCoreEntity
If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureExtensions` extension method in your entity mapping:
If you are building a reusable module and want to allow application developers to add properties to your entities, you can use the `ConfigureEfCoreEntity` extension method in your entity mapping. However, there is a shortcut extension method `ConfigureObjectExtensions` that can be used while configuring the entity mapping:
````csharp
builder.Entity<YourEntity>(b =>
{
b.ConfigureExtensions();
b.ConfigureObjectExtensions();
//...
});
````
If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` in this example), ABP Framework internally calls the `ConfigureExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention.
> If you call `ConfigureByConvention()` extension method (like `b.ConfigureByConvention()` for this example), ABP Framework internally calls the `ConfigureObjectExtensions` method. It is a **best practice** to use the `ConfigureByConvention()` method since it also configures database mapping for base properties by convention.
See the "*ConfigureByConvention Method*" section above for more information.
### GetPropertyNames
`EntityExtensionManager.GetPropertyNames` static method can be used the names of the extension properties defined for this entity. It is normally not needed by an application code, but used by the ABP Framework internally.
## Advanced Topics
### Set Default Repository Classes

3
docs/en/Object-Extensions.md

@ -0,0 +1,3 @@
# Object Extensions
TODO

16
framework/Volo.Abp.sln

@ -273,7 +273,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo", "test\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo\Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic.Demo.csproj", "{0C498CF2-D052-4BF7-AD35-509A90F69707}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "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}"
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("{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
@ -817,6 +821,14 @@ Global
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E1963439-2BE5-4DB5-8438-2A9A792A1ADA}.Release|Any CPU.Build.0 = Release|Any CPU
{D1815C77-16D6-4F99-8814-69065CD89FB3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{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
@ -956,6 +968,8 @@ Global
{29E42ADB-85F8-44AE-A9B0-078F84C1B866} = {5DF0E140-0513-4D0D-BE2E-3D4D85CD70E6}
{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}

36
framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs

@ -0,0 +1,36 @@
using System.Collections.Generic;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending;
namespace AutoMapper
{
public static class AbpAutoMapperExtensibleDtoExtensions
{
public static IMappingExpression<TSource, TDestination> MapExtraProperties<TSource, TDestination>(
this IMappingExpression<TSource, TDestination> mappingExpression,
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both)
where TDestination : IHasExtraProperties
where TSource : IHasExtraProperties
{
return mappingExpression
.ForMember(
x => x.ExtraProperties,
y => y.MapFrom(
(source, destination, extraProps) =>
{
var result = extraProps.IsNullOrEmpty()
? new Dictionary<string, object>()
: new Dictionary<string, object>(extraProps);
HasExtraPropertiesObjectExtendingExtensions
.MapExtraPropertiesTo<TSource, TDestination>(
source.ExtraProperties,
result
);
return result;
})
);
}
}
}

1
framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj

@ -15,6 +15,7 @@
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectMapping\Volo.Abp.ObjectMapping.csproj" />
</ItemGroup>

5
framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs

@ -3,11 +3,14 @@ using AutoMapper;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.ObjectMapping;
namespace Volo.Abp.AutoMapper
{
[DependsOn(typeof(AbpObjectMappingModule))]
[DependsOn(
typeof(AbpObjectMappingModule),
typeof(AbpObjectExtendingModule))]
public class AbpAutoMapperModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

15
framework/src/Volo.Abp.Core/Volo/Abp/Check.cs

@ -121,6 +121,21 @@ namespace Volo.Abp
return value;
}
[ContractAnnotation("type:null => halt")]
public static Type AssignableTo<TBaseType>(
Type type,
[InvokerParameterName] [NotNull] string parameterName)
{
NotNull(type, parameterName);
if (!type.IsAssignableTo<TBaseType>())
{
throw new ArgumentException($"{parameterName} (type of {type.AssemblyQualifiedName}) should be assignable to the {typeof(TBaseType).GetFullNameWithAssemblyName()}!");
}
return type;
}
public static string Length(
[CanBeNull] string value,
[InvokerParameterName] [NotNull] string parameterName,

1
framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj

@ -16,6 +16,7 @@
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" />
<ProjectReference Include="..\Volo.Abp.Uow\Volo.Abp.Uow.csproj" />
</ItemGroup>

4
framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs

@ -2,13 +2,15 @@
using System.Collections.Generic;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Uow;
namespace Volo.Abp.Data
{
[DependsOn(
typeof(AbpObjectExtendingModule),
typeof(AbpUnitOfWorkModule)
)]
)]
public class AbpDataModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)

2
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs

@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
@ -19,6 +20,7 @@ namespace Volo.Abp.Application.Dtos
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>

4
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs

@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It also has the <see cref="Creator"/> object as a DTO represents the user.
/// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
@ -14,7 +15,8 @@ namespace Volo.Abp.Application.Dtos
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObjectObject{TCreator}"/> interface.
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It also has the <see cref="Creator"/> object as a DTO represents the user.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>

35
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs

@ -0,0 +1,35 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
[Serializable]
public abstract class ExtensibleAuditedEntityDto<TPrimaryKey> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, IAuditedObject
{
/// <inheritdoc />
public DateTime? LastModificationTime { get; set; }
/// <inheritdoc />
public Guid? LastModifierId { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
[Serializable]
public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEntityDto, IAuditedObject
{
/// <inheritdoc />
public DateTime? LastModificationTime { get; set; }
/// <inheritdoc />
public Guid? LastModifierId { get; set; }
}
}

40
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs

@ -0,0 +1,40 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleAuditedEntityDto<TPrimaryKey>, IAuditedObject<TUserDto>
{
/// <inheritdoc />
public TUserDto Creator { get; set; }
/// <inheritdoc />
public TUserDto LastModifier { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IAuditedObject"/> interface.
/// It has the <see cref="Creator"/> and <see cref="LastModifier"/> objects as a DTOs represent the related user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleAuditedEntityWithUserDto<TUserDto> : ExtensibleAuditedEntityDto,
IAuditedObject<TUserDto>
{
/// <inheritdoc />
public TUserDto Creator { get; set; }
/// <inheritdoc />
public TUserDto LastModifier { get; set; }
}
}

35
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs

@ -0,0 +1,35 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityDto<TPrimaryKey> : ExtensibleEntityDto<TPrimaryKey>, ICreationAuditedObject
{
/// <inheritdoc />
public DateTime CreationTime { get; set; }
/// <inheritdoc />
public Guid? CreatorId { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, ICreationAuditedObject
{
/// <inheritdoc />
public DateTime CreationTime { get; set; }
/// <inheritdoc />
public Guid? CreatorId { get; set; }
}
}

32
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs

@ -0,0 +1,32 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It has the <see cref="Creator"/> object as a DTO represents the user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleCreationAuditedEntityDto<TPrimaryKey>, ICreationAuditedObject<TUserDto>
{
public TUserDto Creator { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="ICreationAuditedObject{TCreator}"/> interface.
/// It has the <see cref="Creator"/> object as a DTO represents the user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TUserDto">Type of the User DTO</typeparam>
[Serializable]
public abstract class ExtensibleCreationAuditedEntityWithUserDto<TUserDto> : ExtensibleCreationAuditedEntityDto,
ICreationAuditedObject<TUserDto>
{
public TUserDto Creator { get; set; }
}
}

28
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs

@ -0,0 +1,28 @@
using System;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Application.Dtos
{
[Serializable]
public abstract class ExtensibleEntityDto<TKey> : ExtensibleObject, IEntityDto<TKey>
{
/// <summary>
/// Id of the entity.
/// </summary>
public TKey Id { get; set; }
public override string ToString()
{
return $"[DTO: {GetType().Name}] Id = {Id}";
}
}
[Serializable]
public abstract class ExtensibleEntityDto : ExtensibleObject, IEntityDto
{
public override string ToString()
{
return $"[DTO: {GetType().Name}]";
}
}
}

41
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs

@ -0,0 +1,41 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
[Serializable]
public abstract class ExtensibleFullAuditedEntityDto<TPrimaryKey> : ExtensibleAuditedEntityDto<TPrimaryKey>, IFullAuditedObject
{
/// <inheritdoc />
public bool IsDeleted { get; set; }
/// <inheritdoc />
public Guid? DeleterId { get; set; }
/// <inheritdoc />
public DateTime? DeletionTime { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject"/> interface.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
[Serializable]
public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDto, IFullAuditedObject
{
/// <inheritdoc />
public bool IsDeleted { get; set; }
/// <inheritdoc />
public Guid? DeleterId { get; set; }
/// <inheritdoc />
public DateTime? DeletionTime { get; set; }
}
}

46
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs

@ -0,0 +1,46 @@
using System;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject{TUser}"/> interface.
/// It has the <see cref="Creator"/>, <see cref="LastModifier"/> and <see cref="Deleter"/> objects as a DTOs represent the related user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User</typeparam>
[Serializable]
public abstract class ExtensibleFullAuditedEntityWithUserDto<TPrimaryKey, TUserDto> : ExtensibleFullAuditedEntityDto<TPrimaryKey>, IFullAuditedObject<TUserDto>
{
/// <inheritdoc />
public TUserDto Creator { get; set; }
/// <inheritdoc />
public TUserDto LastModifier { get; set; }
/// <inheritdoc />
public TUserDto Deleter { get; set; }
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject{TUser}"/> interface.
/// It has the <see cref="Creator"/>, <see cref="LastModifier"/> and <see cref="Deleter"/> objects as a DTOs represent the related user.
/// It also implements the <see cref="IHasExtraProperties"/> interface.
/// </summary>
/// <typeparam name="TUserDto">Type of the User</typeparam>
[Serializable]
public abstract class ExtensibleFullAuditedEntityWithUserDto<TUserDto> : ExtensibleFullAuditedEntityDto,
IFullAuditedObject<TUserDto>
{
/// <inheritdoc />
public TUserDto Creator { get; set; }
/// <inheritdoc />
public TUserDto LastModifier { get; set; }
/// <inheritdoc />
public TUserDto Deleter { get; set; }
}
}

6
framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs

@ -4,7 +4,8 @@ using Volo.Abp.Auditing;
namespace Volo.Abp.Application.Dtos
{
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObjectObject{TUser}"/> interface.
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject{TUser}"/> interface.
/// It has the <see cref="Creator"/>, <see cref="LastModifier"/> and <see cref="Deleter"/> objects as a DTOs represent the related user.
/// </summary>
/// <typeparam name="TUserDto">Type of the User</typeparam>
[Serializable]
@ -21,7 +22,8 @@ namespace Volo.Abp.Application.Dtos
}
/// <summary>
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObjectObject{TUser}"/> interface.
/// This class can be inherited by DTO classes to implement <see cref="IFullAuditedObject{TUser}"/> interface.
/// It has the <see cref="Creator"/>, <see cref="LastModifier"/> and <see cref="Deleter"/> objects as a DTOs represent the related user.
/// </summary>
/// <typeparam name="TPrimaryKey">Type of primary key</typeparam>
/// <typeparam name="TUserDto">Type of the User</typeparam>

35
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs

@ -18,11 +18,11 @@ using Volo.Abp.Domain.Entities;
using Volo.Abp.Domain.Entities.Events;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.EntityFrameworkCore.EntityHistory;
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.EntityFrameworkCore.ValueConverters;
using Volo.Abp.Guids;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Reflection;
using Volo.Abp.Timing;
using Volo.Abp.Uow;
@ -183,10 +183,18 @@ namespace Volo.Abp.EntityFrameworkCore
return;
}
var propertyNames = EntityExtensionManager.GetPropertyNames(entityType);
var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
if (objectExtension == null)
{
return;
}
foreach (var propertyName in propertyNames)
foreach (var property in objectExtension.GetProperties())
{
if (!property.IsMappedToFieldForEfCore())
{
continue;
}
/* Checking "currentValue != null" has a good advantage:
* Assume that you we already using a named extra property,
* then decided to create a field (entity extension) for it.
@ -194,10 +202,10 @@ namespace Volo.Abp.EntityFrameworkCore
* updates the field on the next save!
*/
var currentValue = e.Entry.CurrentValues[propertyName];
var currentValue = e.Entry.CurrentValues[property.Name];
if (currentValue != null)
{
entity.SetProperty(propertyName, currentValue);
entity.SetProperty(property.Name, currentValue);
}
}
}
@ -251,12 +259,21 @@ namespace Volo.Abp.EntityFrameworkCore
{
return;
}
var propertyNames = EntityExtensionManager.GetPropertyNames(entityType);
foreach (var propertyName in propertyNames)
var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
if (objectExtension == null)
{
return;
}
foreach (var property in objectExtension.GetProperties())
{
entry.Property(propertyName).CurrentValue = entity.GetProperty(propertyName);
if (!entity.HasProperty(property.Name))
{
continue;
}
entry.Property(property.Name).CurrentValue = entity.GetProperty(property.Name);
}
}

14
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs

@ -1,14 +0,0 @@
using System.Collections.Generic;
namespace Volo.Abp.EntityFrameworkCore.Extensions
{
public class EntityExtensionInfo
{
public Dictionary<string, PropertyExtensionInfo> Properties { get; set; }
public EntityExtensionInfo()
{
Properties = new Dictionary<string, PropertyExtensionInfo>();
}
}
}

133
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs

@ -1,133 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Volo.Abp.Data;
namespace Volo.Abp.EntityFrameworkCore.Extensions
{
public static class EntityExtensionManager
{
private static readonly Dictionary<Type, EntityExtensionInfo> ExtensionInfos;
static EntityExtensionManager()
{
ExtensionInfos = new Dictionary<Type, EntityExtensionInfo>();
}
/// <summary>
/// Adds an extension property for an entity.
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
/// by the given one!
/// </summary>
/// <typeparam name="TEntity">Type of the entity</typeparam>
/// <typeparam name="TProperty">Type of the new property</typeparam>
/// <param name="propertyName">Name of the property</param>
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
public static void AddProperty<TEntity, TProperty>(
[NotNull]string propertyName,
[NotNull]Action<PropertyBuilder> propertyBuildAction)
{
AddProperty(
typeof(TEntity),
typeof(TProperty),
propertyName,
propertyBuildAction
);
}
/// <summary>
/// Adds an extension property for an entity.
/// If it is already added, replaces the <paramref name="propertyBuildAction"/>
/// by the given one!
/// </summary>
/// <param name="entityType">Type of the entity</param>
/// <param name="propertyType">Type of the new property</param>
/// <param name="propertyName">Name of the property</param>
/// <param name="propertyBuildAction">An action to configure the database mapping for the new property</param>
public static void AddProperty(
Type entityType,
Type propertyType,
[NotNull]string propertyName,
[NotNull]Action<PropertyBuilder> propertyBuildAction)
{
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(propertyType, nameof(propertyType));
Check.NotNullOrWhiteSpace(propertyName, nameof(propertyName));
Check.NotNull(propertyBuildAction, nameof(propertyBuildAction));
var extensionInfo = ExtensionInfos
.GetOrAdd(entityType, () => new EntityExtensionInfo());
var propertyExtensionInfo = extensionInfo.Properties
.GetOrAdd(propertyName, () => new PropertyExtensionInfo(propertyType));
propertyExtensionInfo.Action = propertyBuildAction;
}
/// <summary>
/// Configures the entity mapping for the defined extensions.
/// </summary>
/// <typeparam name="TEntity">The entity tye</typeparam>
/// <param name="entityTypeBuilder">Entity type builder</param>
public static void ConfigureExtensions<TEntity>(
[NotNull] this EntityTypeBuilder<TEntity> entityTypeBuilder)
where TEntity : class, IHasExtraProperties
{
ConfigureExtensions(typeof(TEntity), entityTypeBuilder);
}
/// <summary>
/// Configures the entity mapping for the defined extensions.
/// </summary>
/// <param name="entityType">Type of the entity</param>
/// <param name="entityTypeBuilder">Entity type builder</param>
public static void ConfigureExtensions(
[NotNull] Type entityType,
[NotNull] EntityTypeBuilder entityTypeBuilder)
{
Check.NotNull(entityType, nameof(entityType));
Check.NotNull(entityTypeBuilder, nameof(entityTypeBuilder));
var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType);
if (entityExtensionInfo == null)
{
return;
}
foreach (var propertyExtensionInfo in entityExtensionInfo.Properties)
{
var propertyName = propertyExtensionInfo.Key;
var propertyType = propertyExtensionInfo.Value.PropertyType;
/* Prevent multiple calls to the entityTypeBuilder.Property(...) method */
if (entityTypeBuilder.Metadata.FindProperty(propertyName) != null)
{
continue;
}
var property = entityTypeBuilder.Property(
propertyType,
propertyName
);
propertyExtensionInfo.Value.Action(property);
}
}
public static string[] GetPropertyNames(Type entityType)
{
var entityExtensionInfo = ExtensionInfos.GetOrDefault(entityType);
if (entityExtensionInfo == null)
{
return Array.Empty<string>();
}
return entityExtensionInfo
.Properties
.Select(p => p.Key)
.ToArray();
}
}
}

17
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs

@ -1,17 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Volo.Abp.EntityFrameworkCore.Extensions
{
public class PropertyExtensionInfo
{
public Action<PropertyBuilder> Action { get; set; }
public Type PropertyType { get; }
public PropertyExtensionInfo(Type propertyType)
{
PropertyType = propertyType;
}
}
}

33
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs

@ -5,10 +5,10 @@ using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Volo.Abp.Auditing;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.EntityFrameworkCore.ValueComparers;
using Volo.Abp.EntityFrameworkCore.ValueConverters;
using Volo.Abp.MultiTenancy;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.EntityFrameworkCore.Modeling
{
@ -18,6 +18,7 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling
{
b.TryConfigureConcurrencyStamp();
b.TryConfigureExtraProperties();
b.TryConfigureObjectExtensions();
b.TryConfigureMayHaveCreator();
b.TryConfigureMustHaveCreator();
b.TryConfigureSoftDelete();
@ -54,15 +55,30 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling
public static void TryConfigureExtraProperties(this EntityTypeBuilder b)
{
//TODO: Max length?
if (b.Metadata.ClrType.IsAssignableTo<IHasExtraProperties>())
if (!b.Metadata.ClrType.IsAssignableTo<IHasExtraProperties>())
{
b.Property<Dictionary<string, object>>(nameof(IHasExtraProperties.ExtraProperties))
.HasColumnName(nameof(IHasExtraProperties.ExtraProperties))
.HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType))
.Metadata.SetValueComparer(new AbpDictionaryValueComparer<string, object>());
return;
}
EntityExtensionManager.ConfigureExtensions(b.Metadata.ClrType, b);
b.Property<Dictionary<string, object>>(nameof(IHasExtraProperties.ExtraProperties))
.HasColumnName(nameof(IHasExtraProperties.ExtraProperties))
.HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType))
.Metadata.SetValueComparer(new AbpDictionaryValueComparer<string, object>());
b.TryConfigureObjectExtensions();
}
public static void ConfigureObjectExtensions<T>(this EntityTypeBuilder<T> b)
where T : class, IHasExtraProperties
{
b.As<EntityTypeBuilder>().TryConfigureObjectExtensions();
}
public static void TryConfigureObjectExtensions(this EntityTypeBuilder b)
{
if (b.Metadata.ClrType.IsAssignableTo<IHasExtraProperties>())
{
ObjectExtensionManager.Instance.ConfigureEfCoreEntity(b);
}
}
@ -286,7 +302,6 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling
b.As<EntityTypeBuilder>().TryConfigureConcurrencyStamp();
}
//TODO: Add other interfaces (IAuditedObject<TUser>...)
}
}

15
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs

@ -2,7 +2,7 @@
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Newtonsoft.Json;
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.EntityFrameworkCore.ValueConverters
{
@ -22,11 +22,16 @@ namespace Volo.Abp.EntityFrameworkCore.ValueConverters
if (entityType != null)
{
var propertyNames = EntityExtensionManager.GetPropertyNames(entityType);
foreach (var propertyName in propertyNames)
var objectExtension = ObjectExtensionManager.Instance.GetOrNull(entityType);
if (objectExtension != null)
{
copyDictionary.Remove(propertyName);
foreach (var property in objectExtension.GetProperties())
{
if (property.IsMappedToFieldForEfCore())
{
copyDictionary.Remove(property.Name);
}
}
}
}

41
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs

@ -0,0 +1,41 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Volo.Abp.ObjectExtending
{
public static class EfCoreObjectExtensionInfoExtensions
{
public static ObjectExtensionInfo MapEfCoreProperty<TProperty>(
[NotNull] this ObjectExtensionInfo objectExtensionInfo,
[NotNull] string propertyName,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction)
{
return objectExtensionInfo.MapEfCoreProperty(
typeof(TProperty),
propertyName,
propertyBuildAction
);
}
public static ObjectExtensionInfo MapEfCoreProperty(
[NotNull] this ObjectExtensionInfo objectExtensionInfo,
[NotNull] Type propertyType,
[NotNull] string propertyName,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction)
{
Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo));
return objectExtensionInfo.AddOrUpdateProperty(
propertyType,
propertyName,
options =>
{
options.MapEfCore(
propertyBuildAction
);
}
);
}
}
}

80
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs

@ -0,0 +1,80 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
namespace Volo.Abp.ObjectExtending
{
public static class EfCoreObjectExtensionManagerExtensions
{
public static ObjectExtensionManager MapEfCoreProperty<TEntity, TProperty>(
[NotNull] this ObjectExtensionManager objectExtensionManager,
[NotNull] string propertyName,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null)
where TEntity : IHasExtraProperties, IEntity
{
return objectExtensionManager.MapEfCoreProperty(
typeof(TEntity),
typeof(TProperty),
propertyName,
propertyBuildAction
);
}
public static ObjectExtensionManager MapEfCoreProperty(
[NotNull] this ObjectExtensionManager objectExtensionManager,
[NotNull] Type entityType,
[NotNull] Type propertyType,
[NotNull] string propertyName,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null)
{
Check.NotNull(objectExtensionManager, nameof(objectExtensionManager));
return objectExtensionManager.AddOrUpdateProperty(
entityType,
propertyType,
propertyName,
options =>
{
options.MapEfCore(
propertyBuildAction
);
}
);
}
public static void ConfigureEfCoreEntity(
[NotNull] this ObjectExtensionManager objectExtensionManager,
[NotNull] EntityTypeBuilder typeBuilder)
{
Check.NotNull(objectExtensionManager, nameof(objectExtensionManager));
Check.NotNull(typeBuilder, nameof(typeBuilder));
var objectExtension = objectExtensionManager.GetOrNull(typeBuilder.Metadata.ClrType);
if (objectExtension == null)
{
return;
}
foreach (var property in objectExtension.GetProperties())
{
var efCoreMapping = property.GetEfCoreMappingOrNull();
if (efCoreMapping == null)
{
continue;
}
/* Prevent multiple calls to the entityTypeBuilder.Property(...) method */
if (typeBuilder.Metadata.FindProperty(property.Name) != null)
{
continue;
}
var propertyBuilder = typeBuilder.Property(property.Type, property.Name);
efCoreMapping.PropertyBuildAction?.Invoke(propertyBuilder);
}
}
}
}

50
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs

@ -0,0 +1,50 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Volo.Abp.ObjectExtending
{
public static class EfCoreObjectExtensionPropertyInfoExtensions
{
public const string EfCorePropertyConfigurationName = "EfCoreMapping";
[NotNull]
public static ObjectExtensionPropertyInfo MapEfCore(
[NotNull] this ObjectExtensionPropertyInfo propertyExtension,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null)
{
Check.NotNull(propertyExtension, nameof(propertyExtension));
propertyExtension.Configuration[EfCorePropertyConfigurationName] =
new ObjectExtensionPropertyInfoEfCoreMappingOptions(
propertyExtension,
propertyBuildAction
);
return propertyExtension;
}
[CanBeNull]
public static ObjectExtensionPropertyInfoEfCoreMappingOptions GetEfCoreMappingOrNull(
[NotNull] this ObjectExtensionPropertyInfo propertyExtension)
{
Check.NotNull(propertyExtension, nameof(propertyExtension));
return propertyExtension
.Configuration
.GetOrDefault(EfCorePropertyConfigurationName)
as ObjectExtensionPropertyInfoEfCoreMappingOptions;
}
public static bool IsMappedToFieldForEfCore(
[NotNull] this ObjectExtensionPropertyInfo propertyExtension)
{
Check.NotNull(propertyExtension, nameof(propertyExtension));
return propertyExtension
.Configuration
.ContainsKey(EfCorePropertyConfigurationName);
}
}
}

27
framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs

@ -0,0 +1,27 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionPropertyInfoEfCoreMappingOptions
{
[NotNull]
public ObjectExtensionPropertyInfo ExtensionProperty { get; }
[NotNull]
public ObjectExtensionInfo ObjectExtension => ExtensionProperty.ObjectExtension;
[CanBeNull]
public Action<PropertyBuilder> PropertyBuildAction { get; set; }
public ObjectExtensionPropertyInfoEfCoreMappingOptions(
[NotNull] ObjectExtensionPropertyInfo extensionProperty,
[CanBeNull] Action<PropertyBuilder> propertyBuildAction = null)
{
ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty));
PropertyBuildAction = propertyBuildAction;
}
}
}

3
framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml

@ -0,0 +1,3 @@
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd">
<ConfigureAwait ContinueOnCapturedContext="false" />
</Weavers>

30
framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<!-- This file was generated by Fody. Manual changes to this file will be lost when your project is rebuilt. -->
<xs:element name="Weavers">
<xs:complexType>
<xs:all>
<xs:element name="ConfigureAwait" minOccurs="0" maxOccurs="1">
<xs:complexType>
<xs:attribute name="ContinueOnCapturedContext" type="xs:boolean" />
</xs:complexType>
</xs:element>
</xs:all>
<xs:attribute name="VerifyAssembly" type="xs:boolean">
<xs:annotation>
<xs:documentation>'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="VerifyIgnoreCodes" type="xs:string">
<xs:annotation>
<xs:documentation>A comma-separated list of error codes that can be safely ignored in assembly verification.</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="GenerateXsd" type="xs:boolean">
<xs:annotation>
<xs:documentation>'false' to turn off automatic generation of the XML Schema file.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
</xs:schema>

3
framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs

@ -0,0 +1,3 @@
using System.Runtime.CompilerServices;
[assembly: InternalsVisibleTo("Volo.Abp.ObjectExtending.Tests")]

21
framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\configureawait.props" />
<Import Project="..\..\..\common.props" />
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<AssemblyName>Volo.Abp.ObjectExtending</AssemblyName>
<PackageId>Volo.Abp.ObjectExtending</PackageId>
<AssetTargetFallback>$(AssetTargetFallback);portable-net45+win8+wp8+wpa81;</AssetTargetFallback>
<GenerateAssemblyConfigurationAttribute>false</GenerateAssemblyConfigurationAttribute>
<GenerateAssemblyCompanyAttribute>false</GenerateAssemblyCompanyAttribute>
<GenerateAssemblyProductAttribute>false</GenerateAssemblyProductAttribute>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Volo.Abp.Core\Volo.Abp.Core.csproj" />
</ItemGroup>
</Project>

0
framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs → framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs

0
framework/src/Volo.Abp.Data/Volo/Abp/Data/IHasExtraProperties.cs → framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs

9
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/AbpObjectExtendingModule.cs

@ -0,0 +1,9 @@
using Volo.Abp.Modularity;
namespace Volo.Abp.ObjectExtending
{
public class AbpObjectExtendingModule : AbpModule
{
}
}

17
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
[Serializable]
public class ExtensibleObject : IHasExtraProperties
{
public Dictionary<string, object> ExtraProperties { get; protected set; }
public ExtensibleObject()
{
ExtraProperties = new Dictionary<string, object>();
}
}
}

175
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs

@ -0,0 +1,175 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public static class HasExtraPropertiesObjectExtendingExtensions
{
/// <summary>
/// Copies extra properties from the <paramref name="source"/> object
/// to the <paramref name="destination"/> object.
///
/// Checks property definitions (over the <see cref="ObjectExtensionManager"/>)
/// based on the <paramref name="definitionChecks"/> preference.
/// </summary>
/// <typeparam name="TSource">Source class type</typeparam>
/// <typeparam name="TDestination">Destination class type</typeparam>
/// <param name="source">The source object</param>
/// <param name="destination">The destination object</param>
/// <param name="definitionChecks">
/// Controls which properties to map.
/// </param>
public static void MapExtraPropertiesTo<TSource, TDestination>(
[NotNull] this TSource source,
[NotNull] TDestination destination,
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both)
where TSource : IHasExtraProperties
where TDestination : IHasExtraProperties
{
Check.NotNull(source, nameof(source));
Check.NotNull(destination, nameof(destination));
MapExtraPropertiesTo(
typeof(TSource),
typeof(TDestination),
source.ExtraProperties,
destination.ExtraProperties,
definitionChecks
);
}
/// <summary>
/// Copies extra properties from the <paramref name="sourceDictionary"/> object
/// to the <paramref name="destinationDictionary"/> object.
///
/// Checks property definitions (over the <see cref="ObjectExtensionManager"/>)
/// based on the <paramref name="definitionChecks"/> preference.
/// </summary>
/// <typeparam name="TSource">Source class type (for definition check)</typeparam>
/// <typeparam name="TDestination">Destination class type (for definition check)</typeparam>
/// <param name="sourceDictionary">The source dictionary object</param>
/// <param name="destinationDictionary">The destination dictionary object</param>
/// <param name="definitionChecks">
/// Controls which properties to map.
/// </param>
public static void MapExtraPropertiesTo<TSource, TDestination>(
[NotNull] Dictionary<string, object> sourceDictionary,
[NotNull] Dictionary<string, object> destinationDictionary,
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both)
where TSource : IHasExtraProperties
where TDestination : IHasExtraProperties
{
MapExtraPropertiesTo(
typeof(TSource),
typeof(TDestination),
sourceDictionary,
destinationDictionary,
definitionChecks
);
}
/// <summary>
/// Copies extra properties from the <paramref name="sourceDictionary"/> object
/// to the <paramref name="destinationDictionary"/> object.
///
/// Checks property definitions (over the <see cref="ObjectExtensionManager"/>)
/// based on the <paramref name="definitionChecks"/> preference.
/// </summary>
/// <param name="sourceType">Source type (for definition check)</param>
/// <param name="destinationType">Destination class type (for definition check)</param>
/// <param name="sourceDictionary">The source dictionary object</param>
/// <param name="destinationDictionary">The destination dictionary object</param>
/// <param name="definitionChecks">
/// Controls which properties to map.
/// </param>
public static void MapExtraPropertiesTo(
[NotNull] Type sourceType,
[NotNull] Type destinationType,
[NotNull] Dictionary<string, object> sourceDictionary,
[NotNull] Dictionary<string, object> destinationDictionary,
MappingPropertyDefinitionChecks definitionChecks = MappingPropertyDefinitionChecks.Both)
{
Check.AssignableTo<IHasExtraProperties>(sourceType, nameof(sourceType));
Check.AssignableTo<IHasExtraProperties>(destinationType, nameof(destinationType));
Check.NotNull(sourceDictionary, nameof(sourceDictionary));
Check.NotNull(destinationDictionary, nameof(destinationDictionary));
var sourceObjectExtension = ObjectExtensionManager.Instance.GetOrNull(sourceType);
if (definitionChecks.HasFlag(MappingPropertyDefinitionChecks.Source) &&
sourceObjectExtension == null)
{
return;
}
var destinationObjectExtension = ObjectExtensionManager.Instance.GetOrNull(destinationType);
if (definitionChecks.HasFlag(MappingPropertyDefinitionChecks.Destination) &&
destinationObjectExtension == null)
{
return;
}
if (definitionChecks == MappingPropertyDefinitionChecks.None)
{
foreach (var keyValue in sourceDictionary)
{
destinationDictionary[keyValue.Key] = keyValue.Value;
}
}
else if (definitionChecks == MappingPropertyDefinitionChecks.Source)
{
Debug.Assert(sourceObjectExtension != null, nameof(sourceObjectExtension) + " != null");
foreach (var property in sourceObjectExtension.GetProperties())
{
if (!sourceDictionary.ContainsKey(property.Name))
{
continue;
}
destinationDictionary[property.Name] = sourceDictionary[property.Name];
}
}
else if (definitionChecks == MappingPropertyDefinitionChecks.Destination)
{
Debug.Assert(destinationObjectExtension != null, nameof(destinationObjectExtension) + " != null");
foreach (var keyValue in sourceDictionary)
{
if (!destinationObjectExtension.HasProperty(keyValue.Key))
{
continue;
}
destinationDictionary[keyValue.Key] = keyValue.Value;
}
}
else if (definitionChecks == MappingPropertyDefinitionChecks.Both)
{
Debug.Assert(sourceObjectExtension != null, nameof(sourceObjectExtension) + " != null");
Debug.Assert(destinationObjectExtension != null, nameof(destinationObjectExtension) + " != null");
foreach (var property in sourceObjectExtension.GetProperties())
{
if (!sourceDictionary.ContainsKey(property.Name))
{
continue;
}
if (!destinationObjectExtension.HasProperty(property.Name))
{
continue;
}
destinationDictionary[property.Name] = sourceDictionary[property.Name];
}
}
else
{
throw new NotImplementedException(definitionChecks + " was not implemented!");
}
}
}
}

28
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs

@ -0,0 +1,28 @@
using System;
namespace Volo.Abp.ObjectExtending
{
[Flags]
public enum MappingPropertyDefinitionChecks : byte
{
/// <summary>
/// No check. Copy all extra properties from the source to the destination.
/// </summary>
None = 0,
/// <summary>
/// Copy the extra properties defined for the source class.
/// </summary>
Source = 1,
/// <summary>
/// Copy the extra properties defined for the destination class.
/// </summary>
Destination = 2,
/// <summary>
/// Copy extra properties defined for both of the source and destination classes.
/// </summary>
Both = Source | Destination
}
}

78
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs

@ -0,0 +1,78 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionInfo
{
[NotNull]
public Type Type { get; }
[NotNull]
protected Dictionary<string, ObjectExtensionPropertyInfo> Properties { get; }
[NotNull]
public Dictionary<object, object> Configuration { get; }
public ObjectExtensionInfo([NotNull] Type type)
{
Type = Check.AssignableTo<IHasExtraProperties>(type, nameof(type));
Properties = new Dictionary<string, ObjectExtensionPropertyInfo>();
Configuration = new Dictionary<object, object>();
}
public virtual bool HasProperty(string propertyName)
{
return Properties.ContainsKey(propertyName);
}
[NotNull]
public virtual ObjectExtensionInfo AddOrUpdateProperty<TProperty>(
[NotNull] string propertyName,
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null)
{
return AddOrUpdateProperty(
typeof(TProperty),
propertyName,
configureAction
);
}
[NotNull]
public virtual ObjectExtensionInfo AddOrUpdateProperty(
[NotNull] Type propertyType,
[NotNull] string propertyName,
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null)
{
Check.NotNull(propertyType, nameof(propertyType));
Check.NotNull(propertyName, nameof(propertyName));
var propertyInfo = Properties.GetOrAdd(
propertyName,
() => new ObjectExtensionPropertyInfo(this, propertyType, propertyName)
);
configureAction?.Invoke(propertyInfo);
return this;
}
[NotNull]
public virtual ImmutableList<ObjectExtensionPropertyInfo> GetProperties()
{
return Properties.Values.ToImmutableList();
}
[CanBeNull]
public virtual ObjectExtensionPropertyInfo GetPropertyOrNull(
[NotNull] string propertyName)
{
Check.NotNullOrEmpty(propertyName, nameof(propertyName));
return Properties.GetOrDefault(propertyName);
}
}
}

66
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManager.cs

@ -0,0 +1,66 @@
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionManager
{
public static ObjectExtensionManager Instance { get; set; } = new ObjectExtensionManager();
protected Dictionary<Type, ObjectExtensionInfo> ObjectsExtensions { get; }
protected internal ObjectExtensionManager()
{
ObjectsExtensions = new Dictionary<Type, ObjectExtensionInfo>();
}
[NotNull]
public virtual ObjectExtensionManager AddOrUpdate<TObject>(
[CanBeNull] Action<ObjectExtensionInfo> configureAction = null)
where TObject : IHasExtraProperties
{
return AddOrUpdate(typeof(TObject), configureAction);
}
[NotNull]
public virtual ObjectExtensionManager AddOrUpdate(
[NotNull] Type type,
[CanBeNull] Action<ObjectExtensionInfo> configureAction = null)
{
Check.AssignableTo<IHasExtraProperties>(type, nameof(type));
var extensionInfo = ObjectsExtensions.GetOrAdd(
type,
() => new ObjectExtensionInfo(type)
);
configureAction?.Invoke(extensionInfo);
return this;
}
[CanBeNull]
public virtual ObjectExtensionInfo GetOrNull<TObject>()
where TObject : IHasExtraProperties
{
return GetOrNull(typeof(TObject));
}
[CanBeNull]
public virtual ObjectExtensionInfo GetOrNull([NotNull] Type type)
{
Check.AssignableTo<IHasExtraProperties>(type, nameof(type));
return ObjectsExtensions.GetOrDefault(type);
}
[NotNull]
public virtual ImmutableList<ObjectExtensionInfo> GetExtendedObjects()
{
return ObjectsExtensions.Values.ToImmutableList();
}
}
}

44
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionManagerExtensions.cs

@ -0,0 +1,44 @@
using System;
using JetBrains.Annotations;
using Volo.Abp.Data;
namespace Volo.Abp.ObjectExtending
{
public static class ObjectExtensionManagerExtensions
{
public static ObjectExtensionManager AddOrUpdateProperty<TObject, TProperty>(
[NotNull] this ObjectExtensionManager objectExtensionManager,
[NotNull] string propertyName,
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null)
where TObject : IHasExtraProperties
{
return objectExtensionManager.AddOrUpdateProperty(
typeof(TObject),
typeof(TProperty),
propertyName,
configureAction
);
}
public static ObjectExtensionManager AddOrUpdateProperty(
[NotNull] this ObjectExtensionManager objectExtensionManager,
[NotNull] Type objectType,
[NotNull] Type propertyType,
[NotNull] string propertyName,
[CanBeNull] Action<ObjectExtensionPropertyInfo> configureAction = null)
{
Check.NotNull(objectExtensionManager, nameof(objectExtensionManager));
return objectExtensionManager.AddOrUpdate(
objectType,
options =>
{
options.AddOrUpdateProperty(
propertyType,
propertyName,
configureAction
);
});
}
}
}

33
framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfo.cs

@ -0,0 +1,33 @@
using System;
using System.Collections.Generic;
using JetBrains.Annotations;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionPropertyInfo
{
[NotNull]
public ObjectExtensionInfo ObjectExtension { get; }
[NotNull]
public string Name { get; }
[NotNull]
public Type Type { get; }
[NotNull]
public Dictionary<object, object> Configuration { get; }
public ObjectExtensionPropertyInfo(
[NotNull] ObjectExtensionInfo objectExtension,
[NotNull] Type type,
[NotNull] string name)
{
ObjectExtension = Check.NotNull(objectExtension, nameof(objectExtension));
Type = Check.NotNull(type, nameof(type));
Name = Check.NotNull(name, nameof(name));
Configuration = new Dictionary<object, object>();
}
}
}

41
framework/test/Volo.Abp.AutoMapper.Tests/AutoMapper/AbpAutoMapperExtensibleDtoExtensions_Tests.cs

@ -0,0 +1,41 @@
using Microsoft.Extensions.DependencyInjection;
using Shouldly;
using Volo.Abp.AutoMapper;
using Volo.Abp.Data;
using Volo.Abp.ObjectExtending.TestObjects;
using Volo.Abp.Testing;
using Xunit;
namespace AutoMapper
{
public class AbpAutoMapperExtensibleDtoExtensions_Tests : AbpIntegratedTest<AutoMapperTestModule>
{
private readonly Volo.Abp.ObjectMapping.IObjectMapper _objectMapper;
public AbpAutoMapperExtensibleDtoExtensions_Tests()
{
_objectMapper = ServiceProvider.GetRequiredService<Volo.Abp.ObjectMapping.IObjectMapper>();
}
[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");
var personDto = new ExtensibleTestPersonDto()
.SetProperty("ExistingDtoProperty", "existing-value");
_objectMapper.Map(person, personDto);
personDto.GetProperty<string>("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
personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
}
}
}

2
framework/test/Volo.Abp.AutoMapper.Tests/Volo.Abp.AutoMapper.Tests.csproj

@ -11,7 +11,7 @@
<ItemGroup>
<ProjectReference Include="..\..\src\Volo.Abp.AutoMapper\Volo.Abp.AutoMapper.csproj" />
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\Volo.Abp.ObjectExtending.Tests\Volo.Abp.ObjectExtending.Tests.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>

9
framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/AutoMapperTestModule.cs

@ -1,9 +1,12 @@
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Modularity;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.AutoMapper
{
[DependsOn(typeof(AbpAutoMapperModule))]
[DependsOn(
typeof(AbpAutoMapperModule),
typeof(AbpObjectExtendingTestModule)
)]
public class AutoMapperTestModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)

4
framework/test/Volo.Abp.AutoMapper.Tests/Volo/Abp/AutoMapper/SampleClasses/MyMapProfile.cs

@ -1,4 +1,5 @@
using AutoMapper;
using Volo.Abp.ObjectExtending.TestObjects;
namespace Volo.Abp.AutoMapper.SampleClasses
{
@ -7,6 +8,9 @@ namespace Volo.Abp.AutoMapper.SampleClasses
public MyMapProfile()
{
CreateMap<MyEntity, MyEntityDto>().ReverseMap();
CreateMap<ExtensibleTestPerson, ExtensibleTestPersonDto>()
.MapExtraProperties();
}
}
}

11
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/Domain/TestEntityExtensionConfigurator.cs

@ -1,4 +1,4 @@
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.ObjectExtending;
using Volo.Abp.TestApp.Domain;
using Volo.Abp.Threading;
@ -12,10 +12,11 @@ namespace Volo.Abp.EntityFrameworkCore.Domain
{
OneTimeRunner.Run(() =>
{
EntityExtensionManager.AddProperty<City, string>(
"PhoneCode",
p => p.HasMaxLength(8)
);
ObjectExtensionManager.Instance
.MapEfCoreProperty<City, string>(
"PhoneCode",
p => p.HasMaxLength(8)
);
});
}
}

3
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/EntityFrameworkCore/TestMigrationsDbContext.cs

@ -1,5 +1,4 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.EntityFrameworkCore.TestApp.SecondContext;
using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext;
using Volo.Abp.TestApp.Domain;
@ -37,8 +36,6 @@ namespace Volo.Abp.EntityFrameworkCore
modelBuilder.Entity<City>(b =>
{
//b.ConfigureExtensions();
b.OwnsMany(c => c.Districts, d =>
{
d.WithOwner().HasForeignKey(x => x.CityId);

3
framework/test/Volo.Abp.EntityFrameworkCore.Tests/Volo/Abp/TestApp/EntityFrameworkCore/TestAppDbContext.cs

@ -1,6 +1,5 @@
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.EntityFrameworkCore.TestApp.ThirdDbContext;
using Volo.Abp.TestApp.Domain;
@ -44,8 +43,6 @@ namespace Volo.Abp.TestApp.EntityFrameworkCore
modelBuilder.Entity<City>(b =>
{
//b.ConfigureExtensions();
b.OwnsMany(c => c.Districts, d =>
{
d.WithOwner().HasForeignKey(x => x.CityId);

16
framework/test/Volo.Abp.ObjectExtending.Tests/Volo.Abp.ObjectExtending.Tests.csproj

@ -0,0 +1,16 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\..\common.test.props" />
<PropertyGroup>
<TargetFramework>netcoreapp3.1</TargetFramework>
<RootNamespace />
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\AbpTestBase\AbpTestBase.csproj" />
<ProjectReference Include="..\..\src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="16.4.0" />
</ItemGroup>
</Project>

9
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<AbpObjectExtendingTestModule>
{
}
}

27
framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/AbpObjectExtendingTestModule.cs

@ -0,0 +1,27 @@
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending.TestObjects;
using Volo.Abp.Threading;
namespace Volo.Abp.ObjectExtending
{
[DependsOn(
typeof(AbpObjectExtendingModule),
typeof(AbpTestBaseModule)
)]
public class AbpObjectExtendingTestModule : AbpModule
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public override void PreConfigureServices(ServiceConfigurationContext context)
{
OneTimeRunner.Run(() =>
{
ObjectExtensionManager.Instance
.AddOrUpdateProperty<ExtensibleTestPerson, string>("Name")
.AddOrUpdateProperty<ExtensibleTestPerson, int>("Age")
.AddOrUpdateProperty<ExtensibleTestPersonDto, string>("Name")
.AddOrUpdateProperty<ExtensibleTestPersonDto, int>("ChildCount");
});
}
}
}

73
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;
private readonly ExtensibleTestPersonDto _personDto;
public HasExtraPropertiesObjectExtendingExtensions_Tests()
{
_person = new ExtensibleTestPerson()
.SetProperty("Name", "John")
.SetProperty("Age", 42)
.SetProperty("ChildCount", 2)
.SetProperty("Sex", "male");
_personDto = new ExtensibleTestPersonDto()
.SetProperty("ExistingDtoProperty", "existing-value");
}
[Fact]
public void MapExtraPropertiesTo_Should_Only_Map_Defined_Properties_By_Default()
{
_person.MapExtraPropertiesTo(_personDto);
_personDto.GetProperty<string>("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
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
}
[Fact]
public void MapExtraPropertiesTo_Should_Only_Map_Source_Defined_Properties_If_Requested()
{
_person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.Source);
_personDto.GetProperty<string>("Name").ShouldBe("John"); //Defined in both classes
_personDto.GetProperty<int>("Age").ShouldBe(42); //Defined in source
_personDto.HasProperty("ChildCount").ShouldBeFalse(); //Not defined in the source
_personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
}
[Fact]
public void MapExtraPropertiesTo_Should_Only_Map_Destination_Defined_Properties_If_Requested()
{
_person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.Destination);
_personDto.GetProperty<string>("Name").ShouldBe("John"); //Defined in both classes
_personDto.GetProperty<int>("ChildCount").ShouldBe(2); //Defined in destination
_personDto.HasProperty("Age").ShouldBeFalse(); //Not defined in destination
_personDto.HasProperty("Sex").ShouldBeFalse(); //Not defined in both classes
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
}
[Fact]
public void MapExtraPropertiesTo_Should_Copy_all_With_No_Property_Definition_Check()
{
_person.MapExtraPropertiesTo(_personDto, MappingPropertyDefinitionChecks.None);
_personDto.GetProperty<string>("Name").ShouldBe("John");
_personDto.GetProperty<int>("Age").ShouldBe(42);
_personDto.GetProperty<int>("ChildCount").ShouldBe(2);
_personDto.GetProperty<string>("Sex").ShouldBe("male");
_personDto.GetProperty<string>("ExistingDtoProperty").ShouldBe("existing-value"); //Should not clear existing values
}
}
}

63
framework/test/Volo.Abp.ObjectExtending.Tests/Volo/Abp/ObjectExtending/ObjectExtensionManager_Tests.cs

@ -0,0 +1,63 @@
using System.Linq;
using Shouldly;
using Xunit;
namespace Volo.Abp.ObjectExtending
{
public class ObjectExtensionManager_Tests
{
private readonly ObjectExtensionManager _objectExtensionManager;
public ObjectExtensionManager_Tests()
{
_objectExtensionManager = new ObjectExtensionManager();
}
[Fact]
public void Should_Not_Add_Same_Property_Multiple_Times()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, string>("TestProp")
.AddOrUpdateProperty<MyExtensibleObject, string>("TestProp");
var objectExtension = _objectExtensionManager.GetOrNull<MyExtensibleObject>();
objectExtension.ShouldNotBeNull();
var properties = objectExtension.GetProperties();
properties.Count.ShouldBe(1);
properties.FirstOrDefault(p => p.Name == "TestProp").ShouldNotBeNull();
}
[Fact]
public void Should_Update_Property_Configuration()
{
_objectExtensionManager
.AddOrUpdateProperty<MyExtensibleObject, string>(
"TestProp",
options =>
{
options.Configuration["TestConfig1"] = "TestConfig1-Value";
}
).AddOrUpdateProperty<MyExtensibleObject, string>(
"TestProp",
options =>
{
options.Configuration["TestConfig2"] = "TestConfig2-Value";
}
);
var objectExtension = _objectExtensionManager.GetOrNull<MyExtensibleObject>();
objectExtension.ShouldNotBeNull();
var property = objectExtension.GetPropertyOrNull("TestProp");
property.ShouldNotBeNull();
property.Configuration["TestConfig1"].ShouldBe("TestConfig1-Value");
property.Configuration["TestConfig2"].ShouldBe("TestConfig2-Value");
}
private class MyExtensibleObject : ExtensibleObject
{
}
}
}

7
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
{
}
}

7
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
{
}
}

1
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IIdentityRoleAppService.cs

@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;

3
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleCreateOrUpdateDtoBase.cs

@ -1,8 +1,9 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Identity
{
public class IdentityRoleCreateOrUpdateDtoBase
public class IdentityRoleCreateOrUpdateDtoBase : ExtensibleObject
{
[Required]
[StringLength(IdentityRoleConsts.MaxNameLength)]

4
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityRoleDto.cs

@ -4,12 +4,12 @@ using Volo.Abp.Domain.Entities;
namespace Volo.Abp.Identity
{
public class IdentityRoleDto : EntityDto<Guid>, IHasConcurrencyStamp
public class IdentityRoleDto : ExtensibleEntityDto<Guid>, IHasConcurrencyStamp
{
public string Name { get; set; }
public bool IsDefault { get; set; }
public bool IsStatic { get; set; }
public bool IsPublic { get; set; }

3
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserCreateOrUpdateDtoBase.cs

@ -1,9 +1,10 @@
using System.ComponentModel.DataAnnotations;
using JetBrains.Annotations;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Identity
{
public abstract class IdentityUserCreateOrUpdateDtoBase
public abstract class IdentityUserCreateOrUpdateDtoBase : ExtensibleObject
{
[Required]
[StringLength(IdentityUserConsts.MaxUserNameLength)]

2
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/IdentityUserDto.cs

@ -5,7 +5,7 @@ using Volo.Abp.MultiTenancy;
namespace Volo.Abp.Identity
{
public class IdentityUserDto : FullAuditedEntityDto<Guid>, IMultiTenant, IHasConcurrencyStamp
public class IdentityUserDto : ExtensibleFullAuditedEntityDto<Guid>, IMultiTenant, IHasConcurrencyStamp
{
public Guid? TenantId { get; set; }

6
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/ProfileDto.cs

@ -1,6 +1,8 @@
namespace Volo.Abp.Identity
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Identity
{
public class ProfileDto
public class ProfileDto : ExtensibleObject
{
public string UserName { get; set; }

3
modules/identity/src/Volo.Abp.Identity.Application.Contracts/Volo/Abp/Identity/UpdateProfileDto.cs

@ -1,8 +1,9 @@
using System.ComponentModel.DataAnnotations;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Identity
{
public class UpdateProfileDto
public class UpdateProfileDto : ExtensibleObject
{
[StringLength(IdentityUserConsts.MaxUserNameLength)]
public string UserName { get; set; }

11
modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/AbpIdentityApplicationModuleAutoMapperProfile.cs

@ -6,9 +6,14 @@ namespace Volo.Abp.Identity
{
public AbpIdentityApplicationModuleAutoMapperProfile()
{
CreateMap<IdentityUser, IdentityUserDto>();
CreateMap<IdentityRole, IdentityRoleDto>();
CreateMap<IdentityUser, ProfileDto>();
CreateMap<IdentityUser, IdentityUserDto>()
.MapExtraProperties();
CreateMap<IdentityRole, IdentityRoleDto>()
.MapExtraProperties();
CreateMap<IdentityUser, ProfileDto>()
.MapExtraProperties();
}
}
}

22
modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityRoleAppService.cs

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Application.Dtos;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Identity
{
@ -24,14 +25,16 @@ namespace Volo.Abp.Identity
public virtual async Task<IdentityRoleDto> GetAsync(Guid id)
{
return ObjectMapper.Map<IdentityRole, IdentityRoleDto>(
await RoleManager.GetByIdAsync(id));
await RoleManager.GetByIdAsync(id)
);
}
public virtual async Task<ListResultDto<IdentityRoleDto>> GetAllListAsync()
{
var list = await RoleRepository.GetListAsync();
return new ListResultDto<IdentityRoleDto>(
ObjectMapper.Map<List<IdentityRole>, List<IdentityRoleDto>>(list));
ObjectMapper.Map<List<IdentityRole>, List<IdentityRoleDto>>(list)
);
}
public virtual async Task<PagedResultDto<IdentityRoleDto>> GetListAsync(PagedAndSortedResultRequestDto input)
@ -48,10 +51,17 @@ namespace Volo.Abp.Identity
[Authorize(IdentityPermissions.Roles.Create)]
public virtual async Task<IdentityRoleDto> CreateAsync(IdentityRoleCreateDto input)
{
var role = new IdentityRole(GuidGenerator.Create(), input.Name, CurrentTenant.Id);
var role = new IdentityRole(
GuidGenerator.Create(),
input.Name,
CurrentTenant.Id
)
{
IsDefault = input.IsDefault,
IsPublic = input.IsPublic
};
role.IsDefault = input.IsDefault;
role.IsPublic = input.IsPublic;
input.MapExtraPropertiesTo(role);
(await RoleManager.CreateAsync(role)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();
@ -70,6 +80,8 @@ namespace Volo.Abp.Identity
role.IsDefault = input.IsDefault;
role.IsPublic = input.IsPublic;
input.MapExtraPropertiesTo(role);
(await RoleManager.UpdateAsync(role)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();

16
modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/IdentityUserAppService.cs

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Application.Dtos;
using Volo.Abp.ObjectExtending;
namespace Volo.Abp.Identity
{
@ -45,6 +46,7 @@ namespace Volo.Abp.Identity
public virtual async Task<ListResultDto<IdentityRoleDto>> GetRolesAsync(Guid id)
{
var roles = await UserRepository.GetRolesAsync(id);
return new ListResultDto<IdentityRoleDto>(
ObjectMapper.Map<List<IdentityRole>, List<IdentityRoleDto>>(roles)
);
@ -53,7 +55,14 @@ namespace Volo.Abp.Identity
[Authorize(IdentityPermissions.Users.Create)]
public virtual async Task<IdentityUserDto> CreateAsync(IdentityUserCreateDto input)
{
var user = new IdentityUser(GuidGenerator.Create(), input.UserName, input.Email, CurrentTenant.Id);
var user = new IdentityUser(
GuidGenerator.Create(),
input.UserName,
input.Email,
CurrentTenant.Id
);
input.MapExtraPropertiesTo(user);
(await UserManager.CreateAsync(user, input.Password)).CheckErrors();
await UpdateUserByInput(user, input);
@ -70,7 +79,10 @@ namespace Volo.Abp.Identity
user.ConcurrencyStamp = input.ConcurrencyStamp;
(await UserManager.SetUserNameAsync(user, input.UserName)).CheckErrors();
await UpdateUserByInput(user, input);
input.MapExtraPropertiesTo(user);
(await UserManager.UpdateAsync(user)).CheckErrors();
if (!input.Password.IsNullOrEmpty())
@ -78,7 +90,7 @@ namespace Volo.Abp.Identity
(await UserManager.RemovePasswordAsync(user)).CheckErrors();
(await UserManager.AddPasswordAsync(user, input.Password)).CheckErrors();
}
await CurrentUnitOfWork.SaveChangesAsync();
return ObjectMapper.Map<IdentityUser, IdentityUserDto>(user);

3
modules/identity/src/Volo.Abp.Identity.Application/Volo/Abp/Identity/ProfileAppService.cs

@ -2,6 +2,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Volo.Abp.Identity.Settings;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Settings;
using Volo.Abp.Users;
@ -43,6 +44,8 @@ namespace Volo.Abp.Identity
user.Name = input.Name;
user.Surname = input.Surname;
input.MapExtraPropertiesTo(user);
(await UserManager.UpdateAsync(user)).CheckErrors();
await CurrentUnitOfWork.SaveChangesAsync();

1
modules/identity/src/Volo.Abp.Identity.EntityFrameworkCore/Volo/Abp/Identity/EntityFrameworkCore/IdentityDbContextModelBuilderExtensions.cs

@ -1,7 +1,6 @@
using System;
using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.Users.EntityFrameworkCore;

8
modules/identity/src/Volo.Abp.Identity.Web/AbpIdentityWebAutoMapperProfile.cs

@ -22,6 +22,7 @@ namespace Volo.Abp.Identity.Web
//CreateModal
CreateMap<CreateUserModalModel.UserInfoViewModel, IdentityUserCreateDto>()
.Ignore(x => x.ExtraProperties)
.ForMember(dest => dest.RoleNames, opt => opt.Ignore());
CreateMap<IdentityRoleDto, CreateUserModalModel.AssignedRoleViewModel>()
@ -29,6 +30,7 @@ namespace Volo.Abp.Identity.Web
//EditModal
CreateMap<EditUserModalModel.UserInfoViewModel, IdentityUserUpdateDto>()
.Ignore(x => x.ExtraProperties)
.ForMember(dest => dest.RoleNames, opt => opt.Ignore());
CreateMap<IdentityRoleDto, EditUserModalModel.AssignedRoleViewModel>()
@ -41,10 +43,12 @@ namespace Volo.Abp.Identity.Web
CreateMap<IdentityRoleDto, EditModalModel.RoleInfoModel>();
//CreateModal
CreateMap<CreateModalModel.RoleInfoModel, IdentityRoleCreateDto>();
CreateMap<CreateModalModel.RoleInfoModel, IdentityRoleCreateDto>()
.Ignore(x => x.ExtraProperties);
//EditModal
CreateMap<EditModalModel.RoleInfoModel, IdentityRoleUpdateDto>();
CreateMap<EditModalModel.RoleInfoModel, IdentityRoleUpdateDto>()
.Ignore(x => x.ExtraProperties);
}
}
}

1
nupkg/common.ps1

@ -91,6 +91,7 @@ $projects = (
"framework/src/Volo.Abp.MongoDB",
"framework/src/Volo.Abp.MultiTenancy",
"framework/src/Volo.Abp.Minify",
"framework/src/Volo.Abp.ObjectExtending",
"framework/src/Volo.Abp.ObjectMapping",
"framework/src/Volo.Abp.Quartz",
"framework/src/Volo.Abp.RabbitMQ",

1
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyCompanyName.MyProjectName.Application.Contracts.csproj

@ -12,6 +12,7 @@
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\..\..\..\framework\src\Volo.Abp.ObjectExtending\Volo.Abp.ObjectExtending.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\account\src\Volo.Abp.Account.Application.Contracts\Volo.Abp.Account.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\identity\src\Volo.Abp.Identity.Application.Contracts\Volo.Abp.Identity.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\..\modules\permission-management\src\Volo.Abp.PermissionManagement.Application.Contracts\Volo.Abp.PermissionManagement.Application.Contracts.csproj" />

9
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameApplicationContractsModule.cs

@ -2,6 +2,7 @@
using Volo.Abp.FeatureManagement;
using Volo.Abp.Identity;
using Volo.Abp.Modularity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.PermissionManagement;
using Volo.Abp.TenantManagement;
@ -13,10 +14,14 @@ namespace MyCompanyName.MyProjectName
typeof(AbpFeatureManagementApplicationContractsModule),
typeof(AbpIdentityApplicationContractsModule),
typeof(AbpPermissionManagementApplicationContractsModule),
typeof(AbpTenantManagementApplicationContractsModule)
typeof(AbpTenantManagementApplicationContractsModule),
typeof(AbpObjectExtendingModule)
)]
public class MyProjectNameApplicationContractsModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
MyProjectNameDtoExtensions.Configure();
}
}
}

29
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.Application.Contracts/MyProjectNameDtoExtensions.cs

@ -0,0 +1,29 @@
using Volo.Abp.Identity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Threading;
namespace MyCompanyName.MyProjectName
{
public static class MyProjectNameDtoExtensions
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
OneTimeRunner.Run(() =>
{
/* You can add extension properties to DTOs
* defined in the depended modules.
*
* Example:
*
* ObjectExtensionManager.Instance
* .AddOrUpdateProperty<IdentityRoleDto, string>("Title");
*
* See the documentation for more:
* https://docs.abp.io/en/abp/latest/Object-Extensions
*/
});
}
}
}

15
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.EntityFrameworkCore/EntityFrameworkCore/MyProjectNameEntityExtensions.cs

@ -1,5 +1,5 @@
using Volo.Abp.EntityFrameworkCore.Extensions;
using Volo.Abp.Identity;
using Volo.Abp.Identity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Threading;
namespace MyCompanyName.MyProjectName.EntityFrameworkCore
@ -17,12 +17,11 @@ namespace MyCompanyName.MyProjectName.EntityFrameworkCore
*
* Example:
*
* EntityExtensionManager.AddProperty<IdentityUser, string>(
* "MyProperty",
* b =>
* {
* b.HasMaxLength(128);
* });
* ObjectExtensionManager.Instance
* .MapEfCoreProperty<IdentityUser, string>(
* "MyProperty",
* b => b.HasMaxLength(128)
* );
*
* See the documentation for more:
* https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities

29
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MongoDb/MyProjectNameEntityExtensions.cs

@ -0,0 +1,29 @@
using Volo.Abp.Identity;
using Volo.Abp.ObjectExtending;
using Volo.Abp.Threading;
namespace MyCompanyName.MyProjectName.MongoDB
{
public static class MyProjectNameEntityExtensions
{
private static readonly OneTimeRunner OneTimeRunner = new OneTimeRunner();
public static void Configure()
{
OneTimeRunner.Run(() =>
{
/* You can extension properties to entities
* defined in the depended modules.
*
* Example:
*
* ObjectExtensionManager.Instance
* .AddOrUpdateProperty<IdentityRole, string>("Title");
*
* See the documentation for more:
* https://docs.abp.io/en/abp/latest/Customizing-Application-Modules-Extending-Entities
*/
});
}
}
}

5
templates/app/aspnet-core/src/MyCompanyName.MyProjectName.MongoDB/MongoDb/MyProjectNameMongoDbModule.cs

@ -24,6 +24,11 @@ namespace MyCompanyName.MyProjectName.MongoDB
)]
public class MyProjectNameMongoDbModule : AbpModule
{
public override void PreConfigureServices(ServiceConfigurationContext context)
{
MyProjectNameEntityExtensions.Configure();
}
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddMongoDbContext<MyProjectNameMongoDbContext>(options =>

Loading…
Cancel
Save