diff --git a/docs/en/Tutorials/Part-1.md b/docs/en/Tutorials/Part-1.md index f846e34250..c684d1fc1c 100644 --- a/docs/en/Tutorials/Part-1.md +++ b/docs/en/Tutorials/Part-1.md @@ -61,7 +61,7 @@ After creating the project, you need to apply the initial migrations and create To run the project, right click to the {{if UI == "MVC"}} `Acme.BookStore.Web`{{end}} {{if UI == "NG"}} `Acme.BookStore.HttpApi.Host` {{end}} project and click **Set As StartUp Project**. And run the web project by pressing **CTRL+F5** (*without debugging and fast*) or press **F5** (*with debugging and slow*). {{if UI == "NG"}}You will see the Swagger UI for BookStore API.{{end}} -Further information, see the [running the application section](../../Getting-Started-{{if UI == "NG"}}Angular{{else}}AspNetCore-MVC{{end}}-Template#running-the-application).Getting-Started-AspNetCore-MVC-Template#running-the-application +Further information, see the [running the application section](../Getting-Started?UI={{UI}}#run-the-application). ![Set as startup project](./images/bookstore-start-project-{{UI_Text}}.png) @@ -335,7 +335,7 @@ INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES ### Create the application service -The next step is to create an [application service](../../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects: +The next step is to create an [application service](../Application-Services.md) to manage the books which will allow us the four basic functions: creating, reading, updating and deleting. Application layer is separated into two projects: * `Acme.BookStore.Application.Contracts` mainly contains your `DTO`s and application service interfaces. * `Acme.BookStore.Application` contains the implementations of your application services. diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png deleted file mode 100644 index a7d49a661b..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list-2.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png deleted file mode 100644 index f531e6f457..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-book-list.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png deleted file mode 100644 index 7cc96c8c94..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-template.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png deleted file mode 100644 index 79314dd2dc..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-localization-files-v2.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png deleted file mode 100644 index dfd4b5d8aa..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-new-book-button.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png deleted file mode 100644 index edf2826361..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration-v2.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png b/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png deleted file mode 100644 index 437c772503..0000000000 Binary files a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-swagger.png and /dev/null differ diff --git a/docs/zh-Hans/Tutorials/Part-1.md b/docs/zh-Hans/Tutorials/Part-1.md index d12e28290b..e427b290a6 100644 --- a/docs/zh-Hans/Tutorials/Part-1.md +++ b/docs/zh-Hans/Tutorials/Part-1.md @@ -1 +1,1078 @@ -TODO .... \ No newline at end of file +## ASP.NET Core {{UI_Value}} 教程 - 第一章 +````json +//[doc-params] +{ + "UI": ["MVC","NG"] +} +```` +{{ +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 +}} + +### 关于本教程 + +在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的应用程序. **{{DB_Text}}**将用作ORM提供者,前端使用{{UI_Value}} 和 JavaScript. + +ASP.NET Core {{UI_Value}} 系列教程包括三个3个部分: + +- **Part-1: 创建项目和书籍列表页面(本章)** +- [Part-2: 创建,编辑,删除书籍](Part-II.md) +- [Part-3: 集成测试](Part-III.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.DbMigrator` 应用程序. 它会应用所有迁移,完成流程后你会看到以下结果,数据库已经准备好了! + +![Migrations applied](./images/bookstore-migrations-applied-{{UI_Text}}.png) + +> 另外你也可以在 Visual Studio 包管理控制台运行 `Update-Database` 命令应用迁移. + +#### 初始化数据库表 + +![Initial database tables](./images/bookstore-database-tables-{{DB}}.png) + +### 运行应用程序 + +右键单击{{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}} + +默认的登录凭证: + +* **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`的类,如下所示: + +````C# +using System; +using Volo.Abp.Domain.Entities.Auditing; + +namespace Acme.BookStore +{ + public class Book : AuditedAggregateRoot + { + public string Name { get; set; } + + public BookType Type { get; set; } + + 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` 等). +* `Guid`是`Book`实体的主键类型. +* 使用 **数据注解** 为EF Core添加映射.或者你也可以使用 EF Core 自带的[fluent mapping API](https://docs.microsoft.com/en-us/ef/core/modeling). + +#### BookType枚举 + +上面所用到的`BookType`枚举定义如下: + +````C# +namespace Acme.BookStore +{ + public enum BookType + { + Undefined, + Adventure, + Biography, + Dystopia, + Fantastic, + Horror, + Science, + ScienceFiction, + Poetry + } +} +```` + +#### 将Book实体添加到DbContext中 + +{{if DB == "ef"}} + +EF Core需要你将实体和 `DbContext` 建立关联.最简单的做法是在`Acme.BookStore.EntityFrameworkCore`项目的`BookStoreDbContext`类中添加`DbSet`属性.如下所示: + +````C# +public class BookStoreDbContext : AbpDbContext +{ + public DbSet Books { get; set; } + ... +} +```` + +{{end}} + +{{if DB == "mongodb"}} + +添加 `IMongoCollection Books` 属性到 `Acme.BookStore.MongoDB` 项目的 `BookStoreMongoDbContext` 中. + +```csharp +public class BookStoreMongoDbContext : AbpMongoDbContext +{ + public IMongoCollection Users => Collection(); + public IMongoCollection Books => Collection();//<--added this line--> + //... +} +``` + +{{end}} + +{{if DB == "ef"}} + +#### 配置你的Book实体 + +在 `Acme.BookStore.EntityFrameworkCore` 项目中打开 `BookStoreDbContextModelCreatingExtensions.cs` 文件,并将以下代码添加到 `ConfigureBookStore` 方法的末尾以配置Book实体: + +````csharp +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` 扩展方法. + +{{end}} + +{{if DB == "mongodb"}} + +#### 添加种子数据 + +添加种子数据是可选的,但第一次运行时最好将初始数据添加到数据库中. ABP提供了[数据种子系统](https://docs.abp.io/en/abp/latest/Data-Seeding). 在 `*.Domain` 项目下创建派生 `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 BookStoreDataSeederContributor + : IDataSeedContributor, ITransientDependency + { + private readonly IRepository _bookRepository; + private readonly IGuidGenerator _guidGenerator; + + public BookStoreDataSeederContributor( + IRepository bookRepository, + IGuidGenerator guidGenerator) + { + _bookRepository = bookRepository; + _guidGenerator = guidGenerator; + } + + public async Task SeedAsync(DataSeedContext context) + { + if (await _bookRepository.GetCountAsync() > 0) + { + return; + } + + 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}} + +{{if DB == "ef"}} + +#### 添加新的Migration并更新数据库 + +这个启动模板使用了[EF Core Code First Migrations](https://docs.microsoft.com/en-us/ef/core/managing-schemas/migrations/)来创建并维护数据库结构.打开 **程序包管理器控制台(Package Manager Console) (PMC)** (工具/Nuget包管理器菜单) + +![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) + +这样就会在 `Migrations` 文件夹中创建一个新的migration类.然后执行 `Update-Database` 命令更新数据库结构: + +````bash +Update-Database +```` + +![bookstore-update-database-after-book-entity](./images/bookstore-update-database-after-book-entity.png) + +#### 添加示例数据 + +`Update-Database`命令在数据库中创建了`AppBooks`表. 打开数据库并输入几个示例行,以便在页面上显示它们: + +```mssql +INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +('f3c04764-6bfd-49e2-859e-3f9bfda6183e', '2018-07-01', '1984',3,'1949-06-08','19.84') + +INSERT INTO AppBooks (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') + +INSERT INTO AppBooks (Id,CreationTime,[Name],[Type],PublishDate,Price) VALUES +('4fa024a1-95ac-49c6-a709-6af9e4d54b54', '2018-07-02', 'Pet Sematary',5,'1983-11-14','23.7') +``` + +![bookstore-books-table](./images/bookstore-books-table.png) + +{{end}} + +### 创建应用服务 + +下一步是创建[应用服务](../Application-Services.md)来管理(创建,列出,更新,删除)书籍. 启动模板中的应用程序层分为两个项目: + +* `Acme.BookStore.Application.Contracts`主要包含你的DTO和应用程序服务接口. +* `Acme.BookStore.Application`包含应用程序服务的实现. + +#### BookDto + +在`Acme.BookStore.Application.Contracts`项目中创建一个名为`BookDto`的DTO类: + +````C# +using System; +using Volo.Abp.Application.Dtos; + +namespace Acme.BookStore +{ + public class BookDto : AuditedEntityDto + { + public string Name { get; set; } + + public BookType Type { get; set; } + + public DateTime PublishDate { get; set; } + + public float Price { get; set; } + } +} +```` + +* **DTO**类被用来在 **表示层** 和 **应用层** **传递数据**.查看[DTO文档](https://docs.abp.io/zh-Hans/abp/latest/Data-Transfer-Objects)查看更多信息. +* 为了在页面上展示书籍信息,`BookDto`被用来将书籍数据传递到表示层. +* `BookDto`继承自 `AuditedEntityDto`.跟上面定义的`Book`类一样具有一些审计属性. + +在将书籍返回到表示层时,需要将`Book`实体转换为`BookDto`对象. [AutoMapper](https://automapper.org)库可以在定义了正确的映射时自动执行此转换. 启动模板配置了AutoMapper,因此你只需在`Acme.BookStore.Application`项目的`BookStoreApplicationAutoMapperProfile`类中定义映射: + +````csharp +using AutoMapper; + +namespace Acme.BookStore +{ + public class BookStoreApplicationAutoMapperProfile : Profile + { + public BookStoreApplicationAutoMapperProfile() + { + CreateMap(); + } + } +} +```` + +#### CreateUpdateBookDto + +在`Acme.BookStore.Application.Contracts`项目中创建一个名为`CreateUpdateBookDto`的DTO类: + +````c# +using System; +using System.ComponentModel.DataAnnotations; +using Volo.Abp.AutoMapper; + +namespace Acme.BookStore +{ + public class CreateUpdateBookDto + { + [Required] + [StringLength(128)] + public string Name { get; set; } + + [Required] + public BookType Type { get; set; } = BookType.Undefined; + + [Required] + public DateTime PublishDate { get; set; } + + [Required] + public float Price { get; set; } + } +} +```` + +* 这个DTO类被用于在创建或更新书籍的时候从用户界面获取图书信息. +* 它定义了数据注释属性(如`[Required]`)来定义属性的验证. DTO由ABP框架[自动验证](https://docs.abp.io/zh-Hans/abp/latest/Validation). + +就像上面的`BookDto`一样,创建一个从`CreateUpdateBookDto`对象到`Book`实体的映射: + +````csharp +using AutoMapper; + +namespace Acme.BookStore +{ + public class BookStoreApplicationAutoMapperProfile : Profile + { + public BookStoreApplicationAutoMapperProfile() + { + CreateMap(); + CreateMap(); //<--added this line--> + } + } +} +```` + +#### IBookAppService + +在`Acme.BookStore.Application.Contracts`项目中定义一个名为`IBookAppService`的接口: + +````C# +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; + +namespace Acme.BookStore +{ + public interface IBookAppService : + ICrudAppService< //定义了CRUD方法 + BookDto, //用来展示书籍 + Guid, //Book实体的主键 + PagedAndSortedResultRequestDto, //获取书籍的时候用于分页和排序 + CreateUpdateBookDto, //用于创建书籍 + CreateUpdateBookDto> //用于更新书籍 + { + + } +} +```` + +* 框架定义应用程序服务的接口**不是必需的**. 但是,它被建议作为最佳实践. +* `ICrudAppService`定义了常见的**CRUD**方法:`GetAsync`,`GetListAsync`,`CreateAsync`,`UpdateAsync`和`DeleteAsync`. 你可以从空的`IApplicationService`接口继承并手动定义自己的方法. +* `ICrudAppService`有一些变体, 你可以在每个方法中使用单独的DTO,也可以分别单独指定. + +#### BookAppService + +在`Acme.BookStore.Application`项目中实现名为`BookAppService`的`IBookAppService`: + +````C# +using System; +using Volo.Abp.Application.Dtos; +using Volo.Abp.Application.Services; +using Volo.Abp.Domain.Repositories; + +namespace Acme.BookStore +{ + public class BookAppService : + CrudAppService, + IBookAppService + { + public BookAppService(IRepository repository) + : base(repository) + { + + } + } +} +```` + +* `BookAppService`继承了`CrudAppService<...>`.它实现了上面定义的CRUD方法. +* `BookAppService`注入`IRepository `,这是`Book`实体的默认仓储. ABP自动为每个聚合根(或实体)创建默认仓储. 请参阅[仓储文档](https://docs.abp.io/zh-Hans/abp/latest/Repositories) +* `BookAppService`使用`IObjectMapper`将`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控制器. + +#### Swagger UI + +启动模板配置为使用[Swashbuckle.AspNetCore](https://github.com/domaindrivendev/Swashbuckle.AspNetCore)运行[swagger UI](https://swagger.io/tools/swagger-ui/). 运行应用程序并在浏览器中输入`https://localhost:XXXX/swagger/`(用你自己的端口替换XXXX)作为URL. + +你会看到一些内置的接口和`Book`的接口,它们都是REST风格的: + +![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 +```` + +检查数据库中的`Books`表以查看新书. 你可以自己尝试`get`,`update`和`delete`功能. + +### 创建书籍页面 + +现在我们来创建一些可见和可用的东西,取代经典的MVC,我们使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/en-us/aspnet/core/tutorials/razor-pages/razor-pages-start). + +在 `Acme.BookStore.Web`项目的`Pages`文件夹下创建一个新的文件夹叫`Books`并添加一个名为`Index.cshtml`的Razor Page. + +![bookstore-add-index-page](./images/bookstore-add-index-page-v2.png) + +打开`Index.cshtml`并把内容修改成下面这样: + +**Index.cshtml:** + +````html +@page +@using Acme.BookStore.Web.Pages.Books +@inherits Acme.BookStore.Web.Pages.BookStorePage +@model IndexModel + +

Books

+```` + +* 此代码更改了Razor View Page Model的默认继承,因此它从`BookStorePage`类(而不是`PageModel`)继承.启动模板附带的`BookStorePage`类,提供所有页面使用的一些共享属性/方法. +* 确保`IndexModel`(Index.cshtml.cs)具有`Acme.BookStore.Web.Pages.Books`命名空间,或者在`Index.cshtml`中更新它. + +**Index.cshtml.cs:** + +```csharp +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace Acme.BookStore.Web.Pages.Books +{ + public class IndexModel : PageModel + { + public void OnGet() + { + + } + } +} +``` + +#### 将Books页面添加到主菜单 + +打开`Menus`文件夹中的 `BookStoreMenuContributor` 类,在`ConfigureMainMenuAsync`方法的底部添加如下代码: + +````csharp +//... +namespace Acme.BookStore.Web.Menus +{ + public class BookStoreMenuContributor : IMenuContributor + { + private async Task ConfigureMainMenuAsync(MenuConfigurationContext context) + { + //<-- added the below code + context.Menu.AddItem( + new ApplicationMenuItem("BooksStore", l["Menu:BookStore"]) + .AddItem( + new ApplicationMenuItem("BooksStore.Books", l["Menu:Books"], url: "/Books") + ) + ); + //--> + } + } +} +```` + +{{end}} + +#### 本地化菜单 + +本地化文本位于`Acme.BookStore.Domain.Shared`项目的`Localization/BookStore`文件夹下: + +![bookstore-localization-files](./images/bookstore-localization-files-v2.png) + +打开`en.json`文件,将`Menu:BookStore`和`Menu:Books`键的本地化文本添加到文件末尾: + +````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:Books": "Books", + "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下Books子菜单项就会跳转到新增的书籍页面. + +#### 书籍列表 + +我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables](https://datatables.net/)可以完全通过AJAX工作,速度快,并提供良好的用户体验. Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,而需要在页面中引用样式和脚本文件. + +##### Index.cshtml + +将`Pages/Books/Index.cshtml`改成下面的样子: + +````html +@page +@inherits Acme.BookStore.Web.Pages.BookStorePage +@model Acme.BookStore.Web.Pages.Books.IndexModel +@section scripts +{ + +} + + +

@L["Books"]

+
+ + + + + @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/Books/`文件夹中创建 `index.js`文件 + +![bookstore-index-js-file](./images/bookstore-index-js-file-v2.png) + +`index.js`的内容如下: + +````js +$(function () { + 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 开发 + +#### 创建books页面 + +是时候创建可见和可用的东西了!开发ABP Angular前端应用程序时,需要使用一些工具: + +- [Angular CLI](https://angular.io/cli) 用于创建模块,组件和服务. +- [NGXS](https://ngxs.gitbook.io/ngxs/) 用于管理状态库. +- [Ng Bootstrap](https://ng-bootstrap.github.io/#/home) 用做UI组件库. +- [Visual Studio Code](https://code.visualstudio.com/) 用做代码编辑器 (你可以选择自己喜欢的编辑器). + +#### 安装 NPM 包 + +在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包: + +```bash +yarn +``` + +#### BooksModule + +运行以下命令创建一个名为 `BooksModule` 的新模块: + +```bash +yarn ng generate module books --route books --module app.module +``` + +![Generating books module](./images/bookstore-creating-books-module-terminal.png) + +#### 路由 + +打开位于 `src\app` 目录下的 `app-routing.module.ts` 文件. 添加新的 `import` 和替换 `books` 路径: + +```js +import { ApplicationLayoutComponent } from '@abp/ng.theme.basic'; //==> added this line to imports <== + +//...replaced original books path with the below +{ + path: 'books', + component: ApplicationLayoutComponent, + loadChildren: () => import('./books/books.module').then(m => m.BooksModule), + data: { + routes: { + name: '::Menu:Books', + iconClass: 'fas fa-book' + } as ABP.Route + }, +} +``` + +* `ApplicationLayoutComponent` 配置将应用程序布局设置为新页面, 我们添加了 `data` 对象. `name` 是菜单项的名称,`iconClass` 是菜单项的图标. + +运行 `yarn start` 等待Angular为应用程序启动服务: + +```bash +yarn start +``` + +打开浏览器导航到 http://localhost:4200/books. 你会看到一个带有 "*books works!*" 的空白页. + +![initial-books-page](./images/bookstore-initial-books-page-with-layout.png) + +#### Book 列表组件 + +用以下内容替换 `books.component.html`: + +```html + +``` + +在命令行运行以下命令,生成名为 book-list 的新组件: + +```bash +yarn ng generate component books/book-list +``` + +![Creating books list](./images/bookstore-creating-book-list-terminal.png) + +打开 `app\books` 目录下的 `books.module.ts` 文件,使用以下内容替换它: + +```js +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; + +import { BooksRoutingModule } from './books-routing.module'; +import { BooksComponent } from './books.component'; +import { BookListComponent } from './book-list/book-list.component'; +import { SharedModule } from '../shared/shared.module'; //<== added this line ==> + +@NgModule({ + declarations: [BooksComponent, BookListComponent], + imports: [ + CommonModule, + BooksRoutingModule, + SharedModule, //<== added this line ==> + ] +}) +export class BooksModule { } +``` + +* 我们导入了 `SharedModule` 并添加到 `imports` 数组. + +打开 `app\books` 目录下的 `books-routing.module.ts` 文件用以下内容替换它: + +```js +import { NgModule } from '@angular/core'; +import { Routes, RouterModule } from '@angular/router'; + +import { BooksComponent } from './books.component'; +import { BookListComponent } from './book-list/book-list.component'; //<== added this line ==> + +//<== replaced routes ==> +const routes: Routes = [ + { + path: '', + component: BooksComponent, + children: [{ path: '', component: BookListComponent }], + }, +]; + +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule] +}) +export class BooksRoutingModule { } +``` + +* 我们导入了 `BookListComponent` 并替换 `routes` 常量. + +我们将看到books页面的 **book-list works!**: + +![Initial book list page](./images/bookstore-initial-book-list-page.png) + +#### 创建 BooksState + +运行以下命令创建名为 `BooksState` 的新state: + +```bash +npx @ngxs/cli --name books --directory src/app/books +``` + +* 此命令在 `src/app/books/state` 文件夹下创建了 `books.state.ts` 和 `books.actions.ts` 文件. 参阅 [NGXS CLI文档](https://www.ngxs.io/plugins/cli)了解更多. + +将 `BooksState` 导入到 `src/app` 文件夹中的 `app.module.ts` 中. 然后添加 `BooksState` 到 `NgxsModule` 的 `forRoot` 静态方法,作为该方法的第一个参数的数组元素. + +```js +// ... +import { BooksState } from './books/state/books.state'; //<== imported BooksState ==> + +@NgModule({ + imports: [ + // other imports + + NgxsModule.forRoot([BooksState]), //<== added BooksState ==> + + //other imports + ], + // ... +}) +export class AppModule {} +``` + +#### 生成代理 + +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) + +#### GetBooks 动作 + +动作可以被认为是一个命令,它应该触发某些事情发生,或者是已经发生的事情的结果事件.[See NGXS Actions文档](https://www.ngxs.io/concepts/actions). + +打开 `app/books/state` 目录下的 `books.actions.ts` 文件用以下内容替换它: + +```js +export class GetBooks { + static readonly type = '[Books] Get'; +} +``` + +#### 实现 BooksState + +打开 `app/books/state` 目录下的 `books.state.ts` 文件用以下内容替换它: + +```js +import { PagedResultDto } from '@abp/ng.core'; +import { State, Action, StateContext, Selector } from '@ngxs/store'; +import { GetBooks } from './books.actions'; +import { BookService } from '../../app/shared/services'; +import { tap } from 'rxjs/operators'; +import { Injectable } from '@angular/core'; +import { BookDto } from '../../app/shared/models'; + +export class BooksStateModel { + public book: PagedResultDto; +} + +@State({ + name: 'BooksState', + defaults: { book: {} } as BooksStateModel, +}) +@Injectable() +export class BooksState { + @Selector() + static getBooks(state: BooksStateModel) { + return state.book.items || []; + } + + constructor(private bookService: BookService) {} + + @Action(GetBooks) + get(ctx: StateContext) { + return this.bookService.getListByInput().pipe( + tap((booksResponse) => { + ctx.patchState({ + book: booksResponse, + }); + }) + ); + } +} +``` + +* 我们添加了book属性到BooksStateModel模态框. +* 我们添加了 `GetBooks` 动作. 它通过 ABP CLI生成的 `BooksService` 检索图书数据. +* `NGXS` 需要在不订阅get函数的情况下返回被观察对象. + +#### BookListComponent + +打开 `app\books\book-list` 目录下的 `book-list.component.ts` 用以下内容替换它: + +```js +import { Component, OnInit } from '@angular/core'; +import { Select, Store } from '@ngxs/store'; +import { Observable } from 'rxjs'; +import { finalize } from 'rxjs/operators'; +import { BookDto, BookType } from '../../app/shared/models'; +import { GetBooks } from '../state/books.actions'; +import { BooksState } from '../state/books.state'; + +@Component({ + selector: 'app-book-list', + templateUrl: './book-list.component.html', + styleUrls: ['./book-list.component.scss'], +}) +export class BookListComponent implements OnInit { + @Select(BooksState.getBooks) + books$: Observable; + + booksType = BookType; + + loading = false; + + constructor(private store: Store) {} + + ngOnInit() { + this.get(); + } + + get() { + this.loading = true; + this.store + .dispatch(new GetBooks()) + .pipe(finalize(() => (this.loading = false))) + .subscribe(() => {}); + } +} +``` + +* 我们添加了 `get` 函数获取books更新store. +* 有关 `NGXS` 特性的更多信息请参见NGXS文档中的[Dispatching actions](https://ngxs.gitbook.io/ngxs/concepts/store#dispatching-actions)和[Select](https://ngxs.gitbook.io/ngxs/concepts/select). + +打开 `app\books\book-list` 目录下的 `book-list.component.html` 用以下内容替换它: + +```html +
+
+
+
+
+ {%{{{ "::Menu:Books" | abpLocalization }}}%} +
+
+
+
+
+
+ + + + + {%{{{ "::Name" | abpLocalization }}}%} + {%{{{ "::Type" | abpLocalization }}}%} + {%{{{ "::PublishDate" | abpLocalization }}}%} + {%{{{ "::Price" | abpLocalization }}}%} + + + + + {%{{{ data.name }}}%} + {%{{{ booksType[data.type] }}}%} + {%{{{ data.publishDate | date }}}%} + {%{{{ data.price }}}%} + + +
+
+``` + +* 我们添加了图书列表页面的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}} + +### 下一章 + +参阅[第二章](part-2.md)了解创建,更新和删除图书. \ No newline at end of file diff --git a/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png b/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png new file mode 100644 index 0000000000..e8243fedc7 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-actions-buttons.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-add-create-dialog-v2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-create-dialog-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-add-create-dialog-v2.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png b/docs/zh-Hans/Tutorials/images/bookstore-add-edit-dialog.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-edit-dialog.png rename to docs/zh-Hans/Tutorials/images/bookstore-add-edit-dialog.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-add-index-page-v2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-add-index-page-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-add-index-page-v2.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png new file mode 100644 index 0000000000..a3197b6457 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-angular-file-tree.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png b/docs/zh-Hans/Tutorials/images/bookstore-appservice-tests.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-appservice-tests.png rename to docs/zh-Hans/Tutorials/images/bookstore-appservice-tests.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png new file mode 100644 index 0000000000..a460d4241b Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-list-2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-book-list.png b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png new file mode 100644 index 0000000000..9e6cc9e010 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-book-list.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png b/docs/zh-Hans/Tutorials/images/bookstore-books-table-actions.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table-actions.png rename to docs/zh-Hans/Tutorials/images/bookstore-books-table-actions.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png b/docs/zh-Hans/Tutorials/images/bookstore-books-table.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-books-table.png rename to docs/zh-Hans/Tutorials/images/bookstore-books-table.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png b/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png new file mode 100644 index 0000000000..a80b180f1c Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-confirmation-popup.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png b/docs/zh-Hans/Tutorials/images/bookstore-create-dialog-2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog-2.png rename to docs/zh-Hans/Tutorials/images/bookstore-create-dialog-2.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png b/docs/zh-Hans/Tutorials/images/bookstore-create-dialog.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-create-dialog.png rename to docs/zh-Hans/Tutorials/images/bookstore-create-dialog.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png new file mode 100644 index 0000000000..b9eb38b8b7 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-create-project-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png b/docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png new file mode 100644 index 0000000000..f453b20279 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-create-project-mvc.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png new file mode 100644 index 0000000000..6f19dcc7bf Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-creating-book-list-terminal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png b/docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png new file mode 100644 index 0000000000..ec9ef4c42f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-creating-books-module-terminal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png new file mode 100644 index 0000000000..857b10de5b Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-ef.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png new file mode 100644 index 0000000000..8d78bd9a54 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-database-tables-mongodb.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-edit-button.png b/docs/zh-Hans/Tutorials/images/bookstore-edit-button.png new file mode 100644 index 0000000000..bfc1c64797 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-edit-button.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png b/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png new file mode 100644 index 0000000000..2a02802bb9 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-empty-new-book-modal.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png b/docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png new file mode 100644 index 0000000000..4f41829f0d Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-final-actions-dropdown.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png b/docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png new file mode 100644 index 0000000000..be7a919017 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-generate-state-books.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png b/docs/zh-Hans/Tutorials/images/bookstore-homepage.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-homepage.png rename to docs/zh-Hans/Tutorials/images/bookstore-homepage.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v2.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-index-js-file-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-index-js-file-v2.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png b/docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png new file mode 100644 index 0000000000..591cffb121 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-initial-book-list-page.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png b/docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png new file mode 100644 index 0000000000..629ad46444 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-initial-books-page-with-layout.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png new file mode 100644 index 0000000000..542cda209c Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-localization-files-v2.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png b/docs/zh-Hans/Tutorials/images/bookstore-menu-items.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-menu-items.png rename to docs/zh-Hans/Tutorials/images/bookstore-menu-items.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png new file mode 100644 index 0000000000..0724e4ae8f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png new file mode 100644 index 0000000000..d59c0ce1d3 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-migrations-applied-mvc.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png new file mode 100644 index 0000000000..8112fe1352 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-button.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png new file mode 100644 index 0000000000..72513de6e5 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form-v2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png new file mode 100644 index 0000000000..95de64f8d4 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-book-form.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png b/docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png new file mode 100644 index 0000000000..97bf7fc7c1 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-new-menu-item.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png b/docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png new file mode 100644 index 0000000000..a640eb2681 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-open-package-manager-console.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png new file mode 100644 index 0000000000..2baea20236 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration-v2.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png b/docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-pmc-add-book-migration.png rename to docs/zh-Hans/Tutorials/images/bookstore-pmc-add-book-migration.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png b/docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png new file mode 100644 index 0000000000..cf6145e03f Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-service-terminal-output.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png new file mode 100644 index 0000000000..07d064a880 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-angular.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png b/docs/zh-Hans/Tutorials/images/bookstore-solution-structure-mvc.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-visual-studio-solution-v3.png rename to docs/zh-Hans/Tutorials/images/bookstore-solution-structure-mvc.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png new file mode 100644 index 0000000000..08abf845a8 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-start-project-angular.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png b/docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png new file mode 100644 index 0000000000..133dc6f131 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-start-project-mvc.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png b/docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png new file mode 100644 index 0000000000..66d630bb56 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-swagger-book-dto-properties.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-swagger.png b/docs/zh-Hans/Tutorials/images/bookstore-swagger.png new file mode 100644 index 0000000000..3ce36a11bc Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-swagger.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png b/docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist-network.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist-network.png rename to docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist-network.png diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png b/docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-js-proxy-getlist.png rename to docs/zh-Hans/Tutorials/images/bookstore-test-js-proxy-getlist.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png new file mode 100644 index 0000000000..6a8947238e Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-angular.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-mvc.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-test-projects-v2.png rename to docs/zh-Hans/Tutorials/images/bookstore-test-projects-mvc.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png new file mode 100644 index 0000000000..8701164d75 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-test-projects-v2.png differ diff --git a/docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png b/docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png new file mode 100644 index 0000000000..4889f4f757 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-update-database-after-book-entity.png differ diff --git a/docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png b/docs/zh-Hans/Tutorials/images/bookstore-user-management.png similarity index 100% rename from docs/zh-Hans/Tutorials/AspNetCore-Mvc/images/bookstore-user-management.png rename to docs/zh-Hans/Tutorials/images/bookstore-user-management.png diff --git a/docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png b/docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png new file mode 100644 index 0000000000..307e3516a5 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/bookstore-visual-studio-solution-v3.png differ diff --git a/docs/zh-Hans/Tutorials/images/generate-proxy-command.png b/docs/zh-Hans/Tutorials/images/generate-proxy-command.png new file mode 100644 index 0000000000..f850ce07a2 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generate-proxy-command.png differ diff --git a/docs/zh-Hans/Tutorials/images/generated-proxies.png b/docs/zh-Hans/Tutorials/images/generated-proxies.png new file mode 100644 index 0000000000..9e466e7d55 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/generated-proxies.png differ diff --git a/docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png b/docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png new file mode 100644 index 0000000000..c9e2fc0e65 Binary files /dev/null and b/docs/zh-Hans/Tutorials/images/mozilla-self-signed-cert-error.png differ