diff --git a/docs/en/API/Application-Configuration.md b/docs/en/API/Application-Configuration.md index 1465f0fd24..3aea468f8c 100644 --- a/docs/en/API/Application-Configuration.md +++ b/docs/en/API/Application-Configuration.md @@ -7,7 +7,7 @@ ABP Framework provides a pre-built and standard endpoint that contains some usef * [Setting](../Settings.md) values for the current user. * Info about the [current user](../CurrentUser.md) (like id and user name). * Info about the current [tenant](../Multi-Tenancy.md) (like id and name). -* [Time zone](../Timing.md) information for the current user and the [clock](Timing.md) type of the application. +* [Time zone](../Timing.md) information for the current user and the [clock](../Timing.md) type of the application. ## HTTP API diff --git a/docs/zh-Hans/API/Application-Configuration.md b/docs/zh-Hans/API/Application-Configuration.md index ba062d9cef..50e74d5bd7 100644 --- a/docs/zh-Hans/API/Application-Configuration.md +++ b/docs/zh-Hans/API/Application-Configuration.md @@ -2,12 +2,12 @@ ABP框架提供了一个预构建的标准端点,其中包含一些有关应用程序/服务的有用信息. 这里是此端点的一些基本信息的列表: -* [本地化](Localization.md)值, 支持应用程序的当前语言. -* 当前用户可用和已授予的[策略](Authorization.md)(权限). -* 当前用户的[设置](Settings.md)值. -* 关于[当前用户](CurrentUser.md)的信息 (如 id 和用户名). -* 关于当前[租户](Multi-Tenancy.md)的信息 (如 id 和名称). -* 当前用户的[时区](Timing.md)信息和应用程序的[时钟](Timing.md)类型. +* [本地化](../Localization.md)值, 支持应用程序的当前语言. +* 当前用户可用和已授予的[策略](../Authorization.md)(权限). +* 当前用户的[设置](../Settings.md)值. +* 关于[当前用户](../CurrentUser.md)的信息 (如 id 和用户名). +* 关于当前[租户](../Multi-Tenancy.md)的信息 (如 id 和名称). +* 当前用户的[时区](../Timing.md)信息和应用程序的[时钟](../Timing.md)类型. ## HTTP API diff --git a/docs/zh-Hans/Authorization.md b/docs/zh-Hans/Authorization.md index 2c269ab5fa..96caae1404 100644 --- a/docs/zh-Hans/Authorization.md +++ b/docs/zh-Hans/Authorization.md @@ -285,8 +285,6 @@ public async Task CreateAsync(CreateAuthorDto input) abp.auth.isGranted('MyPermissionName'); ```` -参阅 [abp.auth](UI/AspNetCore/JavaScript-API/Index.md) API 文档了解详情. - ## 权限管理 通常权限管理是管理员用户使用权限管理模态框进行授权: diff --git a/docs/zh-Hans/Best-Practices/Application-Services.md b/docs/zh-Hans/Best-Practices/Application-Services.md index 6c681edde1..82a5dc8aba 100644 --- a/docs/zh-Hans/Best-Practices/Application-Services.md +++ b/docs/zh-Hans/Best-Practices/Application-Services.md @@ -203,7 +203,7 @@ Task VoteAsync(Guid id, VoteType type); #### 额外的属性 -* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. +* **推荐** 使用 `MapExtraPropertiesTo` 扩展方法 ([参阅](../Object-Extensions.md)) 或配置对象映射 (`MapExtraProperties`) 以允许应用开发人员能够扩展对象和服务. #### 操作/删除 实体 diff --git a/docs/zh-Hans/Blob-Storing-Database.md b/docs/zh-Hans/Blob-Storing-Database.md index e45c79d179..cc688e4396 100644 --- a/docs/zh-Hans/Blob-Storing-Database.md +++ b/docs/zh-Hans/Blob-Storing-Database.md @@ -27,7 +27,7 @@ abp add-module Volo.Abp.BlobStoring.Database 这里是此提供程序定义的所有包: -* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Domain.Shared) +* [Volo.Abp.BlobStoring.Database.Domain.Shared](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain.Shared) * [Volo.Abp.BlobStoring.Database.Domain](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.Domain) * [Volo.Abp.BlobStoring.Database.EntityFrameworkCore](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.EntityFrameworkCore) * [Volo.Abp.BlobStoring.Database.MongoDB](https://www.nuget.org/packages/Volo.Abp.BlobStoring.Database.MongoDB) diff --git a/docs/zh-Hans/Entity-Framework-Core-Migrations.md b/docs/zh-Hans/Entity-Framework-Core-Migrations.md index 77f5ac5cd7..876d98721b 100644 --- a/docs/zh-Hans/Entity-Framework-Core-Migrations.md +++ b/docs/zh-Hans/Entity-Framework-Core-Migrations.md @@ -3,7 +3,7 @@ 本文首先介绍[应用程序启动模板](Startup-Templates/Application.md)提供的**默认结构**,并讨论你可能希望为自己的应用程序实现的**各种场景**. -> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Index.md). +> 本文档适用于希望完全理解和自定义[应用程序启动模板](Startup-Templates/Application.md)附带的数据库结构的人员. 如果你只是想创建实体和管理代码优先(code first)迁移,只需要遵循[启动教程](Tutorials/Part-1.md). ### 源码 diff --git a/docs/zh-Hans/Modules/Docs.md b/docs/zh-Hans/Modules/Docs.md index 63759d1462..acc956be1b 100644 --- a/docs/zh-Hans/Modules/Docs.md +++ b/docs/zh-Hans/Modules/Docs.md @@ -611,7 +611,7 @@ Configure(options => 应用程序启动后如果`Index`不存在则会自动创建`Index`. `DefaultElasticClientProvider`负责创建`IElasticClient`, 默认情况下它会从`IConfiguration`中读取Elastic Search的`Url`. -如果你的IElasticClient需要其它配置请使用重写IElasticClientProvider服务并在依赖注入系统中替换它. +如果你的 `IElasticClient` 需要其它配置请使用重写 `IElasticClientProvider` 服务并在[依赖注入](../Dependency-Injection.md)系统中替换它. ``` { "ElasticSearch": { diff --git a/docs/zh-Hans/Startup-Templates/Module.md b/docs/zh-Hans/Startup-Templates/Module.md index a6706ad783..8341670af4 100644 --- a/docs/zh-Hans/Startup-Templates/Module.md +++ b/docs/zh-Hans/Startup-Templates/Module.md @@ -56,7 +56,7 @@ abp new Acme.IssueManagement -t module --no-ui ### .Domain 项目 -解决方案的领域层. 它主要包含 [实体, 集合根](../Entities.md), [领域服务](../Domain-Services.md), [值类型](../Value-Types.md), [仓储接口](../Repositories.md) 和解决方案的其他领域对象. +解决方案的领域层. 它主要包含 [实体, 集合根](../Entities.md), [领域服务](../Domain-Services.md), 值类型, [仓储接口](../Repositories.md) 和解决方案的其他领域对象. 例如 `Issue` 实体, `IssueManager` 领域服务和 `IIssueRepository` 接口都适合放在这个项目中. diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md index bcf4e21b9f..78d2166f6b 100644 --- a/docs/zh-Hans/Tutorials/Part-1.md +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -1,128 +1,75 @@ -## ASP.NET Core {{UI_Value}} 教程 - 第一章 +## Web应用程序开发教程 - 第一章: 创建服务端 ````json //[doc-params] { - "UI": ["MVC","NG"] + "UI": ["MVC","NG"], + "DB": ["EF","Mongo"] } ```` {{ if UI == "MVC" - DB="ef" - DB_Text="Entity Framework Core" UI_Text="mvc" else if UI == "NG" - DB="mongodb" - DB_Text="MongoDB" UI_Text="angular" else - DB ="?" UI_Text="?" end +if DB == "EF" + DB_Text="Entity Framework Core" +else if DB == "Mongo" + DB_Text="MongoDB" +else + DB_Text="?" +end }} ### 关于本教程 -在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的应用程序. **{{DB_Text}}**将用作ORM提供者,前端使用{{UI_Value}} 和 JavaScript. - -ASP.NET Core {{UI_Value}} 系列教程包括三个3个部分: - -- **Part-1: 创建项目和书籍列表页面(本章)** -- [Part-2: 创建,编辑,删除书籍](Part-2.md) -- [Part-3: 集成测试](Part-3.md) - -> 你也可以观看由ABP社区成员为本教程录制的[视频课程](https://amazingsolutions.teachable.com/p/lets-build-the-bookstore-application). - -### 创建新项目 - -创建一个名为 `Acme.BookStore` 的新项目,其中 `Acme` 是公司名 `BookStore` 是项目名. 你可以参阅[入门](../Getting-Started?UI={{UI}}#run-the-application) 文档了解如何创建新项目. 我们将使用CLI创建新项目. - -#### 创建项目 - -使用以下命令创建一个新的ABP项目,使用 `{{DB_Text}}` 做为数据库提供者, UI选项使用 `{{UI_Value}}`. 其他CLI选项请参考[ABP CLI](https://docs.abp.io/en/abp/latest/CLI)文档. - -```bash -abp new Acme.BookStore --template app --database-provider {{DB}} --ui {{UI_Text}} --mobile none -``` - -![Creating project](./images/bookstore-create-project-{{UI_Text}}.png) +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的: -### 应用迁移 +* **{{DB_Text}}** 做为ORM提供程序. +* **{{UI_Value}}** 做为UI框架. -项目创建后,需要应用初始化迁移创建数据库. 运行 `Acme.BookStore.DbMigrator` 应用程序. 它会应用所有迁移,完成流程后你会看到以下结果,数据库已经准备好了! +本教程分为以下部分: -![Migrations applied](./images/bookstore-migrations-applied-{{UI_Text}}.png) +- **Part 1: 创建服务端 (本章)** +- [Part 2: 图书列表页面](Part-2.md) +- [Part 3: 创建,更新和删除图书](Part-3.md) +- [Part 4: 集成测试](Part-4.md) +- [Part 5: 授权](Part-5.md) +- [Part 6: 作者: 领域层](Part-6.md) +- [Part 7: 作者: 数据库集成](Part-7.md) +- [Part 8: 作者: 应用服务层](Part-8.md) +- [Part 9: 作者: 用户页面](Part-9.md) +- [Part 10: 图书到作者的关系](Part-10.md) -> 另外你也可以在 Visual Studio 包管理控制台运行 `Update-Database` 命令应用迁移. +### 下载源码 -#### 初始化数据库表 +本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两种可供下载的源码组合: -![Initial database tables](./images/bookstore-database-tables-{{DB}}.png) +* [MVC (Razor Pages) UI 与 EF Core](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore) +* [Angular UI 与 MongoDB](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb) -### 运行应用程序 +## 创建解决方案 -右键单击{{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} 项目**设置为启动项**. 使用 **CTRL+F5** 或 **F5** 运行应用程序. {{if UI == "NG"}}你会看到BookStore API的Swagger UI.{{end}} - -更多信息,参阅[入门教程](../../Getting-Started?UI={{UI}})的运行应用程序部分. - -![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) - -{{if UI == "NG"}} - -在 `angular` 下打开命令行终端,执行 `yarn` 命令: - -```bash -yarn -``` - -所有的模块加载后,执行 `yarn start` 命令: - -```bash -yarn start -``` - -默认网站从以下URL访问: - -http://localhost:4200/ - -如果你成功看到登录页面,可以按 `ctrl-c` 退出Angular托管.(我们稍后再运行). - -> 注意, Firefox不使用Windows凭据存储,你需要手动将自签名的开发人员证书导入到Firefox. 打开Firefox并导航到以下网址: -> -> https://localhost:44322/api/abp/application-configuration -> -> 如果你看到下图,单击 **Accept the Risk 和 Continue** 按钮绕过警告. -> -> ![Set as startup project](./images/mozilla-self-signed-cert-error.png) - -{{end}} +在开始开发之前,请按照[入门教程](../Getting-Started.md)创建名为 `Acme.BookStore` 的新解决方案. -默认的登录凭证: +## 创建Book实体 -* **Username**: admin -* **Password**: 1q2w3E* - -### 解决方案的结构 - -下面的图片展示了从启动模板创建的项目是如何分层的. - -![bookstore-visual-studio-solution](./images/bookstore-solution-structure-{{UI_Text}}.png) - -> 你可以查看[应用程序模板文档](../startup-templates/application#solution-structure)以详细了解解决方案结构. - -### 创建Book实体 - -启动模板中的域层分为两个项目: +启动模板中的**领域层**分为两个项目: - `Acme.BookStore.Domain`包含你的[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities), [领域服务](https://docs.abp.io/zh-Hans/abp/latest/Domain-Services)和其他核心域对象. - `Acme.BookStore.Domain.Shared`包含可与客户共享的常量,枚举或其他域相关对象. -在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义[实体](https://docs.abp.io/zh-Hans/abp/latest/Entities). 该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个名为`Book`的类,如下所示: +在解决方案的**领域层**(`Acme.BookStore.Domain`项目)中定义你的实体. -````C# +该应用程序的主要实体是`Book`. 在`Acme.BookStore.Domain`项目中创建一个 `Books` 文件夹并在其中添加一个名为 `Book` 的类,如下所示: + +````csharp using System; using Volo.Abp.Domain.Entities.Auditing; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class Book : AuditedAggregateRoot { @@ -133,33 +80,22 @@ namespace Acme.BookStore public DateTime PublishDate { get; set; } public float Price { get; set; } - - protected Book() - { - } - public Book(Guid id, string name, BookType type, DateTime publishDate, float price) - :base(id) - { - Name = name; - Type = type; - PublishDate = publishDate; - Price = price; - } } } ```` -* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是**域驱动设计(DDD)** 概念之一. 有关详细信息和最佳做法,请参阅[实体文档](https://docs.abp.io/zh-Hans/abp/latest/Entities). -* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). +* ABP为实体提供了两个基本的基类: `AggregateRoot`和`Entity`. **Aggregate Root**是**[领域驱动设计](./Domain-Driven-Design.md)** 概念之一. 可以视为直接查询和处理的根实体(请参阅[实体文档](./Entities.md)). +* `Book`实体继承了`AuditedAggregateRoot`,`AuditedAggregateRoot`类在`AggregateRoot`类的基础上添加了一些审计属性(`CreationTime`, `CreatorId`, `LastModificationTime` 等). ABP框架自动为你管理这些属性. * `Guid`是`Book`实体的主键类型. -* 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling). -#### BookType枚举 +> 为了保持简单,本教程将实体属性保留为 **public get/set** . 如果你想了解关于DDD最佳实践,请参阅[实体文档](../Entities.md). -上面所用到的`BookType`枚举定义如下: +### BookType枚举 -````C# -namespace Acme.BookStore +上面所用到了 `BookType` 枚举,在 `Acme.BookStore.Domain.Shared` 项目创建 `BookType`. + +````csharp +namespace Acme.BookStore.Books { public enum BookType { @@ -176,69 +112,112 @@ namespace Acme.BookStore } ```` -#### 将Book实体添加到DbContext中 +最终的文件夹/文件结构应该如下所示: + +![bookstore-book-and-booktype](images/bookstore-book-and-booktype.png) + +### 将Book实体添加到DbContext中 -{{if DB == "ef"}} +{{if DB == "EF"}} EF Core需要你将实体和 `DbContext` 建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如下所示: -````C# +````csharp public class BookStoreDbContext : AbpDbContext { - public DbSet Book { get; set; } - ... + public DbSet Books { get; set; } + //... } ```` {{end}} -{{if DB == "mongodb"}} +{{if DB == "Mongo"}} 添加 `IMongoCollection Book` 属性到 `Acme.BookStore.MongoDB` 项目的 `BookStoreMongoDbContext` 中. ```csharp public class BookStoreMongoDbContext : AbpMongoDbContext { - public IMongoCollection Users => Collection(); - public IMongoCollection Book => Collection();//<--added this line--> - //... + public IMongoCollection Books => Collection(); + //... } ``` {{end}} -{{if DB == "ef"}} +{{if DB == "EF"}} -#### 配置你的Book实体 +### 将Book实体映射到数据库表 -在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,并将以下代码添加到 `ConfigureBookStore` 方法的末尾以配置Book实体: +在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,添加 `Book` 实体的映射代码. 最终类应为: ````csharp -builder.Entity(b => +using Acme.BookStore.Books; +using Microsoft.EntityFrameworkCore; +using Volo.Abp; +using Volo.Abp.EntityFrameworkCore.Modeling; + +namespace Acme.BookStore.EntityFrameworkCore { - b.ToTable(BookStoreConsts.DbTablePrefix + "Book", BookStoreConsts.DbSchema); - b.ConfigureByConvention(); //auto configure for the base class props - b.Property(x => x.Name).IsRequired().HasMaxLength(128); -}); + public static class BookStoreDbContextModelCreatingExtensions + { + public static void ConfigureBookStore(this ModelBuilder builder) + { + Check.NotNull(builder, nameof(builder)); + + /* Configure your own tables/entities inside here */ + + builder.Entity(b => + { + b.ToTable(BookStoreConsts.DbTablePrefix + "Books", + BookStoreConsts.DbSchema); + b.ConfigureByConvention(); //auto configure for the base class props + b.Property(x => x.Name).IsRequired().HasMaxLength(128); + }); + } + } +} ```` -添加 `using Volo.Abp.EntityFrameworkCore.Modeling;` 以使用 `ConfigureByConvention` 扩展方法. +* `BookStoreConsts` 含有用于表的架构和表前缀的常量值. 你不必使用它,但建议在单点控制表前缀. +* `ConfigureByConvention()` 方法优雅的配置/映射继承的属性,应始终对你所有的实体使用它. -{{end}} +### 添加数据迁移 -{{if DB == "mongodb"}} +启动模板使用[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)创建和维护数据库架构. 打开菜单*工具 > NuGet包管理器*下的**程序包管理控制台 (PMC)**. + +![Open Package Manager Console](images/bookstore-open-package-manager-console.png) + +选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations` 做为**默认项目**然后执行以下命令: + +```bash +Add-Migration "Created_Book_Entity" +``` + +![bookstore-pmc-add-book-migration](./images/bookstore-pmc-add-book-migration-v2.png) + +它会在 `Acme.BookStore.EntityFrameworkCore.DbMigrations` 项目中的 `Migrations` 文件内创建一个新的迁移类. + +在更新数据库之前,请阅读下面的部分了解如何将一些初始数据插入到数据库. + +> 如果你使用其他IDE而不是Visual Studio, 你可以使用 [`dotnet-ef]`(https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/?tabs=dotnet-core-cli#create-a-migration) 工具. + +{{end}} #### 添加种子数据 -添加种子数据是可选的,但第一次运行时最好将初始数据添加到数据库中. ABP提供了[数据种子系统](https://docs.abp.io/en/abp/latest/Data-Seeding). 在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类: +> >在运行应用程序之前最好将初始数据添加到数据库中. 本节介绍ABP框架的[数据种子系统](../Data-Seeding.md). 如果你不想创建种子数据可以跳过本节,但是建议你遵循它来学习这个有用的ABP Framework功能。 + +在 `*.Domain` 项目下创建派生 `IDataSeedContributor` 的类,并且拷贝以下代码: ```csharp using System; using System.Threading.Tasks; +using Acme.BookStore.Books; using Volo.Abp.Data; using Volo.Abp.DependencyInjection; using Volo.Abp.Domain.Repositories; -using Volo.Abp.Guids; namespace Acme.BookStore { @@ -246,102 +225,75 @@ namespace Acme.BookStore : IDataSeedContributor, ITransientDependency { private readonly IRepository _bookRepository; - private readonly IGuidGenerator _guidGenerator; - public BookStoreDataSeederContributor( - IRepository bookRepository, - IGuidGenerator guidGenerator) + public BookStoreDataSeederContributor(IRepository bookRepository) { _bookRepository = bookRepository; - _guidGenerator = guidGenerator; } public async Task SeedAsync(DataSeedContext context) { - if (await _bookRepository.GetCountAsync() > 0) + if (await _bookRepository.GetCountAsync() <= 0) { - return; + await _bookRepository.InsertAsync( + new Book + { + Name = "1984", + Type = BookType.Dystopia, + PublishDate = new DateTime(1949, 6, 8), + Price = 19.84f + }, + autoSave: true + ); + + await _bookRepository.InsertAsync( + new Book + { + Name = "The Hitchhiker's Guide to the Galaxy", + Type = BookType.ScienceFiction, + PublishDate = new DateTime(1995, 9, 27), + Price = 42.0f + }, + autoSave: true + ); } - - await _bookRepository.InsertAsync( - new Book( - id: _guidGenerator.Create(), - name: "1984", - type: BookType.Dystopia, - publishDate: new DateTime(1949, 6, 8), - price: 19.84f - ) - ); - - await _bookRepository.InsertAsync( - new Book( - id: _guidGenerator.Create(), - name: "The Hitchhiker's Guide to the Galaxy", - type: BookType.ScienceFiction, - publishDate: new DateTime(1995, 9, 27), - price: 42.0f - ) - ); } } } ``` -{{end}} +* 如果数据库中当前没有图书,则此代码使用 `IRepository`(默认为[repository](../Repositories.md)将两本书插入数据库. -{{if DB == "ef"}} +### 更新数据库 -#### 添加新的Migration并更新数据库 +运行 `Acme.BookStore.DbMigrator` 应用程序来更新数据库: -这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单) +![bookstore-dbmigrator-on-solution](images/bookstore-dbmigrator-on-solution.png) -![Open Package Manager Console](./images/bookstore-open-package-manager-console.png) +{{if DB == "EF"}} -选择 `Acme.BookStore.EntityFrameworkCore.DbMigrations`作为默认的项目然后执行下面的命令: +`.DbMigrator` 是一个控制台使用程序,可以在**开发**和**生产**环境**迁移数据库架构**和**初始化种子数据**. -```bash -Add-Migration "Created_Book_Entity" -``` - -![bookstore-pmc-add-book-migration](./images/bookstore-pmc-add-book-migration-v2.png) - -这样就会在 `Migrations` 文件夹中创建一个新的migration类.然后执行 `Update-Database` 命令更新数据库结构: - -````bash -Update-Database -```` - -![bookstore-update-database-after-book-entity](./images/bookstore-update-database-after-book-entity.png) - -#### 添加示例数据 - -`Update-Database`命令在数据库中创建了`AppBook`表. 打开数据库并输入几个示例行,以便在页面上显示它们: - -```mssql -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('f3c04764-6bfd-49e2-859e-3f9bfda6183e', '2018-07-01', '1984',3,'1949-06-08','19.84') - -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('13024066-35c9-473c-997b-83cd8d3e29dc', '2018-07-01', 'The Hitchhiker`s Guide to the Galaxy',7,'1995-09-27','42') +{{end}} -INSERT INTO AppBook (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES -('4fa024a1-95ac-49c6-a709-6af9e4d54b54', '2018-07-02', 'Pet Sematary',5,'1983-11-14','23.7') -``` +{{if DB == "Mongo"}} -![bookstore-book-table](./images/bookstore-book-table.png) +尽管MongoDB**不需要**数据库架构迁移,但仍建议运行该应用程序,因为它可以在数据库上播种初始数据. 可以在**开发**和**生产**环境中使用. {{end}} -### 创建应用服务 +## 创建应用程序 -下一步是创建[应用服务](../Application-Services.md)来管理(创建,列出,更新,删除)书籍. 启动模板中的应用程序层分为两个项目: +应用程序层由两个分离的项目组成: -* `Acme.BookStore.Application.Contracts`主要包含你的DTO和应用程序服务接口. -* `Acme.BookStore.Application`包含应用程序服务的实现. +* `Acme.BookStore.Application.Contracts 包含你的[DTO](../Data-Transfer-Objects.md)和[应用服务](../Application-Services.md)接口. +* `Acme.BookStore.Application` 包含你的应用服务实现. -#### BookDto +在本部分中,你将创建一个应用程序服务,使用ABP Framework的 `CrudAppService` 基类来获取,创建,更新和删除书籍. -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`BookDto`的DTO类: +### BookDto + +`CrudAppService` 基类需要定义实体的基本DTO. 在 `Acme.BookStore.Application.Contracts` 项目中创建一个名为 `BookDto` 的DTO类: ````C# using System; @@ -364,11 +316,12 @@ namespace Acme.BookStore * **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects)查看更多信息. * 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. -* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的`Book`类一样具有一些审计属性. +* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的 `Book` 实体一样具有一些审计属性. 在将书籍返回到表示层时,需要将`Book`实体转换为`BookDto`对象. [AutoMapper](https://automapper.org)库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper,因此你只需在`Acme.BookStore.Application`项目的`BookStoreApplicationAutoMapperProfile`类中定义映射: ````csharp +using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore @@ -383,16 +336,17 @@ namespace Acme.BookStore } ```` -#### CreateUpdateBookDto +> 参阅 [对象对对象映射](../Object-To-Object-Mapping.md) 文档了解详情. + +### CreateUpdateBookDto -在`Acme.BookStore.Application.Contracts`项目中创建一个名为`CreateUpdateBookDto`的DTO类: +在`Acme.BookStore.Application.Contracts`项目中创建一个名为 `CreateUpdateBookDto` 的DTO类: -````c# +````csharp using System; using System.ComponentModel.DataAnnotations; -using Volo.Abp.AutoMapper; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class CreateUpdateBookDto { @@ -404,7 +358,8 @@ namespace Acme.BookStore public BookType Type { get; set; } = BookType.Undefined; [Required] - public DateTime PublishDate { get; set; } + [DataType(DataType.Date)] + public DateTime PublishDate { get; set; } = DateTime.Now; [Required] public float Price { get; set; } @@ -415,9 +370,11 @@ namespace Acme.BookStore * 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. * 它定义了数据注释属性(如`[Required]`)来定义属性的验证. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). -就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射: +就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射,最终映射配置类如下: + ````csharp +using Acme.BookStore.Books; using AutoMapper; namespace Acme.BookStore @@ -427,30 +384,29 @@ namespace Acme.BookStore public BookStoreApplicationAutoMapperProfile() { CreateMap(); - CreateMap(); //<--added this line--> + CreateMap(); } } } ```` -#### IBookAppService +### IBookAppService -在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: +下一步是为应用程序定义接口,在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: -````C# +````csharp using System; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public interface IBookAppService : - ICrudAppService< //定义了CRUD方法 - BookDto, //用来展示书籍 - Guid, //Book实体的主键 - PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序 - CreateUpdateBookDto, //用于创建书籍 - CreateUpdateBookDto> //用于更新书籍 + ICrudAppService< //Defines CRUD methods + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto> //Used to create/update a book { } @@ -458,25 +414,29 @@ namespace Acme.BookStore ```` * 框架定义应用程序服务的接口**不是必需的**. 但是,它被建议作为最佳实践. -* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法. -* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定. +* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法(将在下一部分中完成). +* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定(例如使用不同的DTO进行创建和更新). -#### BookAppService +### BookAppService -在`Acme.BookStore.Application`项目中实现名为`BookAppService`的`IBookAppService`: +在`Acme.BookStore.Application`项目中创建名为 `BookAppService` 的 `IBookAppService` 实现: -````C# +````csharp using System; using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Services; using Volo.Abp.Domain.Repositories; -namespace Acme.BookStore +namespace Acme.BookStore.Books { public class BookAppService : - CrudAppService, - IBookAppService + CrudAppService< + Book, //The Book entity + BookDto, //Used to show books + Guid, //Primary key of the book entity + PagedAndSortedResultRequestDto, //Used for paging/sorting + CreateUpdateBookDto>, //Used to create/update a book + IBookAppService //implement the IBookAppService { public BookAppService(IRepository repository) : base(repository) @@ -487,15 +447,17 @@ namespace Acme.BookStore } ```` -* `BookAppService`继承了`CrudAppService<...>`.它实现了上面定义的CRUD方法. +* `BookAppService`继承了`CrudAppService<...>`.它实现了 `ICrudAppService` 定义的CRUD方法. * `BookAppService`注入`IRepository `,这是`Book`实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅[仓储文档](https://docs.abp.io/zh-Hans/abp/latest/Repositories) -* `BookAppService`使用`IObjectMapper`将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 你之前定义了映射, 因此它将按预期工作. +* `BookAppService`使用[`IObjectMapper`](../Object-To-Object-Mapping.md)将`Book`对象转换为`BookDto`对象, 将`CreateUpdateBookDto`对象转换为`Book`对象. 启动模板使用[AutoMapper](http://automapper.org/)库作为对象映射提供程序. 我们之前定义了映射, 因此它将按预期工作. ### 自动生成API Controllers -你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. ABP可以[**自动**](https://docs.abp.io/zh-Hans/abp/latest/API/Auto-API-Controllers)按照惯例将你的应用程序服务配置为MVC API控制器. +你通常创建**Controller**以将应用程序服务公开为**HTTP API**端点. 因此允许浏览器或第三方客户端通过AJAX调用它们. + +ABP可以[**自动**](../API/Auto-API-Controllers.md)按照惯例将你的应用程序服务配置为MVC API控制器. -#### Swagger UI +### Swagger UI 启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用你自己的端口替换XXXX)作为URL. @@ -503,485 +465,42 @@ namespace Acme.BookStore ![bookstore-swagger](images/bookstore-swagger.png) -Swagger有一个很好的UI来测试API. 你可以尝试执行`[GET] /api/app/book` API来获取书籍列表. - -{{if UI == "MVC"}} - -### 动态JavaScript代理 - -在Javascript端通过AJAX的方式调用HTTP API接口是很常见的,你可以使用`$.ajax`或者其他的工具来调用接口.当然,ABP中提供了更好的方式. - -ABP **自动** 为所有的API接口创建了JavaScript **代理**.因此,你可以像调用 **JavaScript function**一样调用任何接口. - -#### 在浏览器的开发者控制台中测试接口 - -你可以使用你钟爱的浏览器的 **开发者控制台** 中轻松测试JavaScript代理.运行程序,并打开浏览器的 **开发者工具**(快捷键:F12),切换到 **Console** 标签,输入下面的代码并回车: - -````js -acme.bookStore.book.getList({}).done(function (result) { console.log(result); }); -```` - -* `acme.bookStore`是`BookAppService`的命名空间,转换成了[驼峰命名](https://en.wikipedia.org/wiki/Camel_case). -* `book`是`BookAppService`转换后的名字(去除了AppService后缀并转成了驼峰命名). -* `getList`是定义在`AsyncCrudAppService`基类中的`GetListAsync`方法转换后的名字(去除了Async后缀并转成了驼峰命名). -* `{}`参数用于将空对象发送到`GetListAsync`方法,该方法通常需要一个类型为`PagedAndSortedResultRequestDto`的对象,用于向服务器发送分页和排序选项(所有属性都是可选的,所以你可以发送一个空对象). -* `getList`方法返回了一个`promise`.因此,你可以传递一个回调函数到`done`(或者`then`)方法中来获取服务返回的结果. - -运行这段代码会产生下面的输出: - -![bookstore-test-js-proxy-getlist](./images/bookstore-test-js-proxy-getlist.png) - -你可以看到服务器返回的 **book list**.你还可以切换到开发者工具的 **network** 查看客户端到服务器端的通讯信息: - -![bookstore-test-js-proxy-getlist-network](./images/bookstore-test-js-proxy-getlist-network.png) - -我们使用`create`方法 **创建一本新书**: - -````js -acme.bookStore.book.create({ name: 'Foundation', type: 7, publishDate: '1951-05-24', price: 21.5 }).done(function (result) { console.log('successfully created the book with id: ' + result.id); }); -```` - -你会看到控制台会显示类似这样的输出: - -````text -successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246 -```` - -检查数据库中的`Book`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. - -### 创建书籍页面 - -现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). +Swagger有一个很好的UI来测试API. -在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Book`并添加一个名为`Index.cshtml`的Razor Page. +你可以尝试执行`[GET] /api/app/book` API来获取书籍列表, 服务端会返回以下JSON结果: -![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) - -打开`Index.cshtml`并把内容修改成下面这样: - -**Index.cshtml:** - -````html -@page -@using Acme.BookStore.Web.Pages.Book -@model IndexModel - -

Book

-```` - -* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Book`命名空间,或者在`Index.cshtml`中更新它. - -**Index.cshtml.cs:** - -```csharp -using Microsoft.AspNetCore.Mvc.RazorPages; - -namespace Acme.BookStore.Web.Pages.Book +````json { - public class IndexModel : PageModel + "totalCount": 2, + "items": [ { - public void OnGet() - { - - } - } -} -``` - -#### 将Book页面添加到主菜单 - -打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码: - -````csharp -//... -namespace Acme.BookStore.Web.Menus -{ - public class BookStoreMenuContributor : IMenuContributor + "name": "The Hitchhiker's Guide to the Galaxy", + "type": 7, + "publishDate": "1995-09-27T00:00:00", + "price": 42, + "lastModificationTime": null, + "lastModifierId": null, + "creationTime": "2020-07-03T21:04:18.4607218", + "creatorId": null, + "id": "86100bb6-cbc1-25be-6643-39f62806969c" + }, { - private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) - { - //<-- added the below code - context.Menu.AddItem( - new ApplicationMenuItem("BookStore", l["Menu:BookStore"]) - .AddItem( - new ApplicationMenuItem("BookStore.Book", l["Menu:Book"], url: "/Book") - ) - ); - //--> - } + "name": "1984", + "type": 3, + "publishDate": "1949-06-08T00:00:00", + "price": 19.84, + "lastModificationTime": null, + "lastModifierId": null, + "creationTime": "2020-07-03T21:04:18.3174016", + "creatorId": null, + "id": "41055277-cce8-37d7-bb37-39f62806960b" } + ] } ```` -{{end}} - -#### 本地化菜单 - -本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下: - -![bookstore-localization-files](./images/bookstore-localization-files-v2.png) - -打开`en.json`文件,将`Menu:BookStore`和`Menu:Book`键的本地化文本添加到文件末尾: - -````json -{ - "Culture": "en", - "Texts": { - "Menu:Home": "Home", - "Welcome": "Welcome", - "LongWelcomeMessage": "Welcome to the application. This is a startup project based on the ABP framework. For more information, visit abp.io.", - - "Menu:BookStore": "Book Store", - "Menu:Book": "Book", - "Actions": "Actions", - "Edit": "Edit", - "PublishDate": "Publish date", - "NewBook": "New book", - "Name": "Name", - "Type": "Type", - "Price": "Price", - "CreationTime": "Creation time", - "AreYouSureToDelete": "Are you sure you want to delete this item?" - } -} -```` - -* ABP的本地化功能建立在[ASP.NET Core's standard localization]((https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization))之上并增加了一些扩展.查看[本地化文档](https://docs.abp.io/zh-Hans/abp/latest/Localization). -* 本地化key是任意的. 你可以设置任何名称. 我们更喜欢为菜单项添加`Menu:`前缀以区别于其他文本. 如果未在本地化文件中定义文本,则它将**返回**到本地化的key(ASP.NET Core的标准行为). - -运行该应用程序,看到新菜单项已添加到顶部栏: - -![bookstore-menu-items](./images/bookstore-new-menu-item.png) - -点击BookStore下Book子菜单项就会跳转到新增的书籍页面. - -#### 书籍列表 - -我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,而需要在页面中引用样式和脚本文件. - -##### Index.cshtml - -将`Pages/Book/Index.cshtml`改成下面的样子: - -````html -@page -@model Acme.BookStore.Web.Pages.Book.IndexModel -@section scripts -{ - -} - - -

@L["Book"]

-
- - - - - @L["Name"] - @L["Type"] - @L["PublishDate"] - @L["Price"] - @L["CreationTime"] - - - - -
-```` - -* `abp-script` [tag helper](https://docs.microsoft.com/en-us/aspnet/core/mvc/views/tag-helpers/intro)用于将外部的 **脚本** 添加到页面中.它比标准的`script`标签多了很多额外的功能.它可以处理 **最小化**和 **版本**.查看[捆绑 & 压缩文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Bundling-Minification)获取更多信息. -* `abp-card` 和 `abp-table` 是为Twitter Bootstrap的[card component](http://getbootstrap.com/docs/4.1/components/card/)封装的 **tag helpers**.ABP中有很多tag helpers,可以很方便的使用大多数[bootstrap](https://getbootstrap.com/)组件.你也可以使用原生的HTML标签代替tag helpers.使用tag helper可以通过智能提示和编译时类型检查减少HTML代码并防止错误.查看[tag helpers 文档](https://docs.abp.io/zh-Hans/abp/latest/UI/AspNetCore/Tag-Helpers/Index). -* 你可以像上面本地化菜单一样 **本地化** 列名. - -#### 添加脚本文件 - -在`Pages/Book/`文件夹中创建 `index.js`文件 - -![bookstore-index-js-file](./images/bookstore-index-js-file-v2.png) - -`index.js`的内容如下: - -````js -$(function () { - var l = abp.localization.getResource('BookStore'); - var dataTable = $('#BooksTable').DataTable(abp.libs.datatables.normalizeConfiguration({ - ajax: abp.libs.datatables.createAjax(acme.bookStore.book.getList), - columnDefs: [ - { data: "name" }, - { data: "type" }, - { data: "publishDate" }, - { data: "price" }, - { data: "creationTime" } - ] - })); -}); -```` - -* `abp.libs.datatables.createAjax`是帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法. -* `abp.libs.datatables.normalizeConfiguration`是另一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表配置. -* `acme.bookStore.book.getList`是获取书籍列表的方法(上面已经介绍过了) -* 查看 [Datatable文档](https://datatables.net/manual/) 了解更多配置项. - -最终的页面如下: - -![Book list](./images/bookstore-book-list-2.png) - -{{end}} - -{{if UI == "NG"}} - -### Angular 开发 - -#### 创建book页面 - -是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: - -- [Angular CLI](https://angular.io/cli) 用于创建模块,组件和服务. -- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. -- [ngx-datatable](https://swimlane.gitbook.io/ngx-datatable/) 用做 datatable 类库. -- [Visual Studio Code](https://code.visualstudio.com/) 用做代码编辑器 (你可以选择自己喜欢的编辑器). - -#### 安装 NPM 包 - -在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: - -```bash -yarn -``` - -#### BookModule - -运行以下命令创建一个名为 `BookModule` 的新模块: - -```bash -yarn ng generate module book --routing true -``` - -![Generating book module](./images/bookstore-creating-book-module-terminal.png) - -#### 路由 - -打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的路由: - -```js -const routes: Routes = [ -// ... -// added a new route to the routes array - { - path: 'books', - loadChildren: () => import('./book/book.module').then(m => m.BookModule) - } -] -``` - -* 我们添加了一个懒加载路由. 参阅 [嬾加載功能模块](https://angular.io/guide/lazy-loading-ngmodules#lazy-loading-feature-modules). - -打开位于 `src\app` 目录下的 `route.provider.ts` 文件,用以下内容替换它: - -```js -import { RoutesService, eLayoutType } from '@abp/ng.core'; -import { APP_INITIALIZER } from '@angular/core'; - -export const APP_ROUTE_PROVIDER = [ - { provide: APP_INITIALIZER, useFactory: configureRoutes, deps: [RoutesService], multi: true }, -]; - -function configureRoutes(routes: RoutesService) { - return () => { - routes.add([ - //... - // added below element - { - path: '/books', - name: '::Menu:Books', - iconClass: 'fas fa-book', - order: 101, - layout: eLayoutType.application, - }, - ]); - }; -} -``` - -* 我们添加了一个新的路由元素在菜单上显示为 "Books" 的导航元素. - * `path` 路由的URL. - * `name` 菜单项的名称,可以使用本地化Key. - * `iconClass` 菜单项的图标. - * `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面. - * `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application`, `eLayoutType.account` 或 `eLayoutType.empty`. - -更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice). - -#### Book 列表组件 - -在命令行运行以下命令,生成名为 book-list 的新组件: - -```bash -yarn ng generate component book/book-list -``` - -![Creating book list](./images/bookstore-creating-book-list-terminal.png) - -打开 `app\book` 目录下的 `book.module.ts` 文件,使用以下内容替换它: - -```js -import { NgModule } from '@angular/core'; -import { CommonModule } from '@angular/common'; -import { BookRoutingModule } from './book-routing.module'; -import { BookListComponent } from './book-list/book-list.component'; -import { SharedModule } from '../shared/shared.module'; //<== added this line ==> - -@NgModule({ - declarations: [BookListComponent], - imports: [ - CommonModule, - BookRoutingModule, - SharedModule, //<== added this line ==> - ], -}) -export class BookModule {} -``` - -* 我们导入了 `SharedModule` 并添加到 `imports` 数组. - -打开 `app\book` 目录下的 `book-routing.module.ts` 文件用以下内容替换它: - -```js -import { NgModule } from '@angular/core'; -import { Routes, RouterModule } from '@angular/router'; -import { BookListComponent } from './book-list/book-list.component'; // <== added this line ==> - -// <== replaced routes ==> -const routes: Routes = [ - { - path: '', - component: BookListComponent, - }, -]; - -@NgModule({ - imports: [RouterModule.forChild(routes)], - exports: [RouterModule] -}) -export class BookRoutingModule { } -``` - -* 我们导入了 `BookListComponent` 并替换 `routes` 常量. - -运行 `yarn start`,等待Angular启动服务: - -```bash -yarn start -``` - -我们将看到book页面的 **book-list works!**: - -![Initial book list page](./images/bookstore-initial-book-list-page.png) - -#### 生成代理 - -ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档](../CLI.md). - -在 `angular` 文件夹下运行以下命令: - -```bash -abp generate-proxy --module app -``` - -![Generate proxy command](./images/generate-proxy-command.png) - -生成的文件如下: - -![Generated files](./images/generated-proxies.png) - -#### BookListComponent - -打开 `app\book\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: - -```js -import { ListService, PagedResultDto } from '@abp/ng.core'; -import { Component, OnInit } from '@angular/core'; -import { BookDto, BookType } from '../models'; -import { BookService } from '../services'; - -@Component({ - selector: 'app-book-list', - templateUrl: './book-list.component.html', - styleUrls: ['./book-list.component.scss'], - providers: [ListService], -}) -export class BookListComponent implements OnInit { - book = { items: [], totalCount: 0 } as PagedResultDto; - - booksType = BookType; - - constructor(public readonly list: ListService, private bookService: BookService) {} - - ngOnInit() { - const bookStreamCreator = (query) => this.bookService.getListByInput(query); - - this.list.hookToQuery(bookStreamCreator).subscribe((response) => { - this.book = response; - }); - } -} -``` - -* 我们注入了生成的 `BookService`. -* 我们实现了 [ListService](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service),它是一个公用服务,提供了简单的分页,排序和搜索. - -打开 `app\book\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: - -```html -
-
-
-
-
- {%{{{ '::Menu:Books' | abpLocalization }}}%} -
-
-
-
-
-
- - - - - {%{{{ booksType[row.type] }}}%} - - - - - {%{{{ row.publishDate | date }}}%} - - - - - {%{{{ row.price | currency }}}%} - - - -
-
-``` - -* 我们添加了图书列表页面的HTML代码. - -现在你可以在浏览器看到最终结果: - -![Book list final result](./images/bookstore-book-list.png) - -项目的文件系统结构: - -![Book list final result](./images/bookstore-angular-file-tree.png) - -在本教程中我们遵循了官方的[Angular风格指南](https://angular.io/guide/styleguide#file-tree). - -{{end}} +这很酷,因为我们没有编写任何代码来创建API控制器,但是现在我们有了一个可以正常使用的REST API! -### 下一章 +## 下一章 -参阅[第二章](part-2.md)了解创建,更新和删除图书. +参阅教程的[下一章](part-2.md). diff --git a/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png b/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png new file mode 100644 index 0000000000..c24e33c628 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-add-migration-authors.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png new file mode 100644 index 0000000000..9fba94d5bf Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png new file mode 100644 index 0000000000..f318ddfd31 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-author-to-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png b/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png new file mode 100644 index 0000000000..5fe4046794 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-added-authors-to-modals.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png new file mode 100644 index 0000000000..bbd865e44b Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-authors-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png new file mode 100644 index 0000000000..ac231ef7a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-service-proxy-author.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png b/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png new file mode 100644 index 0000000000..fde53834bb Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-author-domain-layer.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png b/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png new file mode 100644 index 0000000000..a20093d2c4 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-author-permissions.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png b/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png new file mode 100644 index 0000000000..6b8959df54 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-authors-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png b/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png new file mode 100644 index 0000000000..1620ebf150 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-and-booktype.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png new file mode 100644 index 0000000000..cbd7ec4a6a Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-list-3.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png index ecdb87c737..4982032e64 100644 Binary files a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png and b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png b/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png new file mode 100644 index 0000000000..0b81900307 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-dbmigrator-on-solution.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png new file mode 100644 index 0000000000..507bdd9406 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-button-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png new file mode 100644 index 0000000000..9c275c6d15 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-delete-actions.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png b/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png new file mode 100644 index 0000000000..141f9d14ee Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-getlist-result-network.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png new file mode 100644 index 0000000000..add95788a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v3.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png b/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png new file mode 100644 index 0000000000..f393c47875 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-javascript-proxy-console.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png b/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png new file mode 100644 index 0000000000..dda0dab4c1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-author-modal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png new file mode 100644 index 0000000000..12f1819688 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png new file mode 100644 index 0000000000..13f7da9285 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button-small.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png b/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png new file mode 100644 index 0000000000..749f7a013f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-permissions-ui.png differ diff --git a/docs/zh-Hans/Tutorials/images/generated-proxies-2.png b/docs/zh-Hans/Tutorials/images/generated-proxies-2.png new file mode 100644 index 0000000000..adad417c3d Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generated-proxies-2.png differ