From f1bbd7c24bd536acf8062245079c4aca1b818adb Mon Sep 17 00:00:00 2001 From: "liangshiw@outlook.com" Date: Sat, 13 Jun 2020 13:42:06 +0800 Subject: [PATCH] Translate document --- docs/en/Data-Seeding.md | 2 +- docs/zh-Hans/API/Application-Configuration.md | 22 ++ docs/zh-Hans/Application-Services.md | 2 +- docs/zh-Hans/Blob-Storing-Database.md | 2 + docs/zh-Hans/Data-Seeding.md | 161 +++++++++- docs/zh-Hans/Data-Transfer-Objects.md | 281 +++++++++++++++++- docs/zh-Hans/Domain-Driven-Design.md | 2 +- docs/zh-Hans/Entities.md | 6 +- docs/zh-Hans/Guid-Generation.md | 112 ++++++- docs/zh-Hans/Timing.md | 113 +++++++ .../UI/AspNetCore/JavaScript-API/Index.md | 23 ++ .../AspNetCore/Tag-Helpers/Button-groups.md | 37 +++ .../UI/AspNetCore/Tag-Helpers/Carousel.md | 71 +++++ docs/zh-Hans/docs-nav.json | 28 +- 14 files changed, 839 insertions(+), 23 deletions(-) create mode 100644 docs/zh-Hans/API/Application-Configuration.md create mode 100644 docs/zh-Hans/Timing.md create mode 100644 docs/zh-Hans/UI/AspNetCore/JavaScript-API/Index.md create mode 100644 docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Button-groups.md create mode 100644 docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Carousel.md diff --git a/docs/en/Data-Seeding.md b/docs/en/Data-Seeding.md index d6236da9d9..8827fe96c0 100644 --- a/docs/en/Data-Seeding.md +++ b/docs/en/Data-Seeding.md @@ -70,7 +70,7 @@ namespace Acme.BookStore * `IDataSeedContributor` defines the `SeedAsync` method to execute the **data seed logic**. * It is typical to **check database** if the seeding data is already present. -* You can **inject** and service and perform any logic needed to seed the data. +* You can **inject** service and perform any logic needed to seed the data. > Data seed contributors are automatically discovered by the ABP Framework and executed as a part of the data seed process. diff --git a/docs/zh-Hans/API/Application-Configuration.md b/docs/zh-Hans/API/Application-Configuration.md new file mode 100644 index 0000000000..ba062d9cef --- /dev/null +++ b/docs/zh-Hans/API/Application-Configuration.md @@ -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)来学习如何使用这个端点公开的本地化值. \ No newline at end of file diff --git a/docs/zh-Hans/Application-Services.md b/docs/zh-Hans/Application-Services.md index d0e29c2881..6bb18daa37 100644 --- a/docs/zh-Hans/Application-Services.md +++ b/docs/zh-Hans/Application-Services.md @@ -2,7 +2,7 @@ 应用服务实现应用程序的**用例**, 将**领域层逻辑公开给表示层**. -从表示层(可选)调用应用服务,**DTO (数据传对象)** 作为参数. 返回(可选)DTO给表示层. +从表示层(可选)调用应用服务,**DTO ([数据传对象](Data-Transfer-Objects.md))** 作为参数. 返回(可选)DTO给表示层. ## 示例 diff --git a/docs/zh-Hans/Blob-Storing-Database.md b/docs/zh-Hans/Blob-Storing-Database.md index 653aa9f904..e45c79d179 100644 --- a/docs/zh-Hans/Blob-Storing-Database.md +++ b/docs/zh-Hans/Blob-Storing-Database.md @@ -53,6 +53,8 @@ abp add-module Volo.Abp.BlobStoring.Database ### 配置容器 +如果只使用数据库存储提供程序,则不需要手动配置,因为它是自动完成的. 如果使用多个存储提供程序,可能需要对其进行配置. + 如同[BLOB存储文档](Blob-Storing.md)所述,配置是在[模块](Module-Development-Basics.md)类的 `ConfigureServices` 方法完成的. **示例: 配置为默认使用数据库系统存储提供程序** diff --git a/docs/zh-Hans/Data-Seeding.md b/docs/zh-Hans/Data-Seeding.md index 0b424e7e0d..68f6fc1e73 100644 --- a/docs/zh-Hans/Data-Seeding.md +++ b/docs/zh-Hans/Data-Seeding.md @@ -1,3 +1,160 @@ -# Data Seeding +# 种子数据 -TODO \ No newline at end of file +## 介绍 + +使用数据库的某些应用程序(或模块),可能需要有一些**初始数据**才能​​够正常启动和运行. 例如**管理员用户**和角色必须在一开始就可用. 否则你就无法**登录**到应用程序创建新用户和角色. + +数据种子也可用于[测试](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 _bookRepository; + private readonly IGuidGenerator _guidGenerator; + + public BookStoreDataSeedContributor( + IRepository 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). 这是一个**控制台应用程序**,负责**迁移**数据库架构(关系数据库)和初始种子数据: + +![bookstore-visual-studio-solution-v3](images/bookstore-visual-studio-solution-v3.png) + +控制台应用程序已经为你正确配置,它甚至支持**多租户**场景,其中每个租户拥有自己的数据库(迁移和必须的数据库). + +当你将解决方案的**新版本部署到服务器**时,都需要运行这个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)方法中完成. + +除了标准种子数据(也在生产中使用)之外,你可能还希望为自动测试添加其他种子数据. 你可以在测试项目中创建一个新的数据种子贡献者以处理更多数据. \ No newline at end of file diff --git a/docs/zh-Hans/Data-Transfer-Objects.md b/docs/zh-Hans/Data-Transfer-Objects.md index b0d80246f7..eb844e7ea7 100644 --- a/docs/zh-Hans/Data-Transfer-Objects.md +++ b/docs/zh-Hans/Data-Transfer-Objects.md @@ -1,3 +1,280 @@ -## Data Transfer Objects +# 数据传输对象 -TODO \ No newline at end of file +## 介绍 + +**数据传输对象**(DTO)用于在**应用层**和**表示层**或其他类型的客户端之间传输数据. + +通常用**DTO**作为参数在表示层(可选)调用[应用服务](Application-Services.md). 它使用领域对象执行某些**特定的业务逻辑**,并(可选)将DTO返回到表示层.因此表示层与领域层完全**隔离**. + +### DTO的需求 + +> 如果你感觉你已经知道并确认使用DTO的好处,你可以**跳过这一节**. + +首先为每个应用程序服务方法创建DTO类可能被看作是一项冗长而耗时的工作. 但是如果正确使用它们,它们可以保存在应用程序. 为什么和如何> + +#### 领域层的抽象 + +DTO提供了一种从表示层**抽象领域对象**的有效方法. 实际上你的**层**被正确地分开了. 如果希望完全更改表示层,可以继续使用现有的应用程序层和领域层. 或者你可以重写领域层完全更改数据库架构,实体和O/RM框架,而无需更改表示层. 当然前提是应用程序服务的契约(方法签名和dto)保持不变. + +#### 数据隐藏 + +假设你有一个具有属性Id,名称,电子邮件地址和密码的 `User` 实体. 如果 `UserAppService` 的 `GetAllUsers()` 方法返回 `List`,任何人都可以访问你所有用户的密码,即使你没有在屏幕上显示它. 这不仅关乎安全,还关乎数据隐藏. 应用程序服务应该只返回表示层(或客户端)所需要的内容,不多也不少. + +#### 序列化和延迟加载问题 + +当你将数据(一个对象)返回到表示层时,它很可能是序列化的. 例如在返回JSON的REST API中,你的对象将被序列化为JSON并发送给客户端. 在这方面将实体返回到表示层可能会有问题,尤其是在使用关系数据库和像Entity Framework Core这样的ORM提供者时. + +在真实的应用程序中你的实体可以相互引用. `User` 实体可以引用它的角色. 如果你想序列化用户,它的角色也必须是序列化的. `Role` 类可以有 `List `,而 `Permission` 类可以有一个对 `PermissionGroup` 类的引用,依此类推...想象一下所有这些对象都被立即序列化了. 你可能会意外地序列化整个数据库! 同样,如果你的对象具有循环引用,则它们可能根本**不会**序列化成功. + +有什么解决方案? 将属性标记为 `NonSerialized` 吗? 不,你不知道什么时候应该序列化什么时候应该序列化. 一个应用程序服务方法可能需要它,而另一个则不需要. 在这种情况下返回安全,可序列化且经过特殊设计的DTO是一个不错的选择. + +几乎所有的O/RM框架都支持延迟加载. 此功能可在需要时从数据库加载实体. 假设 `User` 类具有对 `Role` 类的引用. 当你从数据库中获取用户时,`Role` 属性(或集合)不会被立即填充. 首次读取 `Role` 属性时,它是从数据库加载的. 因此如果将这样的实体返回到表示层,它将通过执行额外的查询从数据库中检索额外的实体. 如果序列化工具读取实体,它会递归读取所有属性,并且可以再次检索整个数据库(如果实体之间存在关系). + +如果在表示层中使用实体,可能会出现更多问题.**最好不要在表示层中引用领域/业务层程序集**. + +如果你确定使用DTO,我们可以继续讨论ABP框架提供的关于dto的建议. + +> ABP并不强迫你使用DTO,但是**强烈建议将DTO作为最佳实践**. + +## 标准接口和基类 + +DTO是一个没有依赖性的简单类,你可以用任何方式进行设计. 但是ABP引入了一些**接口**来确定命名**标准属性**和**基类**的**约定**,以免在声明**公共属性**时**重复工作**. + +**它们都不是必需的**,但是使用它们可以**简化和标准化**应用程序代码. + +### 实体相关DTO + +通常你需要创建与你的实体相对应的DTO,从而生成与实体类似的类. ABP框架在创建DTO时提供了一些基类来简化. + +#### EntityDto + +`IEntityDto` 是一个只定义 `Id` 属性的简单接口. 你可以实现它或从 `EntityDto` 继承. + +**Example:** + +````csharp +using System; +using Volo.Abp.Application.Dtos; + +namespace AbpDemo +{ + public class ProductDto : EntityDto + { + 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` 接口和 `ListResultDto` 类用于使其成为标准. + +`IListResult` 接口的定义: + +````csharp +public interface IListResult +{ + IReadOnlyList 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 _productRepository; + + public ProductAppService(IRepository productRepository) + { + _productRepository = productRepository; + } + + public async Task> GetListAsync() + { + //Get entities from the repository + List products = await _productRepository.GetListAsync(); + + //Map entities to DTOs + List productDtos = + ObjectMapper.Map, List>(products); + + //Return the result + return new ListResultDto(productDtos); + } + } +} +```` + +你可以简单地返回 `productDtos` 对象(并更改方法的返回类型), 这也没有错. 返回一个 `ListResultDto` 会使`List` 做为 `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` 集成自 `IListResult` 和 `IHasTotalCount`, 所以它有 `Items` 和 `TotalCount` 属性. + +建议你继承以下基类DTO类之一,而不是手动实现接口: + +* `PagedResultDto` 继承自 `ListResultDto` 和实现了 `IPagedResult`. + +**示例: 从服务器请求分页和排序的结果并返回分页列表** + +````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 _productRepository; + + public ProductAppService(IRepository productRepository) + { + _productRepository = productRepository; + } + + public async Task> 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 products = await query.ToListAsync(); + + //Map entities to DTOs + List productDtos = + ObjectMapper.Map, List>(products); + + //Return the result + return new PagedResultDto(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**. \ No newline at end of file diff --git a/docs/zh-Hans/Domain-Driven-Design.md b/docs/zh-Hans/Domain-Driven-Design.md index 2197d35f50..d088f7596b 100644 --- a/docs/zh-Hans/Domain-Driven-Design.md +++ b/docs/zh-Hans/Domain-Driven-Design.md @@ -2,7 +2,7 @@ ## 什么是DDD? -ABP框架提供了**基础设施**使基于**DDD**的开发更易实现. DDD在[维基百科中的定义](https://zh.wikipedia.org/wiki/%E5%9F%9F%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91)如下: +ABP框架提供了**基础设施**使基于**领域驱动设计**的开发更易实现. DDD在[维基百科中的定义](https://zh.wikipedia.org/wiki/%E5%9F%9F%E9%A9%B1%E5%8A%A8%E5%BC%80%E5%8F%91)如下: > **领域驱动设计(DDD)** 是一种通过将实现连接到持续进化的模型来满足复杂需求的软件开发方法. 领域驱动设计的前提是: > diff --git a/docs/zh-Hans/Entities.md b/docs/zh-Hans/Entities.md index fd8528dc30..190088753f 100644 --- a/docs/zh-Hans/Entities.md +++ b/docs/zh-Hans/Entities.md @@ -26,9 +26,9 @@ public class Book : Entity 如果你的实体Id类型为 `Guid`,有一些好的实践可以实现: * 创建一个构造函数,获取ID作为参数传递给基类. - * 如果没有为GUID Id斌值,ABP框架会在保存时设置它,但是在将实体保存到数据库之前最好在实体上有一个有效的Id. -* 如果使用带参数的构造函数创建实体,那么还要创建一个 `protected` 构造函数. 当数据库提供程序从数据库读取你的实体时(反序列化时)将使用它. -* 不要使用 `Guid.NewGuid()` 来设置Id! 在创建实体的代码中使用[`IGuidGenerator`服务](Guid-Generation.md)传递Id参数. `IGuidGenerator`经过优化可以产生连续的GUID.这对于关系数据库中的聚集索引非常重要. + * 如果没有为GUID Id斌值,**ABP框架会在保存时设置它**,但是在将实体保存到数据库之前最好在实体上有一个有效的Id. +* 如果使用带参数的构造函数创建实体,那么还要创建一个 `private` 或 `protected` 构造函数. 当数据库提供程序从数据库读取你的实体时(反序列化时)将使用它. +* 不要使用 `Guid.NewGuid()` 来设置Id! 在创建实体的代码中**使用[`IGuidGenerator`服务](Guid-Generation.md)**传递Id参数. `IGuidGenerator`经过优化可以产生连续的GUID.这对于关系数据库中的聚集索引非常重要. 示例实体: diff --git a/docs/zh-Hans/Guid-Generation.md b/docs/zh-Hans/Guid-Generation.md index be0c1f425b..1b097f8b08 100644 --- a/docs/zh-Hans/Guid-Generation.md +++ b/docs/zh-Hans/Guid-Generation.md @@ -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 + { + 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 _productRepository; + private readonly IGuidGenerator _guidGenerator; + + public MyProductService( + IRepository 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(options => +{ + options.DefaultSequentialGuidType = SequentialGuidType.SequentialAsBinary; +}); +```` + +> EF Core[集成包](https://docs.abp.io/en/abp/latest/Entity-Framework-Core-Other-DBMS)已为相关的DBMS设置相应的值. 如果你正在使用这些集成包,在大多数情况下则无需设置此选项. diff --git a/docs/zh-Hans/Timing.md b/docs/zh-Hans/Timing.md new file mode 100644 index 0000000000..71ddbb9058 --- /dev/null +++ b/docs/zh-Hans/Timing.md @@ -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(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)库实现. \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/JavaScript-API/Index.md b/docs/zh-Hans/UI/AspNetCore/JavaScript-API/Index.md new file mode 100644 index 0000000000..b06025cd16 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/JavaScript-API/Index.md @@ -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 \ No newline at end of file diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Button-groups.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Button-groups.md new file mode 100644 index 0000000000..b69f39c7b4 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Button-groups.md @@ -0,0 +1,37 @@ +# 按钮组 + +## 介绍 + +`abp-button-group` 是创建分组按钮的主要元素. + +基本用法: + +````csharp + + Left + Middle + Right + +```` + +## Demo + +参阅[按钮组demo页面](https://bootstrap-taghelpers.abp.io/Components/Button-groups)查看示例. + +## Attributes + +### direction + +按钮的方向. 应为以下值之一: + +* `Horizontal` (默认值) +* `Vertical` + +### size + +组中按钮的大小. 应为以下值之一: + +* `Default` (默认值) +* `Small` +* `Medium` +* `Large` diff --git a/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Carousel.md b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Carousel.md new file mode 100644 index 0000000000..f230428c15 --- /dev/null +++ b/docs/zh-Hans/UI/AspNetCore/Tag-Helpers/Carousel.md @@ -0,0 +1,71 @@ +# 轮播 + +## 介绍 + +`abp-carousel` 是abp标签轮播元素 + +基本用法: + +````csharp + + + + + +```` + +## 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 + +当无法显示图像时,该值设置轮播项目图像的替代文本. \ No newline at end of file diff --git a/docs/zh-Hans/docs-nav.json b/docs/zh-Hans/docs-nav.json index 2a2c66ebb6..d105400184 100644 --- a/docs/zh-Hans/docs-nav.json +++ b/docs/zh-Hans/docs-nav.json @@ -216,19 +216,12 @@ "path": "Text-Templating.md" }, { - "text": "JSON序列化" + "text": "GUID 生成", + "path": "Guid-Generation.md" }, { - "text": "邮件" - }, - { - "text": "GUIDs" - }, - { - "text": "线程" - }, - { - "text": "定时" + "text": "时钟", + "path": "Timing.md" } ] }, @@ -309,6 +302,15 @@ { "text": "动态C# API客户端", "path": "API/Dynamic-CSharp-API-Clients.md" + }, + { + "text": "ABP端点", + "items": [ + { + "text": "应用程序配置", + "path": "API/Application-Configuration.md" + } + ] } ] }, @@ -481,6 +483,10 @@ "path": "Dapper.md" } ] + }, + { + "text": "种子数据", + "path": "Data-Seeding.md" } ] },