mirror of https://github.com/abpframework/abp
parent
1e128f787f
commit
f1bbd7c24b
@ -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,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