mirror of https://github.com/abpframework/abp.git
committed by
GitHub
14 changed files with 839 additions and 23 deletions
@ -0,0 +1,22 @@ |
|||
# 应用程序配置端点 |
|||
|
|||
ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表: |
|||
|
|||
* [本地化](Localization.md)值, 支持应用程序的当前语言. |
|||
* 当前用户可用和已授予的[策略](Authorization.md)(权限). |
|||
* 当前用户的[设置](Settings.md)值. |
|||
* 关于[当前用户](CurrentUser.md)的信息 (如 id 和用户名). |
|||
* 关于当前[租户](Multi-Tenancy.md)的信息 (如 id 和名称). |
|||
* 当前用户的[时区](Timing.md)信息和应用程序的[时钟](Timing.md)类型. |
|||
|
|||
## HTTP API |
|||
|
|||
如果您导航到基于ABP框架的web应用程序或HTTP服务的 `/api/abp/application-configuration` URL, 你可以得到JSON对象形式配置. 该端点对于创建应用程序的客户端很有用. |
|||
|
|||
## Script |
|||
|
|||
对于ASP.NET Core MVC(剃刀页)应用程序,同样的配置值在JavaScript端也可用. `/Abp/ApplicationConfigurationScript` 是基于上述HTTP API自动生成的脚本的URL. |
|||
|
|||
参阅 [JavaScript API文档](../UI/AspNetCore/JavaScript-API/Index.md) 了解关于ASP.NET Core UI. |
|||
|
|||
其他UI类型提供相关平台的本地服务. 例如查看[Angular UI本地化文档](../UI/Angular/Localization.md)来学习如何使用这个端点公开的本地化值. |
|||
@ -1,3 +1,160 @@ |
|||
# Data Seeding |
|||
# 种子数据 |
|||
|
|||
TODO |
|||
## 介绍 |
|||
|
|||
使用数据库的某些应用程序(或模块),可能需要有一些**初始数据**才能够正常启动和运行. 例如**管理员用户**和角色必须在一开始就可用. 否则你就无法**登录**到应用程序创建新用户和角色. |
|||
|
|||
数据种子也可用于[测试](Testing.md)的目的,你的自动测试可以假定数据库中有一些可用的初始数据. |
|||
|
|||
### 为什么要有种子数据系统? |
|||
|
|||
尽管EF Core Data Seeding系统提供了一种方法,但它非常有限,不包括生产场景. 此外它仅适用于EF Core. |
|||
|
|||
ABP框架提供了种子数据系统; |
|||
|
|||
* **模块化**: 任何[模块](Module-Development-Basics.md)都可以无声地参与数据播种过程,而不相互了解和影响. 通过这种方式模块将种子化自己的初始数据. |
|||
* **数据库独立**: 它不仅适用于 EF Core, 也使用其他数据库提供程序(如 [MongoDB](MongoDB.md)). |
|||
* **生产准备**: 它解决了生产环境中的问题. 参见下面的*On Production*部分. |
|||
* **依赖注入**: 它充分利用了依赖项注入,你可以在播种初始数据时使用任何内部或外部服务. 实际上你可以做的不仅仅是数据播种. |
|||
|
|||
## IDataSeedContributor |
|||
|
|||
将数据种子化到数据库需要实现 `IDataSeedContributor` 接口. |
|||
|
|||
**示例: 如果没有图书,则向数据库播种一个初始图书** |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Data; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Guids; |
|||
|
|||
namespace Acme.BookStore |
|||
{ |
|||
public class BookStoreDataSeedContributor |
|||
: IDataSeedContributor, ITransientDependency |
|||
{ |
|||
private readonly IRepository<Book, Guid> _bookRepository; |
|||
private readonly IGuidGenerator _guidGenerator; |
|||
|
|||
public BookStoreDataSeedContributor( |
|||
IRepository<Book, Guid> bookRepository, |
|||
IGuidGenerator guidGenerator) |
|||
{ |
|||
_bookRepository = bookRepository; |
|||
_guidGenerator = guidGenerator; |
|||
} |
|||
|
|||
public async Task SeedAsync(DataSeedContext context) |
|||
{ |
|||
if (await _bookRepository.GetCountAsync() > 0) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
var book = new Book( |
|||
id: _guidGenerator.Create(), |
|||
name: "The Hitchhiker's Guide to the Galaxy", |
|||
type: BookType.ScienceFiction, |
|||
publishDate: new DateTime(1979, 10, 12), |
|||
price: 42 |
|||
); |
|||
|
|||
await _bookRepository.InsertAsync(book); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* `IDataSeedContributor` 定义了 `SeedAsync` 方法用于执行 **数据种子逻辑**. |
|||
* 通常**检查数据库**是否已经存在种子数据. |
|||
* 你可以**注入**服务,检查数据播种所需的任何逻辑. |
|||
|
|||
> 数据种子贡献者由ABP框架自动发现,并作为数据播种过程的一部分执行. |
|||
|
|||
### DataSeedContext |
|||
|
|||
如果你的应用程序是[多租户](Multi-Tenancy.md), `DataSeedContext` 包含 `TenantId`,因此你可以在插入数据或基于租户执行自定义逻辑时使用该值. |
|||
|
|||
`DataSeedContext` 还包含用于从 `IDataSeeder` 传递到种子提供者的name-value配置参数. |
|||
|
|||
## 模块化 |
|||
|
|||
一个应用程序可以具有多个种子数据贡献者(`IDataSeedContributor`)类. 任何可重用模块也可以实现此接口播种其自己的初始数据. |
|||
|
|||
例如[Identity模块](Modules/Identity.md)有一个种子数据贡献者,它创建一个管理角色和管理用户并分配所有权限. |
|||
|
|||
## IDataSeeder |
|||
|
|||
> 通常你不需要直接使用 `IDataSeeder` 服务,因为如果你从[应用程序启动模板](Startup-Templates/Application.md)开始,该服务已经完成. 但是建议阅读以了解种子数据系统背后的设计. |
|||
|
|||
`IDataSeeder` 是用于生成初始数据的主要服务. 使用它很容易; |
|||
|
|||
````csharp |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IDataSeeder _dataSeeder; |
|||
|
|||
public MyService(IDataSeeder dataSeeder) |
|||
{ |
|||
_dataSeeder = dataSeeder; |
|||
} |
|||
|
|||
public async Task FooAsync() |
|||
{ |
|||
await _dataSeeder.SeedAsync(); |
|||
} |
|||
} |
|||
```` |
|||
|
|||
你可以[注入](Dependency-Injection.md) `IDataSeeder` 并且在你需要时使用它初始化种子数据. 它内部调用 `IDataSeedContributor` 的实现去完成数据播种. |
|||
|
|||
可以将命名的配置参数发送到 `SeedAsync` 方法,如下所示: |
|||
|
|||
````csharp |
|||
await _dataSeeder.SeedAsync( |
|||
new DataSeedContext() |
|||
.WithProperty("MyProperty1", "MyValue1") |
|||
.WithProperty("MyProperty2", 42) |
|||
); |
|||
```` |
|||
|
|||
然后种子数据提供者可以通过前面解释的 `DataSeedContext` 访问这些属性. |
|||
|
|||
如果模块需要参数,应该在[模块文档](Modules/Index.md)中声明它. 例如[Identity Module](Modules/Identity.md)使用 `AdminEmail` 和 `AdminPassword` 参数,如果你提供了(默认使用默认值). |
|||
|
|||
### 在何处以及如何播种数据? |
|||
|
|||
重要的是要了解在何处以及如何执行 `IDataSeeder.SeedAsync()`. |
|||
|
|||
#### On Production |
|||
|
|||
[应用程序启动模板](Startup-Templates/Application.md)带有一个*YourProjectName***.DbMigrator** 项目(图中的Acme.BookStore.DbMigrator). 这是一个**控制台应用程序**,负责**迁移**数据库架构(关系数据库)和初始种子数据: |
|||
|
|||
 |
|||
|
|||
控制台应用程序已经为你正确配置,它甚至支持**多租户**场景,其中每个租户拥有自己的数据库(迁移和必须的数据库). |
|||
|
|||
当你将解决方案的**新版本部署到服务器**时,都需要运行这个DbMigrator应用程序. 它会迁移你的**数据库架构**(创建新的表/字段…)和播种正确运行解决方案的新版本所需的**新初始数据**. 然后就可以部署/启动实际的应用程序了. |
|||
|
|||
即使你使用的是MongoDB或其他NoSQL数据库(不需要进行架构迁移),也建议使用DbMigrator应用程序为你的数据添加种子或执行数据迁移. |
|||
|
|||
有这样一个单独的控制台应用程序有几个优点; |
|||
|
|||
* 你可以在更新你的应用程序**之前运行它**,所以你的应用程序可以在准备就绪的数据库上运行. |
|||
* 与本身初始化种子数据相比,你的应用程序**启动速度更快**. |
|||
* 应用程序可以在**集群环境**中正确运行(其中应用程序的多个实例并发运行). 在这种情况下如果在应用程序启动时播种数据就会有冲突. |
|||
|
|||
#### On Development |
|||
|
|||
我们建议以相同的方式进行开发. 每当你[创建数据库迁移](https://docs.microsoft.com/en-us/ef/ef6/modeling/code-first/migrations/)(例如使用EF Core `Add-Migration` 命令)或更改数据种子代码(稍后说明)时,请运行DbMigrator控制台应用程序. |
|||
|
|||
> 你可以使用EF Core继续执行标准的 `Update-Database` 命令,但是它不会初始化种子数据. |
|||
|
|||
#### On Testing |
|||
|
|||
你可能想为自动[测试](Testing.md)初始化数据种子, 这需要使用 `IDataSeeder.SeedAsync()`. 在[应用程序启动模板](Startup-Templates/Application.md)中,它在TestBase项目的*YourProjectName*TestBaseModule类的[OnApplicationInitialization](Module-Development-Basics.md)方法中完成. |
|||
|
|||
除了标准种子数据(也在生产中使用)之外,你可能还希望为自动测试添加其他种子数据. 你可以在测试项目中创建一个新的数据种子贡献者以处理更多数据. |
|||
@ -1,3 +1,280 @@ |
|||
## Data Transfer Objects |
|||
# 数据传输对象 |
|||
|
|||
TODO |
|||
## 介绍 |
|||
|
|||
**数据传输对象**(DTO)用于在**应用层**和**表示层**或其他类型的客户端之间传输数据. |
|||
|
|||
通常用**DTO**作为参数在表示层(可选)调用[应用服务](Application-Services.md). 它使用领域对象执行某些**特定的业务逻辑**,并(可选)将DTO返回到表示层.因此表示层与领域层完全**隔离**. |
|||
|
|||
### DTO的需求 |
|||
|
|||
> 如果你感觉你已经知道并确认使用DTO的好处,你可以**跳过这一节**. |
|||
|
|||
首先为每个应用程序服务方法创建DTO类可能被看作是一项冗长而耗时的工作. 但是如果正确使用它们,它们可以保存在应用程序. 为什么和如何> |
|||
|
|||
#### 领域层的抽象 |
|||
|
|||
DTO提供了一种从表示层**抽象领域对象**的有效方法. 实际上你的**层**被正确地分开了. 如果希望完全更改表示层,可以继续使用现有的应用程序层和领域层. 或者你可以重写领域层完全更改数据库架构,实体和O/RM框架,而无需更改表示层. 当然前提是应用程序服务的契约(方法签名和dto)保持不变. |
|||
|
|||
#### 数据隐藏 |
|||
|
|||
假设你有一个具有属性Id,名称,电子邮件地址和密码的 `User` 实体. 如果 `UserAppService` 的 `GetAllUsers()` 方法返回 `List<User>`,任何人都可以访问你所有用户的密码,即使你没有在屏幕上显示它. 这不仅关乎安全,还关乎数据隐藏. 应用程序服务应该只返回表示层(或客户端)所需要的内容,不多也不少. |
|||
|
|||
#### 序列化和延迟加载问题 |
|||
|
|||
当你将数据(一个对象)返回到表示层时,它很可能是序列化的. 例如在返回JSON的REST API中,你的对象将被序列化为JSON并发送给客户端. 在这方面将实体返回到表示层可能会有问题,尤其是在使用关系数据库和像Entity Framework Core这样的ORM提供者时. |
|||
|
|||
在真实的应用程序中你的实体可以相互引用. `User` 实体可以引用它的角色. 如果你想序列化用户,它的角色也必须是序列化的. `Role` 类可以有 `List <Permission>`,而 `Permission` 类可以有一个对 `PermissionGroup` 类的引用,依此类推...想象一下所有这些对象都被立即序列化了. 你可能会意外地序列化整个数据库! 同样,如果你的对象具有循环引用,则它们可能根本**不会**序列化成功. |
|||
|
|||
有什么解决方案? 将属性标记为 `NonSerialized` 吗? 不,你不知道什么时候应该序列化什么时候应该序列化. 一个应用程序服务方法可能需要它,而另一个则不需要. 在这种情况下返回安全,可序列化且经过特殊设计的DTO是一个不错的选择. |
|||
|
|||
几乎所有的O/RM框架都支持延迟加载. 此功能可在需要时从数据库加载实体. 假设 `User` 类具有对 `Role` 类的引用. 当你从数据库中获取用户时,`Role` 属性(或集合)不会被立即填充. 首次读取 `Role` 属性时,它是从数据库加载的. 因此如果将这样的实体返回到表示层,它将通过执行额外的查询从数据库中检索额外的实体. 如果序列化工具读取实体,它会递归读取所有属性,并且可以再次检索整个数据库(如果实体之间存在关系). |
|||
|
|||
如果在表示层中使用实体,可能会出现更多问题.**最好不要在表示层中引用领域/业务层程序集**. |
|||
|
|||
如果你确定使用DTO,我们可以继续讨论ABP框架提供的关于dto的建议. |
|||
|
|||
> ABP并不强迫你使用DTO,但是**强烈建议将DTO作为最佳实践**. |
|||
|
|||
## 标准接口和基类 |
|||
|
|||
DTO是一个没有依赖性的简单类,你可以用任何方式进行设计. 但是ABP引入了一些**接口**来确定命名**标准属性**和**基类**的**约定**,以免在声明**公共属性**时**重复工作**. |
|||
|
|||
**它们都不是必需的**,但是使用它们可以**简化和标准化**应用程序代码. |
|||
|
|||
### 实体相关DTO |
|||
|
|||
通常你需要创建与你的实体相对应的DTO,从而生成与实体类似的类. ABP框架在创建DTO时提供了一些基类来简化. |
|||
|
|||
#### EntityDto |
|||
|
|||
`IEntityDto<TKey>` 是一个只定义 `Id` 属性的简单接口. 你可以实现它或从 `EntityDto<TKey>` 继承. |
|||
|
|||
**Example:** |
|||
|
|||
````csharp |
|||
using System; |
|||
using Volo.Abp.Application.Dtos; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class ProductDto : EntityDto<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
//... |
|||
} |
|||
} |
|||
```` |
|||
|
|||
#### 审计DTO |
|||
|
|||
如果你的实体继承自被审计的实体类(或实现审计接口)可以使用以下基类来创建DTO: |
|||
|
|||
* `CreationAuditedEntityDto` |
|||
* `CreationAuditedEntityWithUserDto` |
|||
* `AuditedEntityDto` |
|||
* `AuditedEntityWithUserDto` |
|||
* `FullAuditedEntityDto` |
|||
* `FullAuditedEntityWithUserDto` |
|||
|
|||
#### 可扩展的DTO |
|||
|
|||
如果你想为你的DTO使用[对象扩展系统](Object-Extensions.md),你可以使用或继承以下DTO类: |
|||
|
|||
* `ExtensibleObject` 实现 `IHasExtraProperties` (其它类继承这个类). |
|||
* `ExtensibleEntityDto` |
|||
* `ExtensibleCreationAuditedEntityDto` |
|||
* `ExtensibleCreationAuditedEntityWithUserDto` |
|||
* `ExtensibleAuditedEntityDto` |
|||
* `ExtensibleAuditedEntityWithUserDto` |
|||
* `ExtensibleFullAuditedEntityDto` |
|||
* `ExtensibleFullAuditedEntityWithUserDto` |
|||
|
|||
### 列表结果 |
|||
|
|||
通常将DTO列表返回给客户端. `IListResult<T>` 接口和 `ListResultDto<T>` 类用于使其成为标准. |
|||
|
|||
`IListResult<T>` 接口的定义: |
|||
|
|||
````csharp |
|||
public interface IListResult<T> |
|||
{ |
|||
IReadOnlyList<T> Items { get; set; } |
|||
} |
|||
```` |
|||
|
|||
**示例: 返回产品列表** |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class ProductAppService : ApplicationService, IProductAppService |
|||
{ |
|||
private readonly IRepository<Product, Guid> _productRepository; |
|||
|
|||
public ProductAppService(IRepository<Product, Guid> productRepository) |
|||
{ |
|||
_productRepository = productRepository; |
|||
} |
|||
|
|||
public async Task<ListResultDto<ProductDto>> GetListAsync() |
|||
{ |
|||
//Get entities from the repository |
|||
List<Product> products = await _productRepository.GetListAsync(); |
|||
|
|||
//Map entities to DTOs |
|||
List<ProductDto> productDtos = |
|||
ObjectMapper.Map<List<Product>, List<ProductDto>>(products); |
|||
|
|||
//Return the result |
|||
return new ListResultDto<ProductDto>(productDtos); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
你可以简单地返回 `productDtos` 对象(并更改方法的返回类型), 这也没有错. 返回一个 `ListResultDto` 会使`List<ProductDto>` 做为 `Item` 属性包装到另一个对象中. 这具有一个优点:以后可以在不破坏远程客户端的情况下(当它们作为JSON结果获得值时)在返回值中添加更多属性. 在开发可重用的应用程序模块时特别建议使用这种方式. |
|||
|
|||
### 分页 & 排序列表结果 |
|||
|
|||
从服务器请求分页列表并将分页列表返回给客户端是更常见的情况. ABP定义了一些接口和类来对其进行标准化: |
|||
|
|||
#### 输入 (请求) 类型 |
|||
|
|||
以下接口和类用于标准化客户端发送的输入. |
|||
|
|||
* `ILimitedResultRequest`: 定义 `MaxResultCount`(`int`) 属性从服务器请求指定数量的结果. |
|||
* `IPagedResultRequest`: 继承自 `ILimitedResultRequest` (所以它具有 `MaxResultCount` 属性)并且定义了 `SkipCount` (`int`)用于请求服务器的分页结果时跳过计数. |
|||
* `ISortedResultRequest`: 定义 `Sorting` (`string`)属性以请求服务器的排序结果. 排序值可以是“名称”,"*Name*", "*Name DESC*", "*Name ASC, Age DESC*"... 等. |
|||
* `IPagedAndSortedResultRequest` 继承自 `IPagedResultRequest` 和 `ISortedResultRequest`,所以它有上述所有属性. |
|||
|
|||
建议你继承以下基类DTO类之一,而不是手动实现接口: |
|||
|
|||
* `LimitedResultRequestDto` 实现了 `ILimitedResultRequest`. |
|||
* `PagedResultRequestDto` 实现了 `IPagedResultRequest` (和继承自 `LimitedResultRequestDto`). |
|||
* `PagedAndSortedResultRequestDto` 实现了 `IPagedAndSortedResultRequest` (和继承自 `PagedResultRequestDto`). |
|||
|
|||
##### 最大返回数量 |
|||
|
|||
`LimitedResultRequestDto`(和其它固有的)通过以下规则限制和验证 `MaxResultCount`; |
|||
|
|||
* 如果客户端未设置 `MaxResultCount`,则假定为**10**(默认页面大小). 可以通过设置 `LimitedResultRequestDto.DefaultMaxResultCoun` t静态属性来更改此值. |
|||
* 如果客户端发送的 `MaxResultCount` 大于*1,000**,则会产生**验证错误**. 保护服务器免受滥用服务很重要. 如果需要可以通过设置 `LimitedResultRequestDto.MaxMaxResultCount` 静态属性来更改此值. |
|||
|
|||
建议在应用程序启动时设置静态属性,因为它们是静态的(全局). |
|||
|
|||
#### 输出 (响应) 类型 |
|||
|
|||
以下接口和类用于标准化发送给客户端的输出. |
|||
|
|||
* `IHasTotalCount` 定义 `TotalCount`(`long`)属性以在分页的情况下返回记录的总数. |
|||
* `IPagedResult<T>` 集成自 `IListResult<T>` 和 `IHasTotalCount`, 所以它有 `Items` 和 `TotalCount` 属性. |
|||
|
|||
建议你继承以下基类DTO类之一,而不是手动实现接口: |
|||
|
|||
* `PagedResultDto<T>` 继承自 `ListResultDto<T>` 和实现了 `IPagedResult<T>`. |
|||
|
|||
**示例: 从服务器请求分页和排序的结果并返回分页列表** |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Linq.Dynamic.Core; |
|||
using System.Threading.Tasks; |
|||
using Microsoft.EntityFrameworkCore; |
|||
using Volo.Abp.Application.Dtos; |
|||
using Volo.Abp.Application.Services; |
|||
using Volo.Abp.Domain.Repositories; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class ProductAppService : ApplicationService, IProductAppService |
|||
{ |
|||
private readonly IRepository<Product, Guid> _productRepository; |
|||
|
|||
public ProductAppService(IRepository<Product, Guid> productRepository) |
|||
{ |
|||
_productRepository = productRepository; |
|||
} |
|||
|
|||
public async Task<PagedResultDto<ProductDto>> GetListAsync( |
|||
PagedAndSortedResultRequestDto input) |
|||
{ |
|||
//Create the query |
|||
var query = _productRepository |
|||
.OrderBy(input.Sorting) |
|||
.Skip(input.SkipCount) |
|||
.Take(input.MaxResultCount); |
|||
|
|||
//Get total count from the repository |
|||
var totalCount = await query.CountAsync(); |
|||
|
|||
//Get entities from the repository |
|||
List<Product> products = await query.ToListAsync(); |
|||
|
|||
//Map entities to DTOs |
|||
List<ProductDto> productDtos = |
|||
ObjectMapper.Map<List<Product>, List<ProductDto>>(products); |
|||
|
|||
//Return the result |
|||
return new PagedResultDto<ProductDto>(totalCount, productDtos); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
ABP框架还定义了一种 `PageBy` 扩展方法(与`IPagedResultRequest`兼容),可用于代替 `Skip` + `Take`调用: |
|||
|
|||
````csharp |
|||
var query = _productRepository |
|||
.OrderBy(input.Sorting) |
|||
.PageBy(input); |
|||
```` |
|||
|
|||
> 注意我们将`Volo.Abp.EntityFrameworkCore`包添加到项目中以使用 `ToListAsync` 和 `CountAsync` 方法,因为它们不包含在标准LINQ中,而是由Entity Framework Core定义. |
|||
|
|||
如果你不了解示例代码,另请参阅[仓储文档](Repositories.md). |
|||
|
|||
## 相关话题 |
|||
|
|||
### 验证 |
|||
|
|||
[应用服务](Application-Services.md)方法,控制器操作,页面模型输入...的输入会自动验证. 你可以使用标准数据注释属性或自定义验证方法来执行验证. |
|||
|
|||
参阅[验证文档](Validation.md)了解更多. |
|||
|
|||
### 对象到对象的映射 |
|||
|
|||
创建与实体相关的DTO时通常需要映射这些对象. ABP提供了一个对象到对象的映射系统简化映射过程. 请参阅以下文档: |
|||
|
|||
* [对象到对象映射文档](Object-To-Object-Mapping.md)介绍了这些功能. |
|||
* [应用服务文档](Application-Services.md)提供了完整的示例. |
|||
|
|||
## 最佳实践 |
|||
|
|||
你可以自由设计DTO类,然而这里有一些你可能想要遵循的最佳实践和建议. |
|||
|
|||
### 共同原则 |
|||
|
|||
* DTO应该是**可序列化的**,因为它们通常是序列化和反序列化的(JSON或其他格式). 如果你有另一个带参数的构造函数,建议使用空(无参数)的公共构造函数. |
|||
* 除某些[验证](Validation.md)代码外,DTO**不应包含任何业务逻辑**. |
|||
* DTO不要继承实体,也**不要引用实体**. [应用程序启动模板](Startup-Templates/Application.md)已经通过分隔项目来阻止它. |
|||
* 如果你使用自动[对象到对象](Object-To-Object-Mapping.md)映射库,如AutoMapper,请启用**映射配置验证**以防止潜在的错误. |
|||
|
|||
### 输入DTO原则 |
|||
|
|||
* 只定义用例**所需的属性**. 不要包含不用于用例的属性,这样做会使开发人员感到困惑. |
|||
|
|||
* **不要在**不同的应用程序服务方法之间重用输入DTO. 因为不同的用例将需要和使用DTO的不同属性,从而导致某些属性在某些情况下没有使用,这使得理解和使用服务更加困难,并在将来导致潜在的错误. |
|||
|
|||
### 输出DTO原则 |
|||
|
|||
* 如果在所有情况下填充**所有属性**,就可以**重用输出DTO**. |
|||
@ -1,3 +1,111 @@ |
|||
## Guid 生成 |
|||
# GUID 生成 |
|||
|
|||
待添加 |
|||
GUID是数据库管理系统中使用的常见**主键类型**, ABP框架更喜欢GUID作为预构建[应用模块](Modules/Index.md)的主要对象. `ICurrentUser.Id` 属性([参见文档](CurrentUser.md))是GUID类型,这意味着ABP框架假定用户ID始终是GUID, |
|||
|
|||
## 为什么偏爱GUID? |
|||
|
|||
GUID有优缺点. 你可以在网上找到许多与此主题相关的文章,因此我们不再赘述,而是列出了最基本的优点: |
|||
|
|||
* 它可在所有数据库提供程序中**使用**. |
|||
* 它允许在客户端**确定主键**,而不需要通过**数据库往返**来生成Id值. 在向数据库插入新记录时,这可以提高性能并允许我们在与数据库交互之前知道PK. |
|||
* GUID是**自然唯一的**在以下情况下有一些优势; |
|||
* 你需要与**外部**系统集成, |
|||
* 你需要**拆分或合并**不同的表. |
|||
* 你正在创建**分布式系统** |
|||
* GUID是无法猜测的,因此在某些情况下与自动递增的Id值相比,GUID**更安全**. |
|||
|
|||
尽管存在一些缺点(只需在Web上搜索),但在设计ABP框架时我们发现这些优点更为重要. |
|||
|
|||
## IGuidGenerator |
|||
|
|||
GUID的最重要问题是**默认情况下它不是连续的**. 当你将GUID用作主键并将其设置为表的**聚集索引**(默认设置)时,这会在**插入时带来严重的性能问题**(因为插入新记录可能需要对现有记录进行重新排序). |
|||
|
|||
所以,**永远不要为你的实体使用 `Guid.NewGuid()` 创建ID**!. |
|||
|
|||
这个问题的一个好的解决方案是生成**连续的GUID**,由ABP框架提供的开箱即用的. `IGuidGenerator` 服务创建顺序的GUID(默认由 `SequentialGuidGenerator` 实现). 当需要手动设置[实体](Entities.md)的Id时,请使用 `IGuidGenerator.Create()`. |
|||
|
|||
**示例: 具有GUID主键的实体并创建该实体** |
|||
|
|||
假设你有一个具有 `Guid` 主键的 `Product` [实体](Entities.md): |
|||
|
|||
````csharp |
|||
using System; |
|||
using Volo.Abp.Domain.Entities; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class Product : AggregateRoot<Guid> |
|||
{ |
|||
public string Name { get; set; } |
|||
|
|||
private Product() { /* This constructor is used by the ORM/database provider */ } |
|||
|
|||
public Product(Guid id, string name) |
|||
: base(id) |
|||
{ |
|||
Name = name; |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
然后你想要创建一个产品: |
|||
|
|||
````csharp |
|||
using System; |
|||
using System.Threading.Tasks; |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Domain.Repositories; |
|||
using Volo.Abp.Guids; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class MyProductService : ITransientDependency |
|||
{ |
|||
private readonly IRepository<Product, Guid> _productRepository; |
|||
private readonly IGuidGenerator _guidGenerator; |
|||
|
|||
public MyProductService( |
|||
IRepository<Product, Guid> productRepository, |
|||
IGuidGenerator guidGenerator) |
|||
{ |
|||
_productRepository = productRepository; |
|||
_guidGenerator = guidGenerator; |
|||
} |
|||
|
|||
public async Task CreateAsync(string productName) |
|||
{ |
|||
var product = new Product(_guidGenerator.Create(), productName); |
|||
|
|||
await _productRepository.InsertAsync(product); |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
该服务将 `IGuidGenerator` 注入构造函数中. 如果你的类是[应用服务](Application-Services.md)或派生自其他基类之一,可以直接使用 `GuidGenerator` 基类属性,该属性是预先注入的 `IGuidGenerator` 实例. |
|||
|
|||
## Options |
|||
|
|||
### AbpSequentialGuidGeneratorOptions |
|||
|
|||
`AbpSequentialGuidGeneratorOptions` 是用于配置顺序生成GUID的[选项类](Options.md). 它只有一个属性: |
|||
|
|||
* `DefaultSequentialGuidType` (`SequentialGuidType` 类型的枚举): 生成GUID值时使用的策略. |
|||
|
|||
数据库提供程序在处理GUID时的行为有所不同,你应根据数据库提供程序进行设置. `SequentialGuidType` 有以下枚举成员: |
|||
|
|||
* `SequentialAtEnd` (**default**) 用于[SQL Server](Entity-Framework-Core.md). |
|||
* `SequentialAsString` 用于[MySQL](Entity-Framework-Core-MySQL.md)和[PostgreSQL](Entity-Framework-Core-PostgreSQL.md). |
|||
* `SequentialAsBinary` 用于[Oracle](Entity-Framework-Core-Oracle.md). |
|||
|
|||
在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法配置选项,如下: |
|||
|
|||
````csharp |
|||
Configure<AbpSequentialGuidGeneratorOptions>(options => |
|||
{ |
|||
options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary; |
|||
}); |
|||
```` |
|||
|
|||
> EF Core[集成包](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS)已为相关的DBMS设置相应的值. 如果你正在使用这些集成包,在大多数情况下则无需设置此选项. |
|||
|
|||
@ -0,0 +1,113 @@ |
|||
# 时钟 |
|||
|
|||
使用时间和[时区](https://en.wikipedia.org/wiki/Time_zone)总是很棘手,尤其是当你需要构建供**不同时区**的用户使用的**全局系统**时. |
|||
|
|||
ABP提供了一个基本的基础结构,使其变得容易并在可能的情况下自动进行处理. 本文档涵盖了与时间和时区相关的ABP框架服务和系统. |
|||
|
|||
> 如果你正在创建在单个时区区域运行的本地应用程序,则可能不需要这些系统. 但也建议使用本文中介绍的 `IClock` 服务. |
|||
|
|||
## IClock |
|||
|
|||
`DateTime.Now` 返回带有**服务器本地日期和时间**的 `DateTime` 对象. `DateTime` 对象**不存储时区信息**. 因此你无法知道此对象中存储的**绝对日期和时间**. 你只能做一些**假设**,例如假设它是在UTC+05时区创建的. 当你此值保存到数据库中并稍后读取,或发送到**不同时区**的客户端时,事情就变得特别复杂. |
|||
|
|||
解决此问题的一种方法是始终使用 `DateTime.UtcNow` 并将所有 `DateTime` 对象假定为UTC时间. 在这种情况下你可以在需要时将其转换为目标客户端的时区. |
|||
|
|||
`IClock` 在获取当前时间的同时提供了一种抽象,你可以在应用程序中的单个点上控制日期时间的类型(UTC或本地时间). |
|||
|
|||
**示例: 获取当前时间** |
|||
|
|||
````csharp |
|||
using Volo.Abp.DependencyInjection; |
|||
using Volo.Abp.Timing; |
|||
|
|||
namespace AbpDemo |
|||
{ |
|||
public class MyService : ITransientDependency |
|||
{ |
|||
private readonly IClock _clock; |
|||
|
|||
public MyService(IClock clock) |
|||
{ |
|||
_clock = clock; |
|||
} |
|||
|
|||
public void Foo() |
|||
{ |
|||
//Get the current time! |
|||
var now = _clock.Now; |
|||
} |
|||
} |
|||
} |
|||
```` |
|||
|
|||
* 当你需要获取当前时间时注入 `IClock` 服务. 常用的服务基类(如ApplicationService)已经注入并且做为基类属性提供,所以你可以直接使用 `Clock`. |
|||
* 使用 `Now` 属性获取当前时间. |
|||
|
|||
> 在大多数情况下 `IClock` 是你需要在应用程序中了解和使用的唯一服务. |
|||
|
|||
### Clock 选项 |
|||
|
|||
`AbpClockOptions` 是用于设置时钟种类的[选项](Options.md)类. |
|||
|
|||
**示例: 使用 UTC Clock** |
|||
|
|||
````csharp |
|||
Configure<AbpClockOptions>(options => |
|||
{ |
|||
options.Kind = DateTimeKind.Utc; |
|||
}); |
|||
```` |
|||
|
|||
在你的[模块](Module-Development-Basics.md)的 `ConfigureServices` 方法添加以上内容. |
|||
|
|||
> 默认 `Kind` 是 `Unspecified`,实际上使时钟不存在. 如果要利用Clock系统,要么使用 `Utc` 或 `Local`. |
|||
|
|||
### DateTime 标准化 |
|||
|
|||
`IClock` 的其他重要功能是规范化 `DateTime` 对象. |
|||
|
|||
**示例用法 :** |
|||
|
|||
````csharp |
|||
DateTime dateTime = ...; //Get from somewhere |
|||
var normalizedDateTime = Clock.Normalize(dateTime) |
|||
```` |
|||
|
|||
`Normalize` 方法的工作原理如下: |
|||
|
|||
* 如果当前时钟为UTC,并且给定的 `DateTime` 为本地时间,将给定的 `DateTime` 转换为UTC(通过使用 `DateTime.ToUniversalTime()` 方法). |
|||
* 如果当前时钟是本地的,并且给定的 `DateTime` 是UTC,将给定的 `DateTime` 转换为本地时间(通过使用 `DateTime.ToUniversalTime()` 方法). |
|||
* 如果未指定给定的 `DateTime` 的 `Kind`,将给定的 `DateTime` 的 `Kind`(使用 `DateTime.SpecifyKind(...)` 方法)设置为当前时钟的 `Kind`. |
|||
|
|||
当获取的 `DateTime` 不是由 `IClock` 创建且可能与当前Clock类型不兼容的时候,ABP框架会使用 `Normalize` 方法. 例如; |
|||
|
|||
* ASP.NET Core MVC模型绑定中的 `DateTime` 类型绑定. |
|||
* 通过[Entity Framework Core](Entity-Framework-Core.md)将数据保存到数据库或从数据库读取数据. |
|||
* 在[JSON反序列化](Json.md)上使用 `DateTime` 对象. |
|||
|
|||
#### DisableDateTimeNormalization Attribute |
|||
|
|||
`DisableDateTimeNormalization` attribute可用于禁用所需类或属性的规范化操作. |
|||
|
|||
### 其他 IClock 属性 |
|||
|
|||
除了 `Now`, `IClock` 服务还具有以下属性: |
|||
|
|||
* `Kind`: 返回当前使用的时钟类型(`DateTimeKind.Utc`, `DateTimeKind.Local` 或 `DateTimeKind.Unspecified`)的 `DateTimeKind`. |
|||
* `SupportsMultipleTimezone`: 如果当前时间是UTC,则返回 `true`. |
|||
|
|||
## 时区 |
|||
|
|||
本节介绍与管理时区有关的ABP框架基础结构 |
|||
|
|||
### 时区设置 |
|||
|
|||
ABP框架定义了一个名为 `Abp.Timing.Timezone` 的**设置**,可用于为应用程序的用户,[租户](Multi-Tenancy.md)或全局设置和获取时区. 默认值为 `UTC`. |
|||
|
|||
参阅[设置系统]了解更多关于设置系统. |
|||
|
|||
### ITimezoneProvider |
|||
|
|||
`ITimezoneProvider` 是一个服务,可将[Windows时区ID](https://support.microsoft.com/en-us/help/973627/microsoft-time-zone-index-values)值简单转换为[Iana时区名称](https://www.iana.org/time-zones)值,反之亦然. 它还提供了获取这些时区列表与获取具有给定名称的 `TimeZoneInfo` 的方法. |
|||
|
|||
它已使用[TimeZoneConverter](https://github.com/mj1856/TimeZoneConverter)库实现. |
|||
@ -0,0 +1,23 @@ |
|||
# JavaScript API |
|||
|
|||
ABP为ASP.NET Core MVC / Razor页面应用程序提供了一些执行客户端常见需求的JavaScrpt Api. |
|||
|
|||
## APIs |
|||
|
|||
* abp.ajax |
|||
* [abp.auth] |
|||
* abp.currentUser |
|||
* abp.dom |
|||
* abp.event |
|||
* abp.features |
|||
* abp.localization |
|||
* abp.log |
|||
* abp.ModalManager |
|||
* abp.notify |
|||
* abp.security |
|||
* abp.setting |
|||
* abp.ui |
|||
* abp.utils |
|||
* abp.ResourceLoader |
|||
* abp.WidgetManager |
|||
* Other APIs |
|||
@ -0,0 +1,37 @@ |
|||
# 按钮组 |
|||
|
|||
## 介绍 |
|||
|
|||
`abp-button-group` 是创建分组按钮的主要元素. |
|||
|
|||
基本用法: |
|||
|
|||
````csharp |
|||
<abp-button-group> |
|||
<abp-button button-type="Secondary">Left</abp-button> |
|||
<abp-button button-type="Secondary">Middle</abp-button> |
|||
<abp-button button-type="Secondary">Right</abp-button> |
|||
</abp-button-group> |
|||
```` |
|||
|
|||
## Demo |
|||
|
|||
参阅[按钮组demo页面](https://bootstrap-taghelpers.abp.io/Components/Button-groups)查看示例. |
|||
|
|||
## Attributes |
|||
|
|||
### direction |
|||
|
|||
按钮的方向. 应为以下值之一: |
|||
|
|||
* `Horizontal` (默认值) |
|||
* `Vertical` |
|||
|
|||
### size |
|||
|
|||
组中按钮的大小. 应为以下值之一: |
|||
|
|||
* `Default` (默认值) |
|||
* `Small` |
|||
* `Medium` |
|||
* `Large` |
|||
@ -0,0 +1,71 @@ |
|||
# 轮播 |
|||
|
|||
## 介绍 |
|||
|
|||
`abp-carousel` 是abp标签轮播元素 |
|||
|
|||
基本用法: |
|||
|
|||
````csharp |
|||
<abp-carousel> |
|||
<abp-carousel-item src=""></abp-carousel-item> |
|||
<abp-carousel-item src=""></abp-carousel-item> |
|||
<abp-carousel-item src=""></abp-carousel-item> |
|||
</abp-carousel> |
|||
```` |
|||
|
|||
## Demo |
|||
|
|||
参阅[轮播demo页面](https://bootstrap-taghelpers.abp.io/Components/Carousel)查看示例. |
|||
|
|||
## Attributes |
|||
|
|||
### id |
|||
|
|||
轮播的ID. 如果未设置则会生成一个ID. |
|||
|
|||
### controls |
|||
|
|||
用于启用轮播上的控件(previous和next按钮). 应为以下值之一: |
|||
|
|||
* `false` |
|||
* `true` |
|||
|
|||
### indicators |
|||
|
|||
启用轮播指标. 应为以下值之一: |
|||
|
|||
* `false` |
|||
* `true` |
|||
|
|||
### crossfade |
|||
|
|||
用于启用淡入淡出动画而不是在轮播上滑动. 应为以下值之一: |
|||
|
|||
* `false` |
|||
* `true` |
|||
|
|||
## abp-carousel-item Attributes |
|||
|
|||
### caption-title |
|||
|
|||
设置轮播项的标题 |
|||
|
|||
### caption |
|||
|
|||
设置轮播项的说明. |
|||
|
|||
### src |
|||
|
|||
链接值设置显示在轮播项上的图像的来源. |
|||
|
|||
### active |
|||
|
|||
设置活动轮播项. 应为以下值之一: |
|||
|
|||
* `false` |
|||
* `true` |
|||
|
|||
### alt |
|||
|
|||
当无法显示图像时,该值设置轮播项目图像的替代文本. |
|||
Loading…
Reference in new issue