diff --git a/build/common.ps1 b/build/common.ps1
index e4ba6a8adc..06fa20b35e 100644
--- a/build/common.ps1
+++ b/build/common.ps1
@@ -28,6 +28,7 @@ $solutionPaths = (
"../samples/BookStore-Modular/modules/book-management",
"../samples/BookStore-Modular/application",
"../samples/DashboardDemo",
+ "../samples/EfCoreMigrationDemo",
"../samples/MicroserviceDemo",
"../samples/RabbitMqEventBus",
"../abp_io/AbpIoLocalization"
diff --git a/common.DotSettings b/common.DotSettings
index cb0b2c919f..0eb4875d49 100644
--- a/common.DotSettings
+++ b/common.DotSettings
@@ -20,4 +20,5 @@
FalseFalseSQL
+ True
\ No newline at end of file
diff --git a/docs/en/API/Dynamic-CSharp-API-Clients.md b/docs/en/API/Dynamic-CSharp-API-Clients.md
index 3c3c2ec26c..460623440f 100644
--- a/docs/en/API/Dynamic-CSharp-API-Clients.md
+++ b/docs/en/API/Dynamic-CSharp-API-Clients.md
@@ -69,7 +69,7 @@ public class MyClientAppModule : AbpModule
}
````
-See the "RemoteServiceOptions" section below for more detailed configuration.
+See the "AbpRemoteServiceOptions" section below for more detailed configuration.
## Usage
@@ -104,7 +104,7 @@ While you can inject `IBookAppService` like above to use the client proxy, you c
## Configuration
-### RemoteServiceOptions
+### AbpRemoteServiceOptions
`AbpRemoteServiceOptions` is automatically set from the `appsettings.json` by default. Alternatively, you can use `Configure` method to set or override it. Example:
diff --git a/docs/en/Authorization.md b/docs/en/Authorization.md
index 1bceda951f..888d907b59 100644
--- a/docs/en/Authorization.md
+++ b/docs/en/Authorization.md
@@ -339,10 +339,10 @@ A permission value provider should return one of the following values from the `
- `PermissionGrantResult.Prohibited` is returned to prohibit the user for the permission. If any of the providers return `Prohibited`, the result will always be `Prohibited`. Doesn't matter what other providers return.
- `PermissionGrantResult.Undefined` is returned if this value provider could not decide about the permission value. Return this to let other providers check the permission.
-Once a provider is defined, it should be added to the `PermissionOptions` as shown below:
+Once a provider is defined, it should be added to the `AbpPermissionOptions` as shown below:
```csharp
-Configure(options =>
+Configure(options =>
{
options.ValueProviders.Add();
});
diff --git a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md b/docs/en/Best-Practices/Entity-Framework-Core-Integration.md
index db8117d798..f487f9d61d 100644
--- a/docs/en/Best-Practices/Entity-Framework-Core-Integration.md
+++ b/docs/en/Best-Practices/Entity-Framework-Core-Integration.md
@@ -107,10 +107,10 @@ public static class IdentityDbContextModelBuilderExtensions
````
* **Do** call `b.ConfigureByConvention();` for each entity mapping (as shown above).
-* **Do** create a **configuration options** class by inheriting from the `ModelBuilderConfigurationOptions`. Example:
+* **Do** create a **configuration options** class by inheriting from the `AbpModelBuilderConfigurationOptions`. Example:
````C#
-public class IdentityModelBuilderConfigurationOptions : ModelBuilderConfigurationOptions
+public class IdentityModelBuilderConfigurationOptions : AbpModelBuilderConfigurationOptions
{
public IdentityModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix, AbpIdentityConsts.DefaultDbSchema)
diff --git a/docs/en/Best-Practices/MongoDB-Integration.md b/docs/en/Best-Practices/MongoDB-Integration.md
index 76b6e8561f..74a0e429f1 100644
--- a/docs/en/Best-Practices/MongoDB-Integration.md
+++ b/docs/en/Best-Practices/MongoDB-Integration.md
@@ -90,11 +90,11 @@ public static class AbpIdentityMongoDbContextExtensions
}
```
-- **Do** create a **configuration options** class by inheriting from the `MongoModelBuilderConfigurationOptions`. Example:
+- **Do** create a **configuration options** class by inheriting from the `AbpMongoModelBuilderConfigurationOptions`. Example:
```c#
public class IdentityMongoModelBuilderConfigurationOptions
- : MongoModelBuilderConfigurationOptions
+ : AbpMongoModelBuilderConfigurationOptions
{
public IdentityMongoModelBuilderConfigurationOptions()
: base(AbpIdentityConsts.DefaultDbTablePrefix)
diff --git a/docs/en/CLI.md b/docs/en/CLI.md
index a347599135..f0fd3ae7bd 100644
--- a/docs/en/CLI.md
+++ b/docs/en/CLI.md
@@ -60,6 +60,7 @@ abp new Acme.BookStore
* `--version` or `-v`: Specifies the ABP & template version. It can be a [release tag](https://github.com/abpframework/abp/releases) or a [branch name](https://github.com/abpframework/abp/branches). Uses the latest release if not specified. Most of the times, you will want to use the latest version.
* `--template-source` or `-ts`: Specifies a custom template source to use to build the project. Local and network sources can be used(Like `D\localTemplate` or `https://.zip`).
* `--create-solution-folder` or `-csf`: Specifies if the project will be in a new folder in the output folder or directly the output folder.
+* `--connection-string` or `-cs`: Overwrites the default connection strings in all `appsettings.json` files. The default connection string is `Server=localhost;Database=MyProjectName;Trusted_Connection=True;MultipleActiveResultSets=true`. You can set your own connection string if you don't want to use the default. Be aware that the default database provider is `SQL Server`, therefore you can only enter connection string for SQL Server!
### add-package
diff --git a/docs/en/Customizing-Application-Modules-Extending-Entities.md b/docs/en/Customizing-Application-Modules-Extending-Entities.md
index be8bda6068..be28465ff7 100644
--- a/docs/en/Customizing-Application-Modules-Extending-Entities.md
+++ b/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(
- "SocialSecurityNumber",
- b => { b.HasMaxLength(32); }
-);
+ObjectExtensionManager.Instance
+ .MapEfCoreProperty(
+ "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).
diff --git a/docs/en/Entities.md b/docs/en/Entities.md
index fee1a3618c..8a234b94a5 100644
--- a/docs/en/Entities.md
+++ b/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
diff --git a/docs/en/Entity-Framework-Core-Migrations.md b/docs/en/Entity-Framework-Core-Migrations.md
index 5d2fff011c..fbe0f59d70 100644
--- a/docs/en/Entity-Framework-Core-Migrations.md
+++ b/docs/en/Entity-Framework-Core-Migrations.md
@@ -409,18 +409,19 @@ public static class MyProjectNameEntityExtensions
{
OneTimeRunner.Run(() =>
{
- EntityExtensionManager.AddProperty(
- "Title",
- b => { b.HasMaxLength(128); }
- );
+ ObjectExtensionManager.Instance
+ .MapEfCoreProperty(
+ "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
diff --git a/docs/en/Entity-Framework-Core.md b/docs/en/Entity-Framework-Core.md
index d908ce03f5..66f9d3c2f9 100644
--- a/docs/en/Entity-Framework-Core.md
+++ b/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(
- "Title",
- b => { b.HasMaxLength(128); }
-);
+ObjectExtensionManager.Instance
+ .MapEfCoreProperty(
+ "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(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
diff --git a/docs/en/Modules/Docs.md b/docs/en/Modules/Docs.md
index db752ceb74..bbed28e8fe 100644
--- a/docs/en/Modules/Docs.md
+++ b/docs/en/Modules/Docs.md
@@ -134,7 +134,7 @@ An ABP module must declare `[DependsOn]` attribute if it has a dependency upon a
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
- Configure(options =>
+ Configure(options =>
{
options.DefinitionProviders.Add();
});
diff --git a/docs/en/Object-Extensions.md b/docs/en/Object-Extensions.md
new file mode 100644
index 0000000000..fad3ff2b0c
--- /dev/null
+++ b/docs/en/Object-Extensions.md
@@ -0,0 +1,3 @@
+# Object Extensions
+
+TODO
\ No newline at end of file
diff --git a/docs/en/UI/Angular/Component-Replacement.md b/docs/en/UI/Angular/Component-Replacement.md
index b17c61ae73..d718b46117 100644
--- a/docs/en/UI/Angular/Component-Replacement.md
+++ b/docs/en/UI/Angular/Component-Replacement.md
@@ -1,10 +1,10 @@
-# Component Replacement
+## Component Replacement
You can replace some ABP components with your custom components.
The reason that you **can replace** but **cannot customize** default ABP components is disabling or changing a part of that component can cause problems. So we named those components as _Replaceable Components_.
-## How to Replace a Component
+### How to Replace a Component
Create a new component that you want to use instead of an ABP component. Add that component to `declarations` and `entryComponents` in the `AppModule`.
@@ -29,7 +29,54 @@ export class AppComponent {

-## Available Replaceable Components
+
+### How to Replace a Layout
+
+Each ABP theme module has 3 layouts named `ApplicationLayoutComponent`, `AccountLayoutComponent`, `EmptyLayoutComponent`. These layouts can be replaced with the same way.
+
+> A layout component template should contain `` element.
+
+The below example describes how to replace the `ApplicationLayoutComponent`:
+
+Run the following command to generate a layout in `angular` folder:
+
+```bash
+yarn ng generate component shared/my-application-layout --export --entryComponent
+
+# You don't need the --entryComponent option in Angular 9
+```
+
+Add the following code in your layout template (`my-layout.component.html`) where you want the page to be loaded.
+
+```html
+
+```
+
+Open the `app.component.ts` and add the below content:
+
+```js
+import { ..., AddReplaceableComponent } from '@abp/ng.core'; // imported AddReplaceableComponent
+import { MyApplicationLayoutComponent } from './shared/my-application-layout/my-application-layout.component'; // imported MyApplicationLayoutComponent
+import { Store } from '@ngxs/store'; // imported Store
+//...
+export class AppComponent {
+ constructor(..., private store: Store) {} // injected Store
+
+ ngOnInit() {
+ // added below content
+ this.store.dispatch(
+ new AddReplaceableComponent({
+ component: MyApplicationLayoutComponent,
+ key: 'Theme.ApplicationLayoutComponent',
+ }),
+ );
+
+ //...
+ }
+}
+```
+
+### Available Replaceable Components
| Component key | Description |
| -------------------------------------------------- | --------------------------------------------- |
diff --git a/docs/en/UI/Angular/Content-Security-Strategy.md b/docs/en/UI/Angular/Content-Security-Strategy.md
new file mode 100644
index 0000000000..21d794a1af
--- /dev/null
+++ b/docs/en/UI/Angular/Content-Security-Strategy.md
@@ -0,0 +1,56 @@
+# ContentSecurityStrategy
+
+`ContentSecurityStrategy` is an abstract class exposed by @abp/ng.core package. It helps you mark inline scripts or styles as safe in terms of [Content Security Policy](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy).
+
+
+
+
+## API
+
+
+### constructor(public nonce?: string)
+
+- `nonce` enables whitelisting inline script or styles in order to avoid using `unsafe-inline` in [script-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/script-src#Unsafe_inline_script) and [style-src](https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy/style-src#Unsafe_inline_styles) directives.
+
+
+### applyCSP(element: HTMLScriptElement | HTMLStyleElement): void
+
+This method maps the aforementioned properties to the given `element`.
+
+
+
+
+## LooseContentSecurityPolicy
+
+`LooseContentSecurityPolicy` is a class that extends `ContentSecurityStrategy`. It requires `nonce` and marks given `
+````
+
+在你自己的代码中更改 `UA-xxxxxx-1` .
+
+然后你可以在你模块的 `ConfigureServices` 方法将这个组件添加到任何的钩子点:
+
+````csharp
+Configure(options =>
+{
+ options.Add(
+ LayoutHooks.Head.Last, //The hook name
+ typeof(GoogleAnalyticsViewComponent) //The component to add
+ );
+});
+````
+
+现在谷歌统计代码将在页面的 `head` 所为最后一项插入. 你(或你在使用的模块)可以将多个项添加到相同的钩子,它们都会添加到布局.
+
+在上面我们添加 `GoogleAnalyticsViewComponent` 到所有的布局,你可能只想添加到指定的布局:
+
+````csharp
+Configure(options =>
+{
+ options.Add(
+ LayoutHooks.Head.Last,
+ typeof(GoogleAnalyticsViewComponent),
+ layout: StandardLayouts.Application //Set the layout to add
+ );
+});
+````
+
+参阅下面的布局部分,以了解有关布局系统的更多信息.
+
+### 布局
+
+布局系统允许主题定义标准,命名布局并且允许任何页面选择使用合适的布局. 有三种预定义的布局:
+
+* "**Application**": 应用程序的主要(和默认)布局. 它通常包含页眉,菜单(侧栏),页脚,工具栏等.
+* "**Account**": 登录,注册和其他类似页面使用此布局. 默认它用于 `/Pages/Account` 文件夹下的页面.
+* "**Empty**": 空的最小的布局.
+
+这些名称在 `StandardLayouts` 类定义为常量. 这是标准的布局名称,所有的主题开箱即用的实现. 你也可以创建自己的布局.
+
+#### 布局位置
+
+你可以在[这里](https://github.com/abpframework/abp/tree/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Layouts)找到基本主题的布局文件. 你可以将它们作用构建自己的布局的参考,也可以在必要时覆盖它们.
+
+#### ITheme
+
+ABP框架使用 `ITheme` 服务通过局部名称获取布局位置. 你可以替换此服务动态的选择布局位置.
+
+#### IThemeManager
+
+`IThemeManager` 用于获取当前主题,并得到了布局路径. 任何页面可以都决定自己的布局. 例:
+
+````html
+@using Volo.Abp.AspNetCore.Mvc.UI.Theming
+@inject IThemeManager ThemeManager
+@{
+ Layout = ThemeManager.CurrentTheme.GetLayout(StandardLayouts.Empty);
+}
+````
+
+此页面将使用空白布局. 它使用 `ThemeManager.CurrentTheme.GetEmptyLayout()` 扩展方法.
+
+如果你设置特定目录下所有页面的布局,可以在该文件夹下的 `_ViewStart.cshtml` 文件编写以上代码.
diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json
index 3837d2547f..c1aa2be23d 100644
--- a/docs/zh-Hans/docs-nav.json
+++ b/docs/zh-Hans/docs-nav.json
@@ -5,6 +5,7 @@
"items": [
{
"text": "从启动模板开始",
+ "path": "Getting-Started-With-Startup-Templates.md",
"items": [
{
"text": "ASP.NET Core MVC 模板",
@@ -282,6 +283,10 @@
"text": "仪表板和小部件(Widget)系统",
"path": "UI/AspNetCore/Widgets.md"
},
+ {
+ "text": "自定义/扩展UI",
+ "path": "UI/AspNetCore/Customization-User-Interface.md"
+ },
{
"text": "主题化",
"path": "UI/AspNetCore/Theming.md"
diff --git a/framework/Volo.Abp.sln b/framework/Volo.Abp.sln
index 88c8385b63..3db0998855 100644
--- a/framework/Volo.Abp.sln
+++ b/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}
diff --git a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
index 73b6e025ea..f4c66f56f2 100644
--- a/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
+++ b/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Bundling/Volo/Abp/AspNetCore/Mvc/UI/Bundling/TagHelpers/AbpTagHelperResourceService.cs
@@ -63,7 +63,8 @@ namespace Volo.Abp.AspNetCore.Mvc.UI.Bundling.TagHelpers
foreach (var bundleFile in bundleFiles)
{
var file = WebContentFileProvider.GetFileInfo(bundleFile);
- if (file == null)
+
+ if (file == null || !file.Exists)
{
throw new AbpException($"Could not find the bundle file from {nameof(IWebContentFileProvider)}");
}
diff --git a/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs
new file mode 100644
index 0000000000..664e35e9b0
--- /dev/null
+++ b/framework/src/Volo.Abp.AutoMapper/AutoMapper/AbpAutoMapperExtensibleDtoExtensions.cs
@@ -0,0 +1,37 @@
+using System.Collections.Generic;
+using Volo.Abp.Data;
+using Volo.Abp.ObjectExtending;
+
+namespace AutoMapper
+{
+ public static class AbpAutoMapperExtensibleDtoExtensions
+ {
+ public static IMappingExpression MapExtraProperties(
+ this IMappingExpression mappingExpression,
+ MappingPropertyDefinitionChecks? definitionChecks = null)
+ where TDestination : IHasExtraProperties
+ where TSource : IHasExtraProperties
+ {
+ return mappingExpression
+ .ForMember(
+ x => x.ExtraProperties,
+ y => y.MapFrom(
+ (source, destination, extraProps) =>
+ {
+ var result = extraProps.IsNullOrEmpty()
+ ? new Dictionary()
+ : new Dictionary(extraProps);
+
+ HasExtraPropertiesObjectExtendingExtensions
+ .MapExtraPropertiesTo(
+ source.ExtraProperties,
+ result,
+ definitionChecks
+ );
+
+ return result;
+ })
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj
index e6146fedde..327f2078d1 100644
--- a/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj
+++ b/framework/src/Volo.Abp.AutoMapper/Volo.Abp.AutoMapper.csproj
@@ -15,6 +15,7 @@
+
diff --git a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs b/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs
index ba3ef2ada8..1590e879b1 100644
--- a/framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AbpAutoMapperModule.cs
+++ b/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)
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
index c7d7b4a87b..8a0c21a254 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/Commands/NewCommand.cs
@@ -73,6 +73,12 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation("UI Framework: " + uiFramework);
}
+ var connectionString = GetConnectionString(commandLineArgs);
+ if (connectionString != null)
+ {
+ Logger.LogInformation("Connection string: " + connectionString);
+ }
+
var mobileApp = GetMobilePreference(commandLineArgs);
if (mobileApp != MobileApp.None)
{
@@ -122,7 +128,8 @@ namespace Volo.Abp.Cli.Commands
mobileApp,
gitHubLocalRepositoryPath,
templateSource,
- commandLineArgs.Options
+ commandLineArgs.Options,
+ connectionString
)
);
@@ -168,6 +175,12 @@ namespace Volo.Abp.Cli.Commands
Logger.LogInformation($"'{projectName}' has been successfully created to '{outputFolder}'");
}
+ private static string GetConnectionString(CommandLineArgs commandLineArgs)
+ {
+ var connectionString = commandLineArgs.Options.GetOrNull(Options.ConnectionString.Short, Options.ConnectionString.Long);
+ return string.IsNullOrWhiteSpace(connectionString) ? null : connectionString;
+ }
+
public string GetUsageInfo()
{
var sb = new StringBuilder();
@@ -205,6 +218,7 @@ namespace Volo.Abp.Cli.Commands
sb.AppendLine(" abp new Acme.BookStore -t module --no-ui");
sb.AppendLine(" abp new Acme.BookStore -ts \"D:\\localTemplate\\abp\"");
sb.AppendLine(" abp new Acme.BookStore --local-framework-ref --abp-path \"D:\\github\\abp\"");
+ sb.AppendLine(" abp new Acme.BookStore --connection-string \"Server=myServerName\\myInstanceName;Database=myDatabase;User Id=myUsername;Password=myPassword\"");
sb.AppendLine("");
sb.AppendLine("See the documentation for more info: https://docs.abp.io/en/abp/latest/CLI");
@@ -309,6 +323,12 @@ namespace Volo.Abp.Cli.Commands
public const string Long = "template-source";
}
+ public static class ConnectionString
+ {
+ public const string Short = "cs";
+ public const string Long = "connection-string";
+ }
+
public static class CreateSolutionFolder
{
public const string Short = "csf";
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs
new file mode 100644
index 0000000000..b1e118ee8b
--- /dev/null
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/Steps/ConnectionStringChangeStep.cs
@@ -0,0 +1,76 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using Newtonsoft.Json;
+using Newtonsoft.Json.Linq;
+using Volo.Abp.Cli.ProjectBuilding.Files;
+using Volo.Abp.Text;
+
+namespace Volo.Abp.Cli.ProjectBuilding.Building.Steps
+{
+ public class ConnectionStringChangeStep : ProjectBuildPipelineStep
+ {
+ private const string DefaultConnectionStringKey = "Default";
+
+ public override void Execute(ProjectBuildContext context)
+ {
+ var appSettingsJsonFiles = context.Files.Where(f =>
+ f.Name.EndsWith("appsettings.json", StringComparison.OrdinalIgnoreCase))
+ .ToArray();
+
+ if (!appSettingsJsonFiles.Any())
+ {
+ return;
+ }
+
+ var newConnectionString = $"\"{DefaultConnectionStringKey}\": \"{context.BuildArgs.ConnectionString}\"";
+
+ foreach (var appSettingsJson in appSettingsJsonFiles)
+ {
+ try
+ {
+ var appSettingJsonContentWithoutBom = StringHelper.ConvertFromBytesWithoutBom(appSettingsJson.Bytes);
+ var jsonObject = JObject.Parse(appSettingJsonContentWithoutBom);
+
+ var connectionStringContainer = (JContainer)jsonObject?["ConnectionStrings"];
+ if (connectionStringContainer == null)
+ {
+ continue;
+ }
+
+ if (!connectionStringContainer.Any())
+ {
+ continue;
+ }
+
+ var connectionStrings = connectionStringContainer.ToList();
+
+ foreach (var connectionString in connectionStrings)
+ {
+ var property = ((JProperty)connectionString);
+ var connectionStringName = property.Name;
+
+ if (connectionStringName == DefaultConnectionStringKey)
+ {
+ var defaultConnectionString = property.ToString();
+ if (defaultConnectionString == null)
+ {
+ continue;
+ }
+
+ appSettingsJson.ReplaceText(defaultConnectionString, newConnectionString);
+ break;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine("Cannot change the connection string in " + appSettingsJson.Name + ". Error: " + ex.Message);
+ }
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs
index a68de8ef62..607ec4f1c7 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/Building/TemplateProjectBuildPipelineBuilder.cs
@@ -28,6 +28,11 @@ namespace Volo.Abp.Cli.ProjectBuilding.Building
pipeline.Steps.Add(new RemoveRootFolderStep());
}
+ if (context.BuildArgs.ConnectionString != null)
+ {
+ pipeline.Steps.Add(new ConnectionStringChangeStep());
+ }
+
pipeline.Steps.Add(new CreateProjectResultZipStep());
return pipeline;
diff --git a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs
index ea207bc426..70cb98dff0 100644
--- a/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs
+++ b/framework/src/Volo.Abp.Cli.Core/Volo/Abp/Cli/ProjectBuilding/ProjectBuildArgs.cs
@@ -27,6 +27,9 @@ namespace Volo.Abp.Cli.ProjectBuilding
[CanBeNull]
public string TemplateSource { get; set; }
+ [CanBeNull]
+ public string ConnectionString { get; set; }
+
[NotNull]
public Dictionary ExtraProperties { get; set; }
@@ -39,7 +42,8 @@ namespace Volo.Abp.Cli.ProjectBuilding
MobileApp? mobileApp = null,
[CanBeNull] string abpGitHubLocalRepositoryPath = null,
[CanBeNull] string templateSource = null,
- Dictionary extraProperties = null)
+ Dictionary extraProperties = null,
+ [CanBeNull] string connectionString = null)
{
SolutionName = Check.NotNull(solutionName, nameof(solutionName));
TemplateName = templateName;
@@ -50,6 +54,7 @@ namespace Volo.Abp.Cli.ProjectBuilding
AbpGitHubLocalRepositoryPath = abpGitHubLocalRepositoryPath;
TemplateSource = templateSource;
ExtraProperties = extraProperties ?? new Dictionary();
+ ConnectionString = connectionString;
}
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs
index 44b75d1adb..82d393eef3 100644
--- a/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs
+++ b/framework/src/Volo.Abp.Core/Microsoft/Extensions/Configuration/ConfigurationHelper.cs
@@ -6,7 +6,8 @@ namespace Microsoft.Extensions.Configuration
public static class ConfigurationHelper
{
public static IConfigurationRoot BuildConfiguration(
- AbpConfigurationBuilderOptions options = null)
+ AbpConfigurationBuilderOptions options = null,
+ Action builderAction = null)
{
options = options ?? new AbpConfigurationBuilderOptions();
@@ -43,6 +44,8 @@ namespace Microsoft.Extensions.Configuration
builder = builder.AddCommandLine(options.CommandLineArgs);
}
+ builderAction?.Invoke(builder);
+
return builder.Build();
}
}
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs
index f942f44b0f..7001925745 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Check.cs
+++ b/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(
+ Type type,
+ [InvokerParameterName] [NotNull] string parameterName)
+ {
+ NotNull(type, parameterName);
+
+ if (!type.IsAssignableTo())
+ {
+ 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,
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs
index 21ca7c2086..a945784352 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/IO/FileHelper.cs
@@ -1,8 +1,10 @@
using System;
+using System.Collections.Generic;
using System.IO;
using System.Text;
using System.Threading.Tasks;
using JetBrains.Annotations;
+using Volo.Abp.Text;
namespace Volo.Abp.IO
{
@@ -73,6 +75,63 @@ namespace Volo.Abp.IO
}
}
- //TODO: ReadAllLinesAsync
+ ///
+ /// Opens a text file, reads all lines of the file, and then closes the file.
+ ///
+ /// The file to open for reading.
+ /// Encoding of the file. Default is UTF8
+ /// Specifies how the operating system should open a file. Default is Open
+ /// Defines constants for read, write, or read/write access to a file. Default is Read
+ /// Contains constants for controlling the kind of access other FileStream objects can have to the same file. Default is Read
+ /// Length of StreamReader buffer. Default is 4096.
+ /// Indicates FileStream options. Default is Asynchronous (The file is to be used for asynchronous reading.) and SequentialScan (The file is to be accessed sequentially from beginning to end.)
+ /// A string containing all lines of the file.
+ public static async Task ReadAllLinesAsync(string path,
+ Encoding encoding = null,
+ FileMode fileMode = FileMode.Open,
+ FileAccess fileAccess = FileAccess.Read,
+ FileShare fileShare = FileShare.Read,
+ int bufferSize = 4096,
+ FileOptions fileOptions = FileOptions.Asynchronous | FileOptions.SequentialScan)
+ {
+ if (encoding == null)
+ {
+ encoding = Encoding.UTF8;
+ }
+
+ var lines = new List();
+
+ using (var stream = new FileStream(
+ path,
+ fileMode,
+ fileAccess,
+ fileShare,
+ bufferSize,
+ fileOptions))
+ {
+ using (var reader = new StreamReader(stream, encoding))
+ {
+ string line;
+ while ((line = await reader.ReadLineAsync()) != null)
+ {
+ lines.Add(line);
+ }
+ }
+ }
+
+ return lines.ToArray();
+ }
+
+ ///
+ /// Opens a text file, reads content without BOM
+ ///
+ /// The file to open for reading.
+ /// A string containing all lines of the file.
+ public static async Task ReadFileWithoutBomAsync(string path)
+ {
+ var content = await ReadAllBytesAsync(path);
+
+ return StringHelper.ConvertFromBytesWithoutBom(content);
+ }
}
}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
index b62f21f124..5f66f4788e 100644
--- a/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Reflection/TypeHelper.cs
@@ -1,7 +1,6 @@
using System;
using System.Collections;
using System.Collections.Generic;
-using System.Collections.ObjectModel;
using System.Linq;
using System.Reflection;
@@ -15,7 +14,7 @@ namespace Volo.Abp.Reflection
{
return false;
}
-
+
var type = obj.GetType();
if (!type.GetTypeInfo().IsGenericType)
{
@@ -37,9 +36,7 @@ namespace Volo.Abp.Reflection
return true;
}
- if (includeNullables &&
- type.IsGenericType &&
- type.GetGenericTypeDefinition() == typeof(Nullable<>))
+ if (includeNullables && IsNullable(type))
{
return IsPrimitiveExtendedInternal(type.GenericTypeArguments[0], includeEnums);
}
@@ -47,6 +44,11 @@ namespace Volo.Abp.Reflection
return false;
}
+ public static bool IsNullable(Type type)
+ {
+ return type.IsGenericType && type.GetGenericTypeDefinition() == typeof(Nullable<>);
+ }
+
public static Type GetFirstGenericArgumentIfNullable(this Type t)
{
if (t.GetGenericArguments().Length > 0 && t.GetGenericTypeDefinition() == typeof(Nullable<>))
diff --git a/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs b/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs
new file mode 100644
index 0000000000..c6be5db02b
--- /dev/null
+++ b/framework/src/Volo.Abp.Core/Volo/Abp/Text/StringHelper.cs
@@ -0,0 +1,37 @@
+using System.Text;
+
+namespace Volo.Abp.Text
+{
+ public class StringHelper
+ {
+ ///
+ /// Converts a byte[] to string without BOM (byte order mark).
+ ///
+ /// The byte[] to be converted to string
+ /// The encoding to get string. Default is UTF8
+ ///
+ public static string ConvertFromBytesWithoutBom(byte[] bytes, Encoding encoding = null)
+ {
+ if (bytes == null)
+ {
+ return null;
+ }
+
+ if (encoding == null)
+ {
+ encoding = Encoding.UTF8;
+ }
+
+ var hasBom = bytes.Length >= 3 && bytes[0] == 0xEF && bytes[1] == 0xBB && bytes[2] == 0xBF;
+
+ if (hasBom)
+ {
+ return encoding.GetString(bytes, 3, bytes.Length - 3);
+ }
+ else
+ {
+ return encoding.GetString(bytes);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj b/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj
index 7a53782ae1..cde2718a56 100644
--- a/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj
+++ b/framework/src/Volo.Abp.Data/Volo.Abp.Data.csproj
@@ -16,6 +16,7 @@
+
diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs b/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs
index 2414803970..8c9643012e 100644
--- a/framework/src/Volo.Abp.Data/Volo/Abp/Data/AbpDataModule.cs
+++ b/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)
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs
index c5dd43de14..8a8e99d869 100644
--- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs
+++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/AuditedEntityWithUserDto.cs
@@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos
{
///
/// This class can be inherited by DTO classes to implement interface.
+ /// It has the and objects as a DTOs represent the related user.
///
/// Type of the User DTO
[Serializable]
@@ -19,6 +20,7 @@ namespace Volo.Abp.Application.Dtos
///
/// This class can be inherited by DTO classes to implement interface.
+ /// It has the and objects as a DTOs represent the related user.
///
/// Type of primary key
/// Type of the User DTO
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs
index 51e3c9018a..5d6260ebcd 100644
--- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs
+++ b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/CreationAuditedEntityWithUserDto.cs
@@ -5,6 +5,7 @@ namespace Volo.Abp.Application.Dtos
{
///
/// This class can be inherited by DTO classes to implement interface.
+ /// It also has the object as a DTO represents the user.
///
/// Type of the User DTO
[Serializable]
@@ -14,7 +15,8 @@ namespace Volo.Abp.Application.Dtos
}
///
- /// This class can be inherited by DTO classes to implement interface.
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It also has the object as a DTO represents the user.
///
/// Type of primary key
/// Type of the User DTO
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityDto.cs
new file mode 100644
index 0000000000..6be7d38070
--- /dev/null
+++ b/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
+{
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It also implements the interface.
+ ///
+ /// Type of primary key
+ [Serializable]
+ public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEntityDto, IAuditedObject
+ {
+ ///
+ public DateTime? LastModificationTime { get; set; }
+
+ ///
+ public Guid? LastModifierId { get; set; }
+ }
+
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It also implements the interface.
+ ///
+ [Serializable]
+ public abstract class ExtensibleAuditedEntityDto : ExtensibleCreationAuditedEntityDto, IAuditedObject
+ {
+ ///
+ public DateTime? LastModificationTime { get; set; }
+
+ ///
+ public Guid? LastModifierId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleAuditedEntityWithUserDto.cs
new file mode 100644
index 0000000000..94086c119b
--- /dev/null
+++ b/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
+{
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the and objects as a DTOs represent the related user.
+ /// It also implements the interface.
+ ///
+ /// Type of primary key
+ /// Type of the User DTO
+ [Serializable]
+ public abstract class ExtensibleAuditedEntityWithUserDto : ExtensibleAuditedEntityDto, IAuditedObject
+ {
+ ///
+ public TUserDto Creator { get; set; }
+
+ ///
+ public TUserDto LastModifier { get; set; }
+ }
+
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the and objects as a DTOs represent the related user.
+ /// It also implements the interface.
+ ///
+ /// Type of the User DTO
+ [Serializable]
+ public abstract class ExtensibleAuditedEntityWithUserDto : ExtensibleAuditedEntityDto,
+ IAuditedObject
+ {
+ ///
+ public TUserDto Creator { get; set; }
+
+ ///
+ public TUserDto LastModifier { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityDto.cs
new file mode 100644
index 0000000000..1de40ff4d5
--- /dev/null
+++ b/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
+{
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It also implements the interface.
+ ///
+ /// Type of primary key
+ [Serializable]
+ public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, ICreationAuditedObject
+ {
+ ///
+ public DateTime CreationTime { get; set; }
+
+ ///
+ public Guid? CreatorId { get; set; }
+ }
+
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It also implements the interface.
+ ///
+ [Serializable]
+ public abstract class ExtensibleCreationAuditedEntityDto : ExtensibleEntityDto, ICreationAuditedObject
+ {
+ ///
+ public DateTime CreationTime { get; set; }
+
+ ///
+ public Guid? CreatorId { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleCreationAuditedEntityWithUserDto.cs
new file mode 100644
index 0000000000..d5352d5b03
--- /dev/null
+++ b/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
+{
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the object as a DTO represents the user.
+ /// It also implements the interface.
+ ///
+ /// Type of primary key
+ /// Type of the User DTO
+ [Serializable]
+ public abstract class ExtensibleCreationAuditedEntityWithUserDto : ExtensibleCreationAuditedEntityDto, ICreationAuditedObject
+ {
+ public TUserDto Creator { get; set; }
+ }
+
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the object as a DTO represents the user.
+ /// It also implements the interface.
+ ///
+ /// Type of the User DTO
+ [Serializable]
+ public abstract class ExtensibleCreationAuditedEntityWithUserDto : ExtensibleCreationAuditedEntityDto,
+ ICreationAuditedObject
+ {
+ public TUserDto Creator { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleEntityDto.cs
new file mode 100644
index 0000000000..63d07bcdcf
--- /dev/null
+++ b/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 : ExtensibleObject, IEntityDto
+ {
+ ///
+ /// Id of the entity.
+ ///
+ 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}]";
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityDto.cs
new file mode 100644
index 0000000000..d72bf8135d
--- /dev/null
+++ b/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
+{
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It also implements the interface.
+ ///
+ /// Type of primary key
+ [Serializable]
+ public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDto, IFullAuditedObject
+ {
+ ///
+ public bool IsDeleted { get; set; }
+
+ ///
+ public Guid? DeleterId { get; set; }
+
+ ///
+ public DateTime? DeletionTime { get; set; }
+ }
+
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It also implements the interface.
+ ///
+ [Serializable]
+ public abstract class ExtensibleFullAuditedEntityDto : ExtensibleAuditedEntityDto, IFullAuditedObject
+ {
+ ///
+ public bool IsDeleted { get; set; }
+
+ ///
+ public Guid? DeleterId { get; set; }
+
+ ///
+ public DateTime? DeletionTime { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/ExtensibleFullAuditedEntityWithUserDto.cs
new file mode 100644
index 0000000000..b5e68e7cb7
--- /dev/null
+++ b/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
+{
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the , and objects as a DTOs represent the related user.
+ /// It also implements the interface.
+ ///
+ /// Type of primary key
+ /// Type of the User
+ [Serializable]
+ public abstract class ExtensibleFullAuditedEntityWithUserDto : ExtensibleFullAuditedEntityDto, IFullAuditedObject
+ {
+ ///
+ public TUserDto Creator { get; set; }
+
+ ///
+ public TUserDto LastModifier { get; set; }
+
+ ///
+ public TUserDto Deleter { get; set; }
+ }
+
+ ///
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the , and objects as a DTOs represent the related user.
+ /// It also implements the interface.
+ ///
+ /// Type of the User
+ [Serializable]
+ public abstract class ExtensibleFullAuditedEntityWithUserDto : ExtensibleFullAuditedEntityDto,
+ IFullAuditedObject
+ {
+ ///
+ public TUserDto Creator { get; set; }
+
+ ///
+ public TUserDto LastModifier { get; set; }
+
+ ///
+ public TUserDto Deleter { get; set; }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs b/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs
index 91e31250fb..30b1f0b2ba 100644
--- a/framework/src/Volo.Abp.Ddd.Application.Contracts/Volo/Abp/Application/Dtos/FullAuditedEntityWithUserDto.cs
+++ b/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
{
///
- /// This class can be inherited by DTO classes to implement interface.
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the , and objects as a DTOs represent the related user.
///
/// Type of the User
[Serializable]
@@ -21,7 +22,8 @@ namespace Volo.Abp.Application.Dtos
}
///
- /// This class can be inherited by DTO classes to implement interface.
+ /// This class can be inherited by DTO classes to implement interface.
+ /// It has the , and objects as a DTOs represent the related user.
///
/// Type of primary key
/// Type of the User
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
index ebad209a50..d565f6c114 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
+++ b/framework/src/Volo.Abp.EntityFrameworkCore.MySQL/Volo.Abp.EntityFrameworkCore.MySQL.csproj
@@ -19,7 +19,7 @@
-
+
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
index 2488ec9513..3ba7092395 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/AbpDbContext.cs
+++ b/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);
}
}
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs
deleted file mode 100644
index f1aa105b9f..0000000000
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionInfo.cs
+++ /dev/null
@@ -1,14 +0,0 @@
-using System.Collections.Generic;
-
-namespace Volo.Abp.EntityFrameworkCore.Extensions
-{
- public class EntityExtensionInfo
- {
- public Dictionary Properties { get; set; }
-
- public EntityExtensionInfo()
- {
- Properties = new Dictionary();
- }
- }
-}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs
deleted file mode 100644
index d9f1ae9c31..0000000000
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/EntityExtensionManager.cs
+++ /dev/null
@@ -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 ExtensionInfos;
-
- static EntityExtensionManager()
- {
- ExtensionInfos = new Dictionary();
- }
-
- ///
- /// Adds an extension property for an entity.
- /// If it is already added, replaces the
- /// by the given one!
- ///
- /// Type of the entity
- /// Type of the new property
- /// Name of the property
- /// An action to configure the database mapping for the new property
- public static void AddProperty(
- [NotNull]string propertyName,
- [NotNull]Action propertyBuildAction)
- {
- AddProperty(
- typeof(TEntity),
- typeof(TProperty),
- propertyName,
- propertyBuildAction
- );
- }
-
- ///
- /// Adds an extension property for an entity.
- /// If it is already added, replaces the
- /// by the given one!
- ///
- /// Type of the entity
- /// Type of the new property
- /// Name of the property
- /// An action to configure the database mapping for the new property
- public static void AddProperty(
- Type entityType,
- Type propertyType,
- [NotNull]string propertyName,
- [NotNull]Action 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;
- }
-
- ///
- /// Configures the entity mapping for the defined extensions.
- ///
- /// The entity tye
- /// Entity type builder
- public static void ConfigureExtensions(
- [NotNull] this EntityTypeBuilder entityTypeBuilder)
- where TEntity : class, IHasExtraProperties
- {
- ConfigureExtensions(typeof(TEntity), entityTypeBuilder);
- }
-
- ///
- /// Configures the entity mapping for the defined extensions.
- ///
- /// Type of the entity
- /// Entity type builder
- 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();
- }
-
- return entityExtensionInfo
- .Properties
- .Select(p => p.Key)
- .ToArray();
- }
- }
-}
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs
deleted file mode 100644
index df29bdd62d..0000000000
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Extensions/PropertyExtensionInfo.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using Microsoft.EntityFrameworkCore.Metadata.Builders;
-
-namespace Volo.Abp.EntityFrameworkCore.Extensions
-{
- public class PropertyExtensionInfo
- {
- public Action Action { get; set; }
-
- public Type PropertyType { get; }
-
- public PropertyExtensionInfo(Type propertyType)
- {
- PropertyType = propertyType;
- }
- }
-}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
index eca8831e35..0a84b6871d 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/Modeling/AbpEntityTypeBuilderExtensions.cs
+++ b/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())
+ if (!b.Metadata.ClrType.IsAssignableTo())
{
- b.Property>(nameof(IHasExtraProperties.ExtraProperties))
- .HasColumnName(nameof(IHasExtraProperties.ExtraProperties))
- .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType))
- .Metadata.SetValueComparer(new AbpDictionaryValueComparer());
+ return;
+ }
- EntityExtensionManager.ConfigureExtensions(b.Metadata.ClrType, b);
+ b.Property>(nameof(IHasExtraProperties.ExtraProperties))
+ .HasColumnName(nameof(IHasExtraProperties.ExtraProperties))
+ .HasConversion(new ExtraPropertiesValueConverter(b.Metadata.ClrType))
+ .Metadata.SetValueComparer(new AbpDictionaryValueComparer());
+
+ b.TryConfigureObjectExtensions();
+ }
+
+ public static void ConfigureObjectExtensions(this EntityTypeBuilder b)
+ where T : class, IHasExtraProperties
+ {
+ b.As().TryConfigureObjectExtensions();
+ }
+
+ public static void TryConfigureObjectExtensions(this EntityTypeBuilder b)
+ {
+ if (b.Metadata.ClrType.IsAssignableTo())
+ {
+ ObjectExtensionManager.Instance.ConfigureEfCoreEntity(b);
}
}
@@ -286,7 +302,6 @@ namespace Volo.Abp.EntityFrameworkCore.Modeling
b.As().TryConfigureConcurrencyStamp();
}
-
//TODO: Add other interfaces (IAuditedObject...)
}
}
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
index fb1abf0e24..ddcdb942ba 100644
--- a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/EntityFrameworkCore/ValueConverters/ExtraPropertiesValueConverter.cs
+++ b/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);
+ }
+ }
}
}
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionInfoExtensions.cs
new file mode 100644
index 0000000000..ef3d87119a
--- /dev/null
+++ b/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(
+ [NotNull] this ObjectExtensionInfo objectExtensionInfo,
+ [NotNull] string propertyName,
+ [CanBeNull] Action propertyBuildAction)
+ {
+ return objectExtensionInfo.MapEfCoreProperty(
+ typeof(TProperty),
+ propertyName,
+ propertyBuildAction
+ );
+ }
+
+ public static ObjectExtensionInfo MapEfCoreProperty(
+ [NotNull] this ObjectExtensionInfo objectExtensionInfo,
+ [NotNull] Type propertyType,
+ [NotNull] string propertyName,
+ [CanBeNull] Action propertyBuildAction)
+ {
+ Check.NotNull(objectExtensionInfo, nameof(objectExtensionInfo));
+
+ return objectExtensionInfo.AddOrUpdateProperty(
+ propertyType,
+ propertyName,
+ options =>
+ {
+ options.MapEfCore(
+ propertyBuildAction
+ );
+ }
+ );
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionManagerExtensions.cs
new file mode 100644
index 0000000000..52dca0c105
--- /dev/null
+++ b/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(
+ [NotNull] this ObjectExtensionManager objectExtensionManager,
+ [NotNull] string propertyName,
+ [CanBeNull] Action 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 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);
+ }
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/EfCoreObjectExtensionPropertyInfoExtensions.cs
new file mode 100644
index 0000000000..fda5d88ceb
--- /dev/null
+++ b/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 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);
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs b/framework/src/Volo.Abp.EntityFrameworkCore/Volo/Abp/ObjectExtending/ObjectExtensionPropertyInfoEfCoreMappingOptions.cs
new file mode 100644
index 0000000000..4189c87962
--- /dev/null
+++ b/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 PropertyBuildAction { get; set; }
+
+ public ObjectExtensionPropertyInfoEfCoreMappingOptions(
+ [NotNull] ObjectExtensionPropertyInfo extensionProperty,
+ [CanBeNull] Action propertyBuildAction = null)
+ {
+ ExtensionProperty = Check.NotNull(extensionProperty, nameof(extensionProperty));
+
+ PropertyBuildAction = propertyBuildAction;
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
index 3e0124237f..d5d90ed182 100644
--- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
+++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/DynamicHttpProxyInterceptor.cs
@@ -56,7 +56,7 @@ namespace Volo.Abp.Http.Client.DynamicProxying
IJsonSerializer jsonSerializer,
IRemoteServiceHttpClientAuthenticator clientAuthenticator,
ICancellationTokenProvider cancellationTokenProvider,
- ICorrelationIdProvider correlationIdProvider,
+ ICorrelationIdProvider correlationIdProvider,
IOptions correlationIdOptions,
ICurrentTenant currentTenant)
{
@@ -110,7 +110,14 @@ namespace Volo.Abp.Http.Client.DynamicProxying
//TODO: Think on that
if (TypeHelper.IsPrimitiveExtended(typeof(T), true))
{
- return (T)Convert.ChangeType(responseAsString, typeof(T));
+ if (typeof(DateTime).IsAssignableFrom(typeof(T)))
+ {
+ return (T)(object)DateTime.Parse(responseAsString.Trim('\"'), CultureInfo.InvariantCulture);
+ }
+ else
+ {
+ return (T)Convert.ChangeType(responseAsString, typeof(T));
+ }
}
return JsonSerializer.Deserialize(responseAsString);
@@ -151,8 +158,8 @@ namespace Volo.Abp.Http.Client.DynamicProxying
}
return await response.Content.ReadAsStringAsync();
- }
-
+ }
+
private ApiVersionInfo GetApiVersionInfo(ActionApiDescriptionModel action)
{
var apiVersion = FindBestApiVersion(action);
diff --git a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs
index dcb9868b7f..dae7853e08 100644
--- a/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs
+++ b/framework/src/Volo.Abp.Http.Client/Volo/Abp/Http/Client/DynamicProxying/UrlBuilder.cs
@@ -1,9 +1,12 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using System.Text;
+using JetBrains.Annotations;
using Volo.Abp.Http.Modeling;
using Volo.Abp.Http.ProxyScripting.Generators;
+using Volo.Abp.Reflection;
namespace Volo.Abp.Http.Client.DynamicProxying
{
@@ -88,10 +91,25 @@ namespace Volo.Abp.Http.Client.DynamicProxying
}
}
- private static void AddQueryStringParameter(StringBuilder urlBuilder, bool isFirstParam, string name, object value)
+ private static void AddQueryStringParameter(
+ StringBuilder urlBuilder,
+ bool isFirstParam,
+ string name,
+ [NotNull] object value)
{
urlBuilder.Append(isFirstParam ? "?" : "&");
- urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(value.ToString()));
+
+ urlBuilder.Append(name + "=" + System.Net.WebUtility.UrlEncode(ConvertValueToString(value)));
+ }
+
+ private static string ConvertValueToString([NotNull] object value)
+ {
+ if (value is DateTime dateTimeValue)
+ {
+ return dateTimeValue.ToUniversalTime().ToString("u");
+ }
+
+ return value.ToString();
}
}
}
diff --git a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs
index e69d3f67c7..b78a05c684 100644
--- a/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs
+++ b/framework/src/Volo.Abp.IdentityModel/Volo/Abp/IdentityModel/IdentityModelAuthenticationService.cs
@@ -70,9 +70,17 @@ namespace Volo.Abp.IdentityModel
}
var tokenResponse = await GetTokenResponse(discoveryResponse, configuration);
+
if (tokenResponse.IsError)
{
- throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}");
+ if (tokenResponse.ErrorDescription != null)
+ {
+ throw new AbpException($"Could not get token from the OpenId Connect server! ErrorType: {tokenResponse.ErrorType}. Error: {tokenResponse.Error}. ErrorDescription: {tokenResponse.ErrorDescription}. HttpStatusCode: {tokenResponse.HttpStatusCode}");
+ }
+
+ var rawError = tokenResponse.Raw;
+ var withoutInnerException = rawError.Split(new string[] { "" }, StringSplitOptions.RemoveEmptyEntries);
+ throw new AbpException(withoutInnerException[0]);
}
return tokenResponse.AccessToken;
diff --git a/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml
new file mode 100644
index 0000000000..be0de3a908
--- /dev/null
+++ b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xml
@@ -0,0 +1,3 @@
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd
new file mode 100644
index 0000000000..3f3946e282
--- /dev/null
+++ b/framework/src/Volo.Abp.ObjectExtending/FodyWeavers.xsd
@@ -0,0 +1,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 'true' to run assembly verification (PEVerify) on the target assembly after all weavers have been executed.
+
+
+
+
+ A comma-separated list of error codes that can be safely ignored in assembly verification.
+
+
+
+
+ 'false' to turn off automatic generation of the XML Schema file.
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs
new file mode 100644
index 0000000000..fe0b656805
--- /dev/null
+++ b/framework/src/Volo.Abp.ObjectExtending/Properties/AssemblyInfo.cs
@@ -0,0 +1,3 @@
+using System.Runtime.CompilerServices;
+
+[assembly: InternalsVisibleTo("Volo.Abp.ObjectExtending.Tests")]
diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj b/framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj
new file mode 100644
index 0000000000..43da943f8b
--- /dev/null
+++ b/framework/src/Volo.Abp.ObjectExtending/Volo.Abp.ObjectExtending.csproj
@@ -0,0 +1,21 @@
+
+
+
+
+
+
+ netstandard2.0
+ Volo.Abp.ObjectExtending
+ Volo.Abp.ObjectExtending
+ $(AssetTargetFallback);portable-net45+win8+wp8+wpa81;
+ false
+ false
+ false
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
similarity index 100%
rename from framework/src/Volo.Abp.Data/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
rename to framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/HasExtraPropertiesExtensions.cs
diff --git a/framework/src/Volo.Abp.Data/Volo/Abp/Data/IHasExtraProperties.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs
similarity index 100%
rename from framework/src/Volo.Abp.Data/Volo/Abp/Data/IHasExtraProperties.cs
rename to framework/src/Volo.Abp.ObjectExtending/Volo/Abp/Data/IHasExtraProperties.cs
diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/AbpObjectExtendingModule.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/AbpObjectExtendingModule.cs
new file mode 100644
index 0000000000..9392429fd3
--- /dev/null
+++ b/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
+ {
+
+ }
+}
diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ExtensibleObject.cs
new file mode 100644
index 0000000000..07243c9dca
--- /dev/null
+++ b/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 ExtraProperties { get; protected set; }
+
+ public ExtensibleObject()
+ {
+ ExtraProperties = new Dictionary();
+ }
+ }
+}
diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs
new file mode 100644
index 0000000000..6b687fcf81
--- /dev/null
+++ b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/HasExtraPropertiesObjectExtendingExtensions.cs
@@ -0,0 +1,216 @@
+using System;
+using System.Collections.Generic;
+using JetBrains.Annotations;
+using Volo.Abp.Data;
+
+namespace Volo.Abp.ObjectExtending
+{
+ public static class HasExtraPropertiesObjectExtendingExtensions
+ {
+ ///
+ /// Copies extra properties from the object
+ /// to the object.
+ ///
+ /// Checks property definitions (over the )
+ /// based on the preference.
+ ///
+ /// Source class type
+ /// Destination class type
+ /// The source object
+ /// The destination object
+ ///
+ /// Controls which properties to map.
+ ///
+ public static void MapExtraPropertiesTo(
+ [NotNull] this TSource source,
+ [NotNull] TDestination destination,
+ MappingPropertyDefinitionChecks? definitionChecks = null)
+ 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
+ );
+ }
+
+ ///
+ /// Copies extra properties from the object
+ /// to the object.
+ ///
+ /// Checks property definitions (over the )
+ /// based on the preference.
+ ///
+ /// Source class type (for definition check)
+ /// Destination class type (for definition check)
+ /// The source dictionary object
+ /// The destination dictionary object
+ ///
+ /// Controls which properties to map.
+ ///
+ public static void MapExtraPropertiesTo(
+ [NotNull] Dictionary sourceDictionary,
+ [NotNull] Dictionary destinationDictionary,
+ MappingPropertyDefinitionChecks? definitionChecks = null)
+ where TSource : IHasExtraProperties
+ where TDestination : IHasExtraProperties
+ {
+ MapExtraPropertiesTo(
+ typeof(TSource),
+ typeof(TDestination),
+ sourceDictionary,
+ destinationDictionary,
+ definitionChecks
+ );
+ }
+
+ ///
+ /// Copies extra properties from the object
+ /// to the object.
+ ///
+ /// Checks property definitions (over the )
+ /// based on the preference.
+ ///
+ /// Source type (for definition check)
+ /// Destination class type (for definition check)
+ /// The source dictionary object
+ /// The destination dictionary object
+ ///
+ /// Controls which properties to map.
+ ///
+ public static void MapExtraPropertiesTo(
+ [NotNull] Type sourceType,
+ [NotNull] Type destinationType,
+ [NotNull] Dictionary sourceDictionary,
+ [NotNull] Dictionary destinationDictionary,
+ MappingPropertyDefinitionChecks? definitionChecks = null)
+ {
+ Check.AssignableTo(sourceType, nameof(sourceType));
+ Check.AssignableTo(destinationType, nameof(destinationType));
+ Check.NotNull(sourceDictionary, nameof(sourceDictionary));
+ Check.NotNull(destinationDictionary, nameof(destinationDictionary));
+
+ var sourceObjectExtension = ObjectExtensionManager.Instance.GetOrNull(sourceType);
+ var destinationObjectExtension = ObjectExtensionManager.Instance.GetOrNull(destinationType);
+
+ foreach (var keyValue in sourceDictionary)
+ {
+ if (CanMapProperty(
+ keyValue.Key,
+ sourceObjectExtension,
+ destinationObjectExtension,
+ definitionChecks))
+ {
+ destinationDictionary[keyValue.Key] = keyValue.Value;
+ }
+ }
+ }
+
+ //TODO: Move these methods to a class like ObjectExtensionHelper
+
+ public static bool CanMapProperty(
+ [NotNull] string propertyName,
+ MappingPropertyDefinitionChecks? definitionChecks = null)
+ {
+ return CanMapProperty(
+ typeof(TSource),
+ typeof(TDestination),
+ propertyName,
+ definitionChecks
+ );
+ }
+
+ public static bool CanMapProperty(
+ [NotNull] Type sourceType,
+ [NotNull] Type destinationType,
+ [NotNull] string propertyName,
+ MappingPropertyDefinitionChecks? definitionChecks = null)
+ {
+ Check.AssignableTo(sourceType, nameof(sourceType));
+ Check.AssignableTo(destinationType, nameof(destinationType));
+ Check.NotNull(propertyName, nameof(propertyName));
+
+ var sourceObjectExtension = ObjectExtensionManager.Instance.GetOrNull(sourceType);
+ var destinationObjectExtension = ObjectExtensionManager.Instance.GetOrNull(destinationType);
+
+ return CanMapProperty(
+ propertyName,
+ sourceObjectExtension,
+ destinationObjectExtension,
+ definitionChecks);
+ }
+
+ private static bool CanMapProperty(
+ [NotNull] string propertyName,
+ [CanBeNull] ObjectExtensionInfo sourceObjectExtension,
+ [CanBeNull] ObjectExtensionInfo destinationObjectExtension,
+ MappingPropertyDefinitionChecks? definitionChecks = null)
+ {
+ Check.NotNull(propertyName, nameof(propertyName));
+
+ if (definitionChecks != null)
+ {
+ if (definitionChecks.Value.HasFlag(MappingPropertyDefinitionChecks.Source))
+ {
+ if (sourceObjectExtension == null)
+ {
+ return false;
+ }
+
+ if (!sourceObjectExtension.HasProperty(propertyName))
+ {
+ return false;
+ }
+ }
+
+ if (definitionChecks.Value.HasFlag(MappingPropertyDefinitionChecks.Destination))
+ {
+ if (destinationObjectExtension == null)
+ {
+ return false;
+ }
+
+ if (!destinationObjectExtension.HasProperty(propertyName))
+ {
+ return false;
+ }
+ }
+
+ return true;
+ }
+ else
+ {
+ var sourcePropertyDefinition = sourceObjectExtension?.GetPropertyOrNull(propertyName);
+ var destinationPropertyDefinition = destinationObjectExtension?.GetPropertyOrNull(propertyName);
+
+ if (sourcePropertyDefinition != null)
+ {
+ if (destinationPropertyDefinition != null)
+ {
+ return true;
+ }
+
+ if (sourcePropertyDefinition.CheckPairDefinitionOnMapping == false)
+ {
+ return true;
+ }
+ }
+ else if (destinationPropertyDefinition != null)
+ {
+ if (destinationPropertyDefinition.CheckPairDefinitionOnMapping == false)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/MappingPropertyDefinitionChecks.cs
new file mode 100644
index 0000000000..b33638fa7c
--- /dev/null
+++ b/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
+ {
+ ///
+ /// No check. Copy all extra properties from the source to the destination.
+ ///
+ None = 0,
+
+ ///
+ /// Copy the extra properties defined for the source class.
+ ///
+ Source = 1,
+
+ ///
+ /// Copy the extra properties defined for the destination class.
+ ///
+ Destination = 2,
+
+ ///
+ /// Copy extra properties defined for both of the source and destination classes.
+ ///
+ Both = Source | Destination
+ }
+}
\ No newline at end of file
diff --git a/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs b/framework/src/Volo.Abp.ObjectExtending/Volo/Abp/ObjectExtending/ObjectExtensionInfo.cs
new file mode 100644
index 0000000000..89ecbab574
--- /dev/null
+++ b/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 Properties { get; }
+
+ [NotNull]
+ public Dictionary