@ -2,26 +2,10 @@
````json
//[doc-params]
{
"UI": ["MVC","NG"],
"UI": ["MVC","Blazor","BlazorServer"," 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
}}
## 关于本教程
在本系列教程中, 你将构建一个名为 `Acme.BookStore` 的用于管理书籍及其作者列表的基于ABP的应用程序. 它是使用以下技术开发的:
@ -44,16 +28,30 @@ end
## 下载源码
本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了两 种可供下载的源码组合:
本教程根据你的**UI** 和 **Database**偏好有多个版,我们准备了几 种可供下载的源码组合:
* [MVC (Razor Pages) UI 与 EF Core ](https://github.com/abpframework/abp-samples/tree/master/BookStore-Mvc-EfCore )
* [Blazor UI 与 EF Core ](https://github.com/abpframework/abp-samples/tree/master/BookStore-Blazor-EfCore )
* [Angular UI 与 MongoDB ](https://github.com/abpframework/abp-samples/tree/master/BookStore-Angular-MongoDb )
> 如果你在Windows中遇到 "文件名太长" or "解压错误", 很可能与Windows最大文件路径限制有关. Windows文件路径的最大长度为250字符. 为了解决这个问题,参阅 [在Windows 10中启用长路径 ](https://docs.microsoft.com/en-us/windows/win32/fileio/maximum-file-path-limitation?tabs=cmd#enable-long-paths-in-windows-10-version-1607-and-later ).
> 如果你遇到与Git相关的长路径错误, 尝试使用下面的命令在Windows中启用长路径. 参阅 https://github.com/msysgit/msysgit/wiki/Git-cannot-create-a-file-or-directory-with-a-long-path
> `git config --system core.longpaths true`
{{if UI == "MVC" & & DB == "EF"}}
### 视频教程
本章也被录制为视频教程 ** < a href = "https://www.youtube.com/watch?v=UDNlLiPiBiw&list=PLsNclT2aHJcPNaCf7Io3DbMN6yAk_DgWJ&index=2" target = "_blank" > 发布在YouTube</ a > **.
{{end}}
{{if UI == "MVC"}}
## 动态JavaScript代理
通常在 **JavaScript** 端通过AJAX调用HTTP API端点. 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法.
在 **JavaScript** 端通过AJAX调用HTTP API端点是常见的做法 . 你可以使用 `$.ajax` 或其他工具来调用端点. 但是ABP提供了更好的方法.
ABP**动态**为所有API端点创建 ** [JavaScript代理 ](../UI/AspNetCore/Dynamic-JavaScript-Proxies.md )**. 所以你可以像调用**Javascript本地方法**一样使用任何**端点**.
@ -79,21 +77,21 @@ acme.bookStore.books.book.getList({}).done(function (result) { console.log(resul

Let's **create a new book** using the `create` function:
让我们使用 `create` 函数**创建一本书**:
````js
acme.bookStore.books.book.create({
name: 'Foundation',
type: 7,
publishDate: '1951-05-24',
price: 21.5
}).then(function (result) {
console.log('successfully created the book with id: ' + result.id);
acme.bookStore.books.book.create({
name: 'Foundation',
type: 7,
publishDate: '1951-05-24',
price: 21.5
}).then(function (result) {
console.log('successfully created the book with id: ' + result.id);
});
````
> 如果你下载了本教程的源代码并按照示例中的步骤操作,你需要传递`authorId`参数给创建方法以**创建一本新书**.
您应该在控制台中看到类似以下的消息:
````text
@ -102,7 +100,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
检查数据库中的 `Books` 表你会看到新的一行. 你可以自己尝试使用 `get` , `update` 和 `delete` 函数.
我们将利用这些动态代理功能在接下来的章节来 与服务器通信.
在接下来的章节,我们将利用这些动态代理函数 与服务器通信.
{{end}}
@ -112,7 +110,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
本地化文本位于 `Acme.BookStore.Domain.Shared` 项目的 `Localization/BookStore` 文件夹下:


打开 `en.json` (*英文翻译*)文件并更改内容,如下所示:
@ -154,7 +152,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
* 为按钮项添加 `Menu:` 前缀.
* 使用 `Enum:<enum-type>:<enum-value>` 命名约定来本地化枚举成员. 当您这样做时ABP可以在某些适当的情况下自动将枚举本地化.
如果未在本地化文件中定义文本,则文本将**回退**到本地化键(作为 ASP.NET Core的标准行为).
如果未在本地化文件中定义文本,则文本将**回退**到本地化键(ASP.NET Core的标准行为).
> ABP本地化系统建立在[ASP.NET Core标准本地化](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization)系统之上,并以多种方式进行了扩展. 有关详细信息请参见[本地化文档](../Localization.md).
@ -162,7 +160,7 @@ successfully created the book with id: 439b0ea8-923e-8e1e-5d97-39f2c7ac4246
## 创建图书页面
是时候创建可见的和可用的东西了! 代替经典的MVC, 我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start).
是时候创建可见的和可用的东西了! 我们将使用微软推荐的[Razor Pages UI](https://docs.microsoft.com/zh-cn/aspnet/core/tutorials/razor-pages/razor-pages-start),而不是经典的MVC .
在 `Acme.BookStore.Web` 项目的 `Pages` 文件夹下创建一个名为新的 `Books` 的文件夹. 然后在文件夹右键选择 **添加 > Razor Page** 菜单. 输入名称 `Index` :
@ -189,13 +187,13 @@ namespace Acme.BookStore.Web.Pages.Books
{
public void OnGet()
{
}
}
}
```
### 将Book 页面添加到主菜单
### 将图书 页面添加到主菜单
打开 `Menus` 文件夹中的 `BookStoreMenuContributor` 类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码:
@ -223,9 +221,7 @@ context.Menu.AddItem(
### 图书列表
We will use the [Datatables.net ](https://datatables.net/ ) jQuery library to show the book list. Datatables library completely work via AJAX, it is fast, popular and provides a good user experience.
我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示页面上的表格列表. [Datatables ](https://datatables.net/ )可以完全通过AJAX工作,速度快,并提供良好的用户体验.
我们将使用[Datatables.net](https://datatables.net/)JQuery插件来显示图书列表. [Datatables ](https://datatables.net/ )可以完全通过AJAX工作,速度快,并提供良好的用户体验.
> Datatables插件在启动模板中配置,因此你可以直接在任何页面中使用它,无需在页面中引用样式和脚本文件.
@ -261,7 +257,7 @@ We will use the [Datatables.net](https://datatables.net/) jQuery library to show
在 `Pages/Books/` 文件夹中创建 `index.js` 文件


`index.js` 的内容如下:
@ -321,8 +317,8 @@ $(function () {
````
* `abp.localization.getResource` 获取一个函数,该函数用于使用服务器端定义的相同JSON文件对文本进行本地化. 通过这种方式你可以与客户端共享本地化值.
* `abp.libs.datatables.normalizeConfiguration` 是另 一个辅助方法.不是必须的, 但是它通过为缺少的选项提供常规值来简化数据表 配置.
* `abp.libs.datatables.createAjax` 是帮助ABP的动态JavaScript API代理跟[Datatable](https://datatables.net/)的格式相适应的辅助方法 .
* `abp.libs.datatables.normalizeConfiguration` 是一个辅助方法.不是必须的, 但是它通过为缺少的选项提供约定的值来简化[Datatables](https://datatables.net/) 配置.
* `abp.libs.datatables.createAjax` 是另一个辅助方法,用来适配ABP的动态JavaScript API代理和[Datatable](https://datatables.net/)期望的参数格式 .
* `acme.bookStore.books.book.getList` 是动态JavaScript代理函数(上面已经介绍过了)
* [luxon ](https://moment.github.io/luxon/ ) 库也是该解决方案中预先配置的标准库,你可以轻松地执行日期/时间操作.
@ -334,15 +330,13 @@ $(function () {

这是一个完全正常工作的服务端分页,排序和本地化的图书列表.
{{end}}
这是一个可以正常工作的,服务端分页,排序和本地化的图书列表.
{{if UI == "NG"}}
## 安装NPM包
>注意: 本教程基于ABP Framework v3.0.3 +. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md).
>注意: 本教程基于ABP Framework v3.1.0 +. 如果你的项目版本较旧,请升级您的解决方案. 如果要升级现有的v2.x项目,请参阅[迁移指南](../UI/Angular/Migration-Guide-v3.md).
在 `angular` 目录下打开命令行窗口,选择 `yarn` 命令安装NPM包:
@ -357,9 +351,7 @@ yarn
- [Ng Bootstrap ](https://ng-bootstrap.github.io/#/home ) 用做UI组件库.
- [ngx-datatable ](https://swimlane.gitbook.io/ngx-datatable/ ) 用做 datatable 类库.
### BookModule
运行以下命令创建一个名为 `BookModule` 的新模块:
运行以下命令在angular应用程序根目录创建一个名为 `BookModule` 的新模块:
```bash
yarn ng generate module book --module app --routing --route books
@ -417,7 +409,7 @@ const routes: Routes = [
];
````
现在打开 `src/app/route.provider.ts` 以下 替换 `configureRoutes` 函数:
现在打开 `src/app/route.provider.ts` 替换 `configureRoutes` 函数为以下代码 :
```js
function configureRoutes(routes: RoutesService) {
@ -450,27 +442,31 @@ function configureRoutes(routes: RoutesService) {
`RoutesService` 是ABP框架提供的用于配置主菜单和路由的服务.
* `path` 路由的URL.
* `path` 路由的URL.
* `name` 菜单项的名称(参阅[本地化文档](../UI/Angular/Localization.md)了解更多).
* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标).
* `order` 菜单项的排序.我们定义了101,它显示在 "Administration" 项的后面.
* `layout` BooksModule路由的布局. 可以定义 `eLayoutType.application` , `eLayoutType.account` 或 `eLayoutType.empty` .
* `iconClass` 菜单项的图标(你可以使用默认的[Font Awesome](https://fontawesome.com/)图标).
* `order` 菜单项的排序.
* `layout` BooksModule路由的布局. (有三个预定义的布局类型: `eLayoutType.application` , `eLayoutType.account` 或 `eLayoutType.empty` ).
更多信息请参阅[RoutesService 文档](../UI/Angular/Modifying-the-Menu.md#via-routesservice).
更多信息请参阅[RoutesService 文档](https://docs.abp.io/en/abp/latest/UI/Angular/Modifying-the-Menu.md#via-routesservice).
### 生成服务代理
### 生成代理
[ABP CLI ](../CLI.md ) 提供 `generate-proxy` 命令为HTTP APIs生成客户端代理.有了这些代理,在客户端使用HTTP APIs变得更加方便. 运行 `generate-proxy` 命令前, 你的 host 必须正在运行.
ABP CLI提供了 `generate-proxy` 命令为你的服务HTTP API生成客户端代理简化客户端使用服务的成本. 运行 `generate-proxy` 命令前你的host必须正在运行. 参阅 [CLI 文档 ](../CLI.md ).
> **警告** : 使用IIS Express时有一个问题; 它不允许从另一个进程连接应用程序. 如果你使用Visual Studio, 在运行按钮的下拉框中选择`Acme.BookStore.HttpApi.Host`,不要选择IIS Express, 如下图:
在 `angular` 文件夹下运行以下命令:

启动host应用程序后,在 `angular` 文件夹下运行以下命令:
```bash
abp generate-proxy
```
生成的文件如下 :
这个命令将在`/src/app/proxy/books`文件夹下产生以下文件 :


### BookComponent
@ -479,8 +475,7 @@ abp generate-proxy
```js
import { ListService, PagedResultDto } from '@abp/ng.core';
import { Component, OnInit } from '@angular/core';
import { BookDto } from './models';
import { BookService } from './services';
import { BookService, BookDto } from '@proxy/books';
@Component ({
selector: 'app-book',
@ -494,7 +489,7 @@ export class BookComponent implements OnInit {
constructor(public readonly list: ListService, private bookService: BookService) {}
ngOnInit() {
const bookStreamCreator = (query) => this.bookService.getListByInput (query);
const bookStreamCreator = (query) => this.bookService.getList(query);
this.list.hookToQuery(bookStreamCreator).subscribe((response) => {
this.book = response;
@ -503,8 +498,8 @@ export class BookComponent implements OnInit {
}
```
* 我们注入了生成的 `BookService` .
* 我们实现了 [ListService ](https://docs.abp.io/en/abp/latest/UI/Angular/List-Service ),它是一个公用服务,提供了简单 的分页,排序和搜索.
* 我们引入并 注入了生成的 `BookService` .
* 我们使用 [ListService ](../UI/Angular/List-Service.md ),它是一个工具服务,提供了易用 的分页,排序和搜索.
打开 `/src/app/book/book.component.html` 用以下内容替换它:
@ -545,9 +540,138 @@ export class BookComponent implements OnInit {
现在你可以在浏览器看到最终结果:


{{end}}
{{else if UI == "Blazor" || UI == "BlazorServer"}}
## 创建图书页面
是时候创建可见和可用的东西了! 右击`Acme.BookStore.Blazor`项目下的`Pages`文件夹,新建一个名为`Books.razor`的**razor组件**.

用以下内容替换这个组件的内容:
````html
@page "/books"
< h2 > Books< / h2 >
@code {
}
````
### 将图书页面添加到主菜单
打开`Blazor`项目中的`BookStoreMenuContributor`类,在 `ConfigureMainMenuAsync` 方法的底部添加如下代码:
````csharp
context.Menu.AddItem(
new ApplicationMenuItem(
"BooksStore",
l["Menu:BookStore"],
icon: "fa fa-book"
).AddItem(
new ApplicationMenuItem(
"BooksStore.Books",
l["Menu:Books"],
url: "/books"
)
)
);
````
运行项目,使用用户名 `admin` 和密码 `1q2w3E*` 登录到应用程序. 看到新菜单项已添加到顶部栏:

点击BookStore下的Books子菜单项就会跳转到空的图书页面.
### 图书列表
我们将使用[Blazorise library](https://blazorise.com/)作为UI组件.它是一个强大的库,支持主要的HTML/CSS框架,包括Bootstrap.
ABP提供了一个通用的基类,`AbpCrudPageBase< ... > `,用来创建CRUD风格的页面.这个基类兼容用来构建`IBookAppService`的`ICrudAppService`.所以我们从`AbpCrudPageBase`继承,获得标准CRUD的默认实现.
打开`Books.razor` 并把内容修改成下面这样:
````xml
@page "/books"
@using Volo.Abp.Application.Dtos
@using Acme.BookStore.Books
@using Acme.BookStore.Localization
@using Microsoft.Extensions.Localization
@inject IStringLocalizer< BookStoreResource > L
@inherits AbpCrudPageBase< IBookAppService , BookDto , Guid , PagedAndSortedResultRequestDto , CreateUpdateBookDto >
< Card >
< CardHeader >
< h2 > @L["Books"]< / h2 >
< / CardHeader >
< CardBody >
< DataGrid TItem = "BookDto"
Data="Entities"
ReadData="OnDataGridReadAsync"
TotalItems="TotalCount"
ShowPager="true"
PageSize="PageSize">
< DataGridColumns >
< DataGridColumn TItem = "BookDto"
Field="@nameof(BookDto.Name)"
Caption="@L["Name"]">< / DataGridColumn >
< DataGridColumn TItem = "BookDto"
Field="@nameof(BookDto.Type)"
Caption="@L["Type"]">
< DisplayTemplate >
@L [$"Enum:BookType:{(int)context.Type}"]
< / DisplayTemplate >
< / DataGridColumn >
< DataGridColumn TItem = "BookDto"
Field="@nameof(BookDto.PublishDate)"
Caption="@L["PublishDate"]">
< DisplayTemplate >
@context .PublishDate.ToShortDateString()
< / DisplayTemplate >
< / DataGridColumn >
< DataGridColumn TItem = "BookDto"
Field="@nameof(BookDto.Price)"
Caption="@L["Price"]">
< / DataGridColumn >
< DataGridColumn TItem = "BookDto"
Field="@nameof(BookDto.CreationTime)"
Caption="@L["CreationTime"]">
< DisplayTemplate >
@context .CreationTime.ToLongDateString()
< / DisplayTemplate >
< / DataGridColumn >
< / DataGridColumns >
< / DataGrid >
< / CardBody >
< / Card >
````
> 如果你可以编译并运行成功,但看到一些语法错误.你可以忽略这些错误,因为Visual Studio处理Blazor还有一些bug.
* `AbpCrudPageBase<IBookAppService, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto>` 实现了所有的CRUD细节,我们从它继承.
* `Entities` , `TotalCount` , `PageSize` , `OnDataGridReadAsync` 定义在基类中.
* 注入`IStringLocalizer< BookStoreResource > ` (作为`L`对象),用于本地化.
虽然上面的代码非常容易理解,你仍然可以查看Blazorise [Card ](https://blazorise.com/docs/components/card/ )和[DataGrid](https://blazorise.com/docs/extensions/datagrid/)文档以更好地理解它们.
#### 关于AbpCrudPageBase
对于图书页面,我们将持续从`AbpCrudPageBase`获得益处. 你可以只注入`IBookAppService`并自己执行所有的服务端调用(感谢ABP的[动态C# HTTP API客户端代理](../API/Dynamic-CSharp-API-Clients.md)系统).
## 运行最终应用程序
你可以运行应用程序!该部分的最终用户界面如下所示:

这是一个可以正常工作的,服务端分页,排序和本地化的图书列表.
{{end # UI }}
## 下一章