mirror of https://github.com/abpframework/abp.git
141 changed files with 8391 additions and 1836 deletions
@ -0,0 +1,6 @@ |
|||
# Getting Started with the Startup Templates |
|||
|
|||
See the following tutorials to learn how to get started with the ABP Framework using the pre-built application startup templates: |
|||
|
|||
* [Getting Started With the ASP.NET Core MVC / Razor Pages UI](Getting-Started-AspNetCore-MVC-Template.md) |
|||
* [Getting Started with the Angular UI](Getting-Started-Angular-Template.md) |
|||
@ -0,0 +1,3 @@ |
|||
# Object Extensions |
|||
|
|||
TODO |
|||
File diff suppressed because it is too large
@ -0,0 +1,67 @@ |
|||
## Service Proxies |
|||
|
|||
It is common to call a REST endpoint in the server from our Angular applications. In this case, we generally create **services** (those have methods for each service method on the server side) and **model objects** (matches to [DTOs](../../Data-Transfer-Objects) in the server side). |
|||
|
|||
In addition to manually creating such server-interacting services, we could use tools like [NSWAG](https://github.com/RicoSuter/NSwag) to generate service proxies for us. But NSWAG has the following problems we've experienced: |
|||
|
|||
* It generates a **big, single** .ts file which has some problems; |
|||
* It get **too large** when your application grows. |
|||
* It doesn't fit into the **[modular](../../Module-Development-Basics) approach** of the ABP framework. |
|||
* It creates a bit **ugly code**. We want to have a clean code (just like if we write manually). |
|||
* It can not generate the same **method signature** declared in the server side (because swagger.json doesn't exactly reflect the method signature of the backend service). We've created an endpoint that exposes server side method contacts to allow clients generate a better aligned client proxies. |
|||
|
|||
ABP CLI `generate-proxies` command automatically generates the typescript client proxies by creating folders which separated by module names in the `src/app` folder. |
|||
Run the following command in the **root folder** of the angular application: |
|||
|
|||
```bash |
|||
abp generate-proxy |
|||
``` |
|||
|
|||
It only creates proxies only for your own application's services. It doesn't create proxies for the services of the application modules you're using (by default). There are several options. See the [CLI documentation](../../CLI). |
|||
|
|||
The files generated with the `--module all` option like below: |
|||
|
|||
 |
|||
|
|||
### Services |
|||
|
|||
Each generated service matches a back-end controller. The services methods call back-end APIs via [RestService](./Http-Requests#restservice). |
|||
|
|||
A variable named `apiName` (available as of v2.4) is defined in each service. `apiName` matches the module's RemoteServiceName. This variable passes to the `RestService` as a parameter at each request. If there is no microservice API defined in the environment, `RestService` uses the default. See [getting a specific API endpoint from application config](./Http-Requests#how-to-get-a-specific-api-endpoint-from-application-config) |
|||
|
|||
The `providedIn` property of the services is defined as `'root'`. Therefore no need to add a service as a provider to a module. You can use a service by injecting it into a constructor as shown below: |
|||
|
|||
```js |
|||
import { AbpApplicationConfigurationService } from '../app/shared/services'; |
|||
|
|||
//... |
|||
export class HomeComponent{ |
|||
constructor(private appConfigService: AbpApplicationConfigurationService) {} |
|||
|
|||
ngOnInit() { |
|||
this.appConfigService.get().subscribe() |
|||
} |
|||
} |
|||
``` |
|||
|
|||
The Angular compiler removes the services that have not been injected anywhere from the final output. See the [tree-shakable providers documentation](https://angular.io/guide/dependency-injection-providers#tree-shakable-providers). |
|||
|
|||
### Models |
|||
|
|||
The generated models match the DTOs in the back-end. Each model is generated as a class under the `src/app/*/shared/models` folder. |
|||
|
|||
There are a few [base classes](https://github.com/abpframework/abp/blob/dev/npm/ng-packs/packages/core/src/lib/models/dtos.ts) in the `@abp/ng.core` package. Some models extend these classes. |
|||
|
|||
A class instance can be created as shown below: |
|||
|
|||
```js |
|||
import { IdentityRoleCreateDto } from '../identity/shared/models'; |
|||
//... |
|||
const instance = new IdentityRoleCreateDto({name: 'Role 1', isDefault: false, isPublic: true}) |
|||
``` |
|||
|
|||
Initial values can optionally be passed to each class constructor. |
|||
|
|||
## What's Next? |
|||
|
|||
* [HTTP Requests](./Http-Requests) |
|||
|
After Width: | Height: | Size: 235 KiB |
@ -1,3 +1,469 @@ |
|||
# ASP.NET Core (MVC / Razor Pages) 用户界面自定义指南 |
|||
|
|||
TODO... |
|||
本文档解释了如何重写ASP.NET Core MVC / Razor Page 应用程序依赖[应用模块](../../Modules/Index.md)的用户界面. |
|||
|
|||
## 重写页面 |
|||
|
|||
本节介绍了[Razor 页面](https://docs.microsoft.com/zh-cn/aspnet/core/razor-pages/)开发,它是ASP.NET Core推荐的服务端渲染用户页面的方法. 预构建的模块通常使用Razor页面替代经典的MVC方式(下一节也介绍MVC模式). |
|||
|
|||
你通过有三种重写页面的需求: |
|||
|
|||
* 仅**重写页面模型**(C#)端执行其他逻辑,不更改UI. |
|||
* 仅**重写Razor页面**(.cshtml文件),不更改逻辑. |
|||
* **完全重写** 页面. |
|||
|
|||
### 重写页面模型 (C#) |
|||
|
|||
````csharp |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Identity; |
|||
using Volo.Abp.Identity.Web.Pages.Identity.Users; |
|||
|
|||
namespace Acme.BookStore.Web.Pages.Identity.Users |
|||
{ |
|||
[Dependency(ReplaceServices = true)] |
|||
[ExposeServices(typeof(EditModalModel))] |
|||
public class MyEditModalModel : EditModalModel |
|||
{ |
|||
public MyEditModalModel( |
|||
IIdentityUserAppService identityUserAppService, |
|||
IIdentityRoleAppService identityRoleAppService |
|||
) : base( |
|||
identityUserAppService, |
|||
identityRoleAppService) |
|||
{ |
|||
} |
|||
|
|||
public override async Task<IActionResult> OnPostAsync() |
|||
{ |
|||
//TODO: Additional logic |
|||
await base.OnPostAsync(); |
|||
//TODO: Additional logic |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* 这个类继承并替换 `EditModalModel` ,重写了 `OnPostAsync` 方法在基类代码的前后执行附加逻辑 |
|||
* 它使用 `ExposeServices` 和 `Dependency` attributes去替换这个类. |
|||
|
|||
### 重写Razor页面 (.CSHTML) |
|||
|
|||
使用[虚拟文件系统](../../Virtual-File-System.md)可以重写 `.cshtml` 文件(razor page, razor view, view component... 等.) |
|||
|
|||
虚拟文件系统允许我们将**资源嵌入到程序集中**. 通过这个方式,预构建的模块在Nuget包中定义了Razor页面. 当你依赖模块时,可以覆盖这个模块向虚拟文件系统添加的任何文件,包括页面/视图. |
|||
|
|||
#### 示例 |
|||
|
|||
这个示例重写了[账户模块](../../Modules/Account.md)定义的**登录页面**UI |
|||
|
|||
物理文件可以覆盖相同位置的嵌入文件. 账户模块在 `Pages/Account` 文件夹下定义了 `Login.cshtml` 文件. 所以你可以在同一路径下创建文件覆盖它: |
|||
 |
|||
|
|||
通常你想要拷贝模块的 `.cshtml` 原文件,然后进行需要的更改. 你可以在[这里](https://github.com/abpframework/abp/blob/dev/modules/account/src/Volo.Abp.Account.Web/Pages/Account/Login.cshtml)找到源文件. 不要拷贝 `Login.cshtml.cs` 文件,它是隐藏razor页面的代码,我们不希望覆盖它(见下节). |
|||
|
|||
这就够了,接下来你可以对文件内容做你想要的更改. |
|||
|
|||
### 完全重写Razo页面 |
|||
|
|||
也许你想要完全重写页面,Razor和页面相关的C#文件. |
|||
|
|||
在这种情况下; |
|||
|
|||
1. 像上面描述过的那术重写C#页面模型类,但不需要替换已存在的页面模型类. |
|||
2. 像上面描述过的那样重写Razor页面,并且更改@model指向新的页面模型 |
|||
|
|||
#### 示例 |
|||
|
|||
这个示例重写了[账户模块](../../Modules/Account.md)定义的**登录页面** |
|||
|
|||
创建一个继承自 `LoginModel`(定义在`Volo.Abp.Account.Web.Pages.Account`命名空间下)的页面模型类: |
|||
|
|||
````csharp |
|||
public class MyLoginModel : LoginModel |
|||
{ |
|||
public MyLoginModel( |
|||
IAuthenticationSchemeProvider schemeProvider, |
|||
IOptions<AbpAccountOptions> accountOptions |
|||
) : base( |
|||
schemeProvider, |
|||
accountOptions) |
|||
{ |
|||
|
|||
} |
|||
|
|||
public override Task<IActionResult> OnPostAsync(string action) |
|||
{ |
|||
//TODO: Add logic |
|||
return base.OnPostAsync(action); |
|||
} |
|||
|
|||
//TODO: Add new methods and properties... |
|||
} |
|||
```` |
|||
|
|||
如果需要,你可以重写任何方法或添加新的属性/方法 |
|||
|
|||
> 注意我们没有使用 `[Dependency(ReplaceServices = true)]` 或 `[ExposeServices(typeof(LoginModel))]`,因为我们不想替换依赖注入中已存在的类,我们定义了一个新的. |
|||
|
|||
拷贝 `Login.cshtml` 到你们解决方案,更改 **@model** 指定到 `MyLoginModel`: |
|||
|
|||
````xml |
|||
@page |
|||
... |
|||
@model Acme.BookStore.Web.Pages.Account.MyLoginModel |
|||
... |
|||
```` |
|||
|
|||
这就够了,接下来你可以做任何想要更改. |
|||
|
|||
#### 不使用继承替换页面模型 |
|||
|
|||
你不需要继承源页面模型类(像之前的示例). 你可以完全**重写实现**你自己的页面. 在这种事情下你可以从 `PageModel`,`AbpPageModel` 或任何你需要的合适的基类派生. |
|||
|
|||
## 重写视图组件 |
|||
|
|||
在ABP框架,预构建的模块和主题定义了一些**可重用的视图组件**. 这些视图组件可以像页面一样被替换. |
|||
|
|||
### 示例 |
|||
|
|||
下面是应用程序启动模板自带的 **基本主题** 的截图. |
|||
|
|||
 |
|||
|
|||
[基本主题](../../Themes/Basic.md) 为layout定义了一些视图组件. 例如上面带有红色矩形的突出显示区域称为 **Brand组件**, 你可能想添加自己的**自己的应用程序logo**来自定义此组件. 让我们来看看如何去做. |
|||
|
|||
首先创建你的logo并且放到你的web应用程序文件夹中,我们使用 `wwwroot/logos/bookstore-logo.png` 路径. 然后在 `Themes/Basic/Components/Brand` 文件夹下复制[Brand组件视图](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/Default.cshtml). 结果应该是类似下面的图片: |
|||
|
|||
 |
|||
|
|||
然后对 `Default.cshtml` 文件做你想要的更改. 例如内容可以是这样的: |
|||
|
|||
````xml |
|||
<a href="/"> |
|||
<img src="~/logos/bookstore-logo.png" width="250" height="60"/> |
|||
</a> |
|||
```` |
|||
|
|||
现在你可以运行应用程序看到结果: |
|||
|
|||
 |
|||
|
|||
如果你需要,你也可以仅使用依赖注入系统替换组件[背后的C#类代码](https://github.com/abpframework/abp/blob/dev/framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Brand/MainNavbarBrandViewComponent.cs) |
|||
|
|||
### 重写主题 |
|||
|
|||
正如上所解释的,你可以更改任何组件,layout或c#类. 参阅[主题文档]了解更多关于主题系统的信息. |
|||
|
|||
## 重写静态资源 |
|||
|
|||
重写模块的静态资源(像JavaScript,Css或图片文件)是很简单的. 只需要在解决方案的相同路径创建文件,虚拟文件系统会自动处理它. |
|||
|
|||
## 操作捆绑 |
|||
|
|||
[捆绑 & 压缩](Bundling-Minification.md) 系统提供了**动态可扩展的** 系统去创建**script**和**style**捆绑. 它允许你扩展和操作现有的包. |
|||
|
|||
### 示例: 添加全局CSS文件 |
|||
|
|||
例如APP框架定义了一个**全局样式捆绑**添加到所有的页面(事实上由主题添加layout). 让我们添加一个**自定义样式文件**到这个捆绑文件的最后,我们可以覆盖任何全局样式. |
|||
|
|||
创建在 `wwwroot` 文件夹下创建一个CSS文件 |
|||
|
|||
 |
|||
|
|||
在CSS文件中定义一些规则. 例如: |
|||
|
|||
````css |
|||
.card-title { |
|||
color: orange; |
|||
font-size: 2em; |
|||
text-decoration: underline; |
|||
} |
|||
|
|||
.btn-primary { |
|||
background-color: red; |
|||
} |
|||
```` |
|||
|
|||
然后在你的[模块](../../Module-Development-Basics.md) `ConfigureServices` 方法添加这个文件到标准的全局样式捆绑包: |
|||
|
|||
````csharp |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.StyleBundles.Configure( |
|||
StandardBundles.Styles.Global, //The bundle name! |
|||
bundleConfiguration => |
|||
{ |
|||
bundleConfiguration.AddFiles("/styles/my-global-styles.css"); |
|||
} |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
#### 全局脚本捆绑包 |
|||
|
|||
就像 `StandardBundles.Styles.Global` 一样,还有一个 `StandardBundles.Scripts.Global`,你可以添加文件或操作现有文件. |
|||
|
|||
### 示例: 操作捆绑包文件 |
|||
|
|||
上面的示例中添加了新文件到捆绑包. 如果你创建 **bundle contributor** 类则可以做到更多. 示例: |
|||
|
|||
````csharp |
|||
public class MyGlobalStyleBundleContributor : BundleContributor |
|||
{ |
|||
public override void ConfigureBundle(BundleConfigurationContext context) |
|||
{ |
|||
context.Files.Clear(); |
|||
context.Files.Add("/styles/my-global-styles.css"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
然后你可以添加这个contributor到已存在的捆绑中: |
|||
|
|||
````csharp |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.StyleBundles.Configure( |
|||
StandardBundles.Styles.Global, |
|||
bundleConfiguration => |
|||
{ |
|||
bundleConfiguration.AddContributors(typeof(MyGlobalStyleBundleContributor)); |
|||
} |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
示例中清除了所有的CSS文件,在现实中这并不是一个好主意,你可以找到某个特定的文件替换成你自己的文件. |
|||
|
|||
### 示例: 为特定页面添加JavaScript文件 |
|||
|
|||
上面的示例将全局包添加到布局中. 如果要在依赖模块中为特定页面定义添加CSS/JavaScript文件(或替换文件)怎么做? |
|||
|
|||
假设你想要用户进入身份模块的**角色管理**页面时运行**JavaScript代码**. |
|||
|
|||
首先在 `wwwroot`, `Pages` 或 `Views` 文件夹下创建一个标准的JavaScript文件(默认ABP支持这些文件夹下的静态文件). 根据约定我们推荐 `Pages/Identity/Roles` 文件夹: |
|||
|
|||
 |
|||
|
|||
该文件的内容很简单: |
|||
|
|||
````js |
|||
$(function() { |
|||
abp.log.info('My custom role script file has been loaded!'); |
|||
}); |
|||
```` |
|||
|
|||
然后将这个文件添加到角色管页面理捆绑包中: |
|||
|
|||
````csharp |
|||
Configure<AbpBundlingOptions>(options => |
|||
{ |
|||
options.ScriptBundles |
|||
.Configure( |
|||
typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName, |
|||
bundleConfig => |
|||
{ |
|||
bundleConfig.AddFiles("/Pages/Identity/Roles/my-role-script.js"); |
|||
}); |
|||
}); |
|||
```` |
|||
|
|||
`typeof(Volo.Abp.Identity.Web.Pages.Identity.Roles.IndexModel).FullName` 是获取角色管理页面捆绑包名称的安全方式: |
|||
|
|||
> 请注意并非每个页面都定义了这个页面的捆绑包. 它们仅在需要时定义. |
|||
|
|||
除了添加新的CSS/JavaScript文件到页面,你也可以以替换(通过捆绑包contributor)已存在. |
|||
|
|||
## 布局定制 |
|||
|
|||
布局由主题([参阅主题](Theming.md))定义设计. 它们不包含在下载的应用程序解决方案中. 通过这种方式你可以轻松的**更改**主题并获取新的功能. 你不能**直接更改**应用程序中的布局代码,除非你用自己的布局替换它(在下一部分中说明). |
|||
|
|||
有一些通用的方法可以**自定义布局**,将在下一节中介绍. |
|||
|
|||
### 菜单贡献者 |
|||
|
|||
ABP框架定义了两个**标准菜单**: |
|||
|
|||
 |
|||
|
|||
* `StandardMenus.Main`: 应用程序的主菜单. |
|||
* `StandardMenus.User`: 用户菜单 (通常在屏幕的右上方). |
|||
|
|||
显示菜单是主题的责任,但**菜单项**由模板和你的应用程序代码决定. 只需要实现 `IMenuContributor` 接口并在 `ConfigureMenuAsync` 方法操作菜单项. |
|||
|
|||
渲染菜单时需要执行菜单贡献者. **应用程序启动模板** 已经定义了菜单贡献者,所以你可以使用它. 参阅[导航菜单](Navigation-Menu.md)文档了解更多. |
|||
|
|||
### 工具栏贡献者 |
|||
|
|||
[工具栏系统](Toolbars.md)用于在用户界面定义 **工具栏** . 模块 (或你的应用程序)可以将 **项** 添加到工具栏, 随后主题将在**布局**上呈现工具栏. |
|||
|
|||
只有一个 **标准工具栏** (名称为 "Main" - 定义为常量: `StandardToolbars.Main`). 对于基本主题,按如下呈现: |
|||
|
|||
在上面的屏幕快照中,主工具栏添加了两个项目:语言开关组件和用户菜单. 你可以在此处添加自己的项. |
|||
|
|||
#### 示例: 添加通知图标 |
|||
|
|||
在这个示例中,我们会添加一个**通知(响铃)图标**到语言切换项的左侧. 工具栏的项项目是一个**视图组件**. 所以,在你的项目中创建一个新的视图组件: |
|||
|
|||
 |
|||
|
|||
**NotificationViewComponent.cs** |
|||
|
|||
````csharp |
|||
public class NotificationViewComponent : AbpViewComponent |
|||
{ |
|||
public async Task<IViewComponentResult> InvokeAsync() |
|||
{ |
|||
return View("/Pages/Shared/Components/Notification/Default.cshtml"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
**Default.cshtml** |
|||
|
|||
````xml |
|||
<div id="MainNotificationIcon" style="color: white; margin: 8px;"> |
|||
<i class="far fa-bell"></i> |
|||
</div> |
|||
```` |
|||
|
|||
现在,我们创建一个类实现 `IToolbarContributor` 接口: |
|||
|
|||
````csharp |
|||
public class MyToolbarContributor : IToolbarContributor |
|||
{ |
|||
public Task ConfigureToolbarAsync(IToolbarConfigurationContext context) |
|||
{ |
|||
if (context.Toolbar.Name == StandardToolbars.Main) |
|||
{ |
|||
context.Toolbar.Items |
|||
.Insert(0, new ToolbarItem(typeof(NotificationViewComponent))); |
|||
} |
|||
|
|||
return Task.CompletedTask; |
|||
} |
|||
} |
|||
```` |
|||
|
|||
这个类向 `Main` 工具栏的第一项添加了 `NotificationViewComponent`. |
|||
|
|||
最后你需要将这个贡献者添加到 `AbpToolbarOptions`,在你模块类的 `ConfigureServices` 方法: |
|||
|
|||
````csharp |
|||
Configure<AbpToolbarOptions>(options => |
|||
{ |
|||
options.Contributors.Add(new MyToolbarContributor()); |
|||
}); |
|||
```` |
|||
|
|||
这就够了,当你运行应用程序后会看到工具栏上的通知图标: |
|||
|
|||
 |
|||
|
|||
示例中的 `NotificationViewComponent` 返回没有任何数据的视图. 在实际场景中,你可能想**查询数据库**(或调用HTTP API)获取通知并传递给视图. 如果需要可以将 `JavaScript` 或 `CSS` 文件添加到工具栏的全局捆绑包中(如前所述). |
|||
|
|||
参阅[工具栏文档](Toolbars.md)了解更多关于工具栏系统. |
|||
|
|||
### 布局钩子 |
|||
|
|||
[布局钩子](Layout-Hooks.md) 系统允许你在布局页面的某些特定部分 **添加代码** . 所有主题的所有布局都应该实现这些钩子. 然后你可以将**视图组件**添加到钩子. |
|||
|
|||
#### 示例: 添加谷歌统计 |
|||
|
|||
假设你想要添加谷歌统计脚本到布局(将适用所有的页面). 首先在你的项目中**创建一个视图组件**: |
|||
|
|||
 |
|||
|
|||
**NotificationViewComponent.cs** |
|||
|
|||
````csharp |
|||
public class GoogleAnalyticsViewComponent : AbpViewComponent |
|||
{ |
|||
public IViewComponentResult Invoke() |
|||
{ |
|||
return View("/Pages/Shared/Components/GoogleAnalytics/Default.cshtml"); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
**Default.cshtml** |
|||
|
|||
````html |
|||
<script> |
|||
(function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){ |
|||
(i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o), |
|||
m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m) |
|||
})(window,document,'script','//www.google-analytics.com/analytics.js','ga'); |
|||
|
|||
ga('create', 'UA-xxxxxx-1', 'auto'); |
|||
ga('send', 'pageview'); |
|||
</script> |
|||
```` |
|||
|
|||
在你自己的代码中更改 `UA-xxxxxx-1` . |
|||
|
|||
然后你可以在你模块的 `ConfigureServices` 方法将这个组件添加到任何的钩子点: |
|||
|
|||
````csharp |
|||
Configure<AbpLayoutHookOptions>(options => |
|||
{ |
|||
options.Add( |
|||
LayoutHooks.Head.Last, //The hook name |
|||
typeof(GoogleAnalyticsViewComponent) //The component to add |
|||
); |
|||
}); |
|||
```` |
|||
|
|||
现在谷歌统计代码将在页面的 `head` 所为最后一项插入. 你(或你在使用的模块)可以将多个项添加到相同的钩子,它们都会添加到布局. |
|||
|
|||
在上面我们添加 `GoogleAnalyticsViewComponent` 到所有的布局,你可能只想添加到指定的布局: |
|||
|
|||
````csharp |
|||
Configure<AbpLayoutHookOptions>(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` 文件编写以上代码. |
|||
|
|||
@ -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; |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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}]"; |
|||
} |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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; } |
|||
} |
|||
} |
|||
@ -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>(); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
); |
|||
} |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,3 @@ |
|||
<Weavers xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="FodyWeavers.xsd"> |
|||
<ConfigureAwait ContinueOnCapturedContext="false" /> |
|||
</Weavers> |
|||
@ -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> |
|||
@ -0,0 +1,3 @@ |
|||
using System.Runtime.CompilerServices; |
|||
|
|||
[assembly: InternalsVisibleTo("Volo.Abp.ObjectExtending.Tests")] |
|||
@ -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,0 +1,9 @@ |
|||
using Volo.Abp.Modularity; |
|||
|
|||
namespace Volo.Abp.ObjectExtending |
|||
{ |
|||
public class AbpObjectExtendingModule : AbpModule |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -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>(); |
|||
} |
|||
} |
|||
} |
|||
@ -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!"); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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>(); |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -0,0 +1,9 @@ |
|||
using Volo.Abp.Testing; |
|||
|
|||
namespace Volo.Abp.ObjectExtending |
|||
{ |
|||
public abstract class AbpObjectExtendingTestBase : AbpIntegratedTest<AbpObjectExtendingTestModule> |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -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"); |
|||
}); |
|||
} |
|||
} |
|||
} |
|||
@ -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
|
|||
} |
|||
} |
|||
} |
|||
@ -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 |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.ObjectExtending.TestObjects |
|||
{ |
|||
public class ExtensibleTestPerson : ExtensibleObject |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,7 @@ |
|||
namespace Volo.Abp.ObjectExtending.TestObjects |
|||
{ |
|||
public class ExtensibleTestPersonDto : ExtensibleObject |
|||
{ |
|||
|
|||
} |
|||
} |
|||
@ -1,12 +0,0 @@ |
|||
using Volo.Abp.AspNetCore.Mvc; |
|||
|
|||
namespace VoloDocs.Web.Controllers |
|||
{ |
|||
public class HomeController : AbpController |
|||
{ |
|||
public void Index() |
|||
{ |
|||
|
|||
} |
|||
} |
|||
} |
|||
@ -1,29 +1,59 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.AspNetCore.Mvc; |
|||
using Microsoft.AspNetCore.Mvc.RazorPages; |
|||
using Microsoft.Extensions.Options; |
|||
using Volo.Docs; |
|||
using Volo.Docs.Projects; |
|||
|
|||
namespace VoloDocs.Web.Pages |
|||
{ |
|||
public class IndexModel : PageModel |
|||
{ |
|||
public IReadOnlyList<ProjectDto> Projects { get; set; } |
|||
|
|||
private readonly DocsUiOptions _urlUiOptions; |
|||
|
|||
public IndexModel(IOptions<DocsUiOptions> urlOptions) |
|||
private readonly IProjectAppService _projectAppService; |
|||
|
|||
public IndexModel(IOptions<DocsUiOptions> urlOptions, IProjectAppService projectAppService) |
|||
{ |
|||
_projectAppService = projectAppService; |
|||
_urlUiOptions = urlOptions.Value; |
|||
} |
|||
|
|||
public IActionResult OnGet() |
|||
public async Task<IActionResult> OnGetAsync() |
|||
{ |
|||
//TODO: Create HomeController & Index instead of Page. Otherwise, we have an empty Index.cshtml file.
|
|||
if (!_urlUiOptions.RoutePrefix.IsNullOrWhiteSpace()) |
|||
var projects = await _projectAppService.GetListAsync(); |
|||
|
|||
if (projects.Items.Count == 1) |
|||
{ |
|||
return Redirect("." + _urlUiOptions.RoutePrefix); |
|||
return await RedirectToProjectAsync(projects.Items.First()); |
|||
} |
|||
else if (projects.Items.Count > 1) |
|||
{ |
|||
Projects = projects.Items; |
|||
} |
|||
|
|||
return Page(); |
|||
} |
|||
|
|||
private async Task<IActionResult> RedirectToProjectAsync(ProjectDto project, string language = "en", string version = null) |
|||
{ |
|||
var path = GetUrlForProject(project, language, version); |
|||
return await Task.FromResult(Redirect(path)); |
|||
} |
|||
|
|||
//Eg: "/en/abp/latest"
|
|||
public string GetUrlForProject(ProjectDto project, string language = "en", string version = null) |
|||
{ |
|||
return "." + |
|||
_urlUiOptions.RoutePrefix.EnsureStartsWith('/').EnsureEndsWith('/') + |
|||
language.EnsureEndsWith('/') + |
|||
project.ShortName.EnsureEndsWith('/') + |
|||
(version ?? DocsAppConsts.Latest); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
|
|||
File diff suppressed because it is too large
@ -0,0 +1,7 @@ |
|||
namespace Volo.Docs |
|||
{ |
|||
public class DocsDomainConsts |
|||
{ |
|||
public static string LanguageConfigFileName = "docs-langs.json"; |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using System; |
|||
using Newtonsoft.Json; |
|||
|
|||
namespace Volo.Extensions |
|||
{ |
|||
public static class JsonConvertExtensions |
|||
{ |
|||
public static bool TryDeserializeObject<T>(string jsonContent, out T result) |
|||
{ |
|||
try |
|||
{ |
|||
result = JsonConvert.DeserializeObject<T>(jsonContent); |
|||
return true; |
|||
} |
|||
catch |
|||
{ |
|||
result = default; |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
File diff suppressed because one or more lines are too long
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue