mirror of https://github.com/abpframework/abp.git
177 changed files with 12360 additions and 1802 deletions
@ -0,0 +1,3 @@ |
|||
# Object Extensions |
|||
|
|||
TODO |
|||
@ -0,0 +1,3 @@ |
|||
## Getting Started With the Angular Application Template |
|||
|
|||
TODO... |
|||
@ -0,0 +1,6 @@ |
|||
# 启动模板入门 |
|||
|
|||
参阅下面的教程来学习如何开始使用的ABP框架预构建的应用程序启动模板: |
|||
|
|||
* [ASP.NET Core MVC/Razor页面模板入门](Getting-Started-AspNetCore-MVC-Template.md) |
|||
* [Angular UI模板入门](Getting-Started-Angular-Template.md) |
|||
@ -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,37 @@ |
|||
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, |
|||
definitionChecks |
|||
); |
|||
|
|||
return result; |
|||
}) |
|||
); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,37 @@ |
|||
using System.Text; |
|||
|
|||
namespace Volo.Abp.Text |
|||
{ |
|||
public class StringHelper |
|||
{ |
|||
/// <summary>
|
|||
/// Converts a byte[] to string without BOM (byte order mark).
|
|||
/// </summary>
|
|||
/// <param name="bytes">The byte[] to be converted to string</param>
|
|||
/// <param name="encoding">The encoding to get string. Default is UTF8</param>
|
|||
/// <returns></returns>
|
|||
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); |
|||
} |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
} |
|||
Some files were not shown because too many files changed in this diff
Loading…
Reference in new issue