## 思路
我更倾向逐个功能地推进应用开发。本节将说明如何在 UI 上显示产品列表。
1. 首先,我们会为`Product`实体定义一个`ProductDto`;
2. 然后,我们将创建一个向表示层返回产品列表的应用服务方法;
3. 此外,我们将学习如何自动映射`Product`到`ProductDto`
在创建 UI 之前,我将向您展示如何为应用服务编写**自动化测试**。这样,在开始 UI 开发之前,我们就可以确定应用服务是否正常工作。
在整个在开发过程中,我们将探索 ABP 框架的一些能力,例如自动 API 控制器和动态 JavaScript 代理系统。
最后,我们将创建一个新页面,并在其中添加一个数据表,然后从服务端获取产品列表,并将其显示在 UI 上。
梳理完思路,我们从创建一个`ProductDto`类开始。
## ProductDto 类
DTO 用于在应用层和表示层之间传输数据。最佳实践是将 DTO 返回到表示层而不是实体,因为将实体直接暴露给表示层可能导致序列化和安全问题,有了DTO,我们不但可以抽象实体,对接口展示内容也更加可控。
为了在 UI 层中可复用,DTO 规定在Application.Contracts项目中进行定义。我们首先在\*.Application.Contracts项目的Products文件夹中创建一个`ProductDto`类:
```
using System;
using Volo.Abp.Application.Dtos;
namespace ProductManagement.Products
{
public class ProductDto : AuditedEntityDto<Guid>
{
public Guid CategoryId { get; set; }
public string CategoryName { get; set; }
public string Name { get; set; }
public float Price { get; set; }
public bool IsFreeCargo { get; set; }
public DateTime ReleaseDate { get; set; }
public ProductStockState StockState { get; set; }
}
}
```
`ProductDto`与实体类基本相似,但又有以下区别:
* 它派生自`AuditedEntityDto<Guid>`,它定义了`Id`、`CreationTime`、`CreatorId`、`LastModificationTime`和`LastModifierId`属性(我们不需要做删除审计`DeletionTime`,因为删除的实体不是从数据库中读取的)。
* 我们没有向实体`Category`添加导航属性,而是使用了一个`string`类型的`CategoryName`的属性,用以在 UI 上显示。
我们将使用使用`ProductDto`类从`IProductAppService`接口返回产品列表。
## 产品应用服务
**应用服务**实现了应用的业务逻辑,UI 调用它们用于用户交互。通常,应用服务方法返回一个 DTO。
#### 1 应用服务与 API 控制器
>[warning] ABP的应用服务和MVC 中的 API 控制器有何区别?
您可以将应用服务与 [ASP.NET](http://ASP.NET) Core MVC 中的 API 控制器进行比较。虽然它们有相似之处,但是:
1. 应用服务更适合 DDD ,它们不依赖于特定的 UI 技术。
2. 此外,ABP 可以自动将您的应用服务公开为 HTTP API。
我们在\*.Application.Contracts项目的Products文件夹中创建一个`IProductAppService`接口:
```
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace ProductManagement.Products
{
public interface IProductAppService : IApplicationService
{
Task<PagedResultDto<ProductDto>> GetListAsync(PagedAndSortedResultRequestDto input);
}
}
```
我们可以看到一些预定义的 ABP 类型:
* `IProductAppService`约定从`IApplicationService`接口,这样ABP 就可以识别应用服务。
* `GetListAsync`方法的入参`PagedAndSortedResultRequestDto`是 ABP 框架的标准 DTO 类,它定义了`MaxResultCount`、`SkipCount`和`Sorting`属性。
* `GetListAsync`方法返回`PagedResultDto<ProductDto>`,其中包含一个`TotalCount`属性和一个`ProductDto`对象集合,这是使用 ABP 框架返回分页结果的便捷方式。
当然,您可以使用自己的 DTO 代替这些预定义的 DTO。但是,当您想要标准化一些常见问题,避免到处都使用相同的命名时,它们非常有用。
#### 2 异步方法
将所有应用服务方法定义为异步方法是最佳实践。如果您定义为同步方法,在某些情况下,某些 ABP 功能(例如工作单元)可能无法按预期工作。
现在,我们可以实现`IProductAppService`接口来执行用例。
#### 3 产品应用服务
我们在ProductManagement.Application项目中创建一个`ProductAppService`类:
```
using System.Linq.Dynamic.Core;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;
namespace ProductManagement.Products
{
public class ProductAppService : ProductManagementAppService, IProductAppService
{
private readonly IRepository<Product, Guid> _productRepository;
public ProductAppService(IRepository<Product, Guid> productRepository)
{
_productRepository = productRepository;
}
public async Task<PagedResultDto<ProductDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
/* TODO: Implementation */
}
}
}
```
`ProductAppService`派生自`ProductManagementAppService`,它在启动模板中定义,可用作应用服务的基类。它实现了之前定义的`IProductAppService`接口,并注入`IRepository<Product, Guid>`服务。这就是通用**默认存储**库,方面我们对数据库执行操作(ABP 自动为所有聚合根实体提供默认存储库实现)。
我们实现`GetListAsync`方法,如下代码块所示:
```
public async Task<PagedResultDto<ProductDto>> GetListAsync(PagedAndSortedResultRequestDto input)
{
var queryable = await _productRepository.WithDetailsAsync(x => x.Category);
queryable = queryable
.Skip(input.SkipCount)
.Take(input.MaxResultCount)
.OrderBy(input.Sorting ?? nameof(Product.Name));
var products = await AsyncExecuter.ToListAsync(queryable);
var count = await _productRepository.GetCountAsync();
return new PagedResultDto<ProductDto>(
count,
ObjectMapper.Map<List<Product>, List<ProductDto>>(products)
);
}
```
这里,`_productRepository.WithDetailsAsync`返回一个包含产品类别的`IQueryable<Product>`对象,(`WithDetailsAsync`方法类似于 EF Core 的`Include`扩展方法,用于将相关数据加载到查询中)。于是,我们就可以方便地使用标准的(**LINQ**) 扩展方法,比如`Skip`、`Take`和`OrderBy`等。
`AsyncExecuter`服务(基类中预先注入)用于执行`IQueryable`对象,这使得可以使用异步 LINQ 扩展方法执行数据库查询,而无需依赖应用程序层中的 EF Core 包。(我们将在\[*第 6 章* \] 中对`AsyncExecuter`进行更详细的探讨)
最后,我们使用`ObjectMapper`服务(在基类中预先注入)将`Product`集合映射到`ProductDto`集合。
## 对象映射
`ObjectMapper`(`IObjectMapper`)会自动使用**AutoMapper**库进行类型转换。它要求我们在使用之前预先定义映射关系。启动模板包含一个配置文件类,您可以在其中创建映射。
在ProductManage.Application项目中打开`ProductManagementApplicationAutoMapperProfile`类,并将其更改为以下内容:
```
using AutoMapper;
using ProductManagement.Products;
namespace ProductManagement
{
public class ProductManagementApplicationAutoMapperProfile : Profile
{
public ProductManagementApplicationAutoMapperProfile()
{
CreateMap<Product, ProductDto>();
}
}
}
```
如`CreateMap`所定义的映射。它可以自动将`Product`转换为`ProductDto`对象。
AutoMapper中有一个有趣的功能:**Flattening**,它默认会将复杂的对象模型展平为更简单的模型。在这个例子中,`Product`类有一个`Category`属性,而`Category`类也有一个`Name`属性。因此,如果要访问产品的类别名称,则应使用`Product.Category.Name`表达式。但是,`ProductDto`的`CategoryName`可以直接使用`ProductDto.CategoryName`表达式进行访问。AutoMapper 会通过展平`Category.Name`来自动映射成`CategoryName`。
应用层服务已经基本完成。在开始 UI 之前,我们先介绍如何为应用层编写自动化测试。
- 前言
- 第一部分
- 第1章 现代软件开发和 ABP 框架
- 企业级 Web 开发的挑战
- ABP框架的能力清单
- 第2章 ABP框架入门
- 安装 ABP CLI
- 创建新解决方案
- 运行解决方案
- 探索预构建模块
- 第3章 逐步开发开发ABP应用
- 创建解决方案
- 定义领域对象
- EFCore和数据库映射
- 定义应用服务
- 测试产品
- 产品列表
- 创建产品
- 编辑产品
- 删除产品
- 第4章 探索 EventHub解决方案
- 应用介绍
- 架构探索
- 方案运行
- 第二部分
- 第5章 探索ABP基础架构
- 了解模块化
- 使用依赖注入系统
- 配置应用程序
- 实现选项模式
- 日志系统
- 第6章 数据访问基础架构
- 定义实体
- 定义仓储库
- EF Core集成
- 了解 UoW
- 第7章 探索横切关注点
- 认证授权
- 用户验证
- 异常处理
- 第8章 体验 ABP 的功能和服务
- 获取当前用户
- 使用数据过滤
- 控制审计日志
- 缓存数据
- 本地化用户界面
- 第三部分
- 第9章 理解领域驱动设计
- 介绍 DDD
- 构建基于 DDD 的 解决方案
- 处理多个应用程序
- 了解执行流程
- DDD的通用原则
- 第10章 领域层 Domain
- 领域事件案例分析
- 聚合和实体的设计原则和实践
- 实现领域服务
- 落地存储库
- 构建规约(Specification)
- 领域事件
- 第11章 应用层 Application
- 落地应用服务
- 设计 DTO
- 理解各层的职责
- 第四部分
- 第12章 MVC/Razor 页面
- 主题系统
- 绑定和压缩
- 导航菜单
- Bootstrap标签助手
- 创建表单并验证
- 使用模态窗口
- 使用JS API
- 调用HTTP API
- 第13章 Blazor WebAssembly UI
- 什么是Blazor
- ABP Blazor UI
- 验证用户身份
- 理解主题系统
- 使用菜单
- 使用基本服务
- 使用UI服务
- 消费HTTP API
- 使用全局脚本和样式
- 第14章 HTTP API 和实时服务
- 构建HTTP API
- 使用HTTP API
- 使用SignalR
- 第五部分
- 第15章 落地模块化
- 理解模块化
- 构建支付模块
- 安装模块
- 第16章 实现多租户
- 理解多租户
- 多租户基础设施
- 使用功能系统
- 何时使用多租户
- 第17章 构建自动化测试
- 了解ABP测试基础设施
- 构建单元测试
- 构建集成测试