3.2 应用服务开发
3.2.1 应用服务基础概念
应用服务是连接领域层和表示层的桥梁,负责处理业务逻辑、数据转换和事务管理。在ABP框架中,应用服务通常用于:
- 处理来自表示层的请求
- 协调领域对象执行业务逻辑
- 转换领域对象为DTO(数据传输对象)
- 管理事务
- 处理权限和异常
- 实现业务流程
应用服务的核心原则包括:
- 封装业务逻辑:将复杂的业务逻辑封装在应用服务中
- 数据转换:负责领域对象和DTO之间的转换
- 事务管理:确保业务操作的原子性
- 权限控制:验证用户是否有权执行特定操作
- 异常处理:处理业务异常并返回友好的错误信息
3.2.2 ABP框架中的应用服务基类
ABP框架提供了一系列应用服务基类,简化了应用服务的创建和管理:
| ApplicationService | 基本应用服务基类,提供常用的应用服务功能 | 所有应用服务 |
| CrudAppService<TEntity, TDto, TKey> | 提供CRUD操作的应用服务基类 | 需要基本CRUD功能的应用服务 |
| AsyncCrudAppService<TEntity, TDto, TKey> | 异步CRUD应用服务基类 | 需要异步CRUD功能的应用服务 |
| CrudAppService<TEntity, TDto, TKey, TGetListInput, TCreateInput, TUpdateInput> | 完整的CRUD应用服务基类 | 需要完整CRUD功能的应用服务 |
3.2.3 应用服务接口定义
应用服务接口定义了应用服务提供的功能,是表示层与应用服务层的契约。
3.2.3.1 基本应用服务接口
创建一个基本的图书应用服务接口:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace BookStore.Application.Books
{
// 图书应用服务接口,继承自IApplicationService
public interface IBookAppService : IApplicationService
{
// 获取图书列表
Task<PagedResultDto<BookDto>> GetListAsync(GetBookListDto input);
// 获取单个图书
Task<BookDto> GetAsync(Guid id);
// 创建图书
Task<BookDto> CreateAsync(CreateBookDto input);
// 更新图书
Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input);
// 删除图书
Task DeleteAsync(Guid id);
// 获取可用图书列表
Task<List<BookDto>> GetAvailableBooksAsync();
// 获取图书统计信息
Task<BookStatisticsDto> GetStatisticsAsync();
}
}
3.2.3.2 继承自ICrudAppService的接口
如果应用服务只需要基本的CRUD功能,可以继承自ICrudAppService接口:
using System;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
namespace BookStore.Application.Authors
{
// 作者应用服务接口,继承自ICrudAppService
public interface IAuthorAppService : ICrudAppService<
AuthorDto, // 返回的DTO类型
Guid, // 实体主键类型
PagedAndSortedResultRequestDto, // 获取列表的输入DTO类型
CreateAuthorDto, // 创建的输入DTO类型
UpdateAuthorDto // 更新的输入DTO类型
>
{
// 可以添加额外的方法
Task<List<AuthorLookupDto>> GetAuthorLookupAsync();
}
}
3.2.4 DTO设计与映射
DTO(数据传输对象)用于在应用服务层和表示层之间传输数据,避免直接暴露领域对象。
3.2.4.1 DTO设计原则
3.2.4.2 基本DTO示例
创建图书相关的DTO:
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;
namespace BookStore.Application.Books
{
// 图书DTO,用于返回图书信息
public class BookDto : FullAuditedEntityDto<Guid>
{
public string Name { get; set; }
public string Isbn { get; set; }
public decimal Price { get; set; }
public Guid AuthorId { get; set; }
public string AuthorName { get; set; }
public bool IsBorrowed { get; set; }
}
// 获取图书列表的输入DTO
public class GetBookListDto : PagedAndSortedResultRequestDto
{
public string Filter { get; set; }
public Guid? AuthorId { get; set; }
public bool? IsBorrowed { get; set; }
}
// 创建图书的输入DTO
public class CreateBookDto
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[StringLength(20)]
public string Isbn { get; set; }
[Required]
[Range(0.01, double.MaxValue)]
public decimal Price { get; set; }
[Required]
public Guid AuthorId { get; set; }
}
// 更新图书的输入DTO
public class UpdateBookDto
{
[Required]
[StringLength(100)]
public string Name { get; set; }
[Required]
[StringLength(20)]
public string Isbn { get; set; }
[Required]
[Range(0.01, double.MaxValue)]
public decimal Price { get; set; }
[Required]
public Guid AuthorId { get; set; }
}
// 图书统计DTO
public class BookStatisticsDto
{
public int TotalBooks { get; set; }
public int AvailableBooks { get; set; }
public int BorrowedBooks { get; set; }
public decimal TotalValue { get; set; }
}
// 图书查找DTO,用于下拉选择
public class BookLookupDto : EntityDto<Guid>
{
public string Name { get; set; }
public string Isbn { get; set; }
}
}
3.2.4.3 使用AutoMapper进行映射
ABP框架集成了AutoMapper,用于自动映射领域对象和DTO。
using AutoMapper;
using BookStore.Domain.Books;
using BookStore.Domain.Authors;
namespace BookStore.Application.Books
{
// 图书映射配置
public class BookMapperProfile : Profile
{
public BookMapperProfile()
{
// 映射图书实体到图书DTO
CreateMap<Book, BookDto>()
.ForMember(dest => dest.AuthorName, opt => opt.MapFrom(src => src.Author.Name));
// 映射创建DTO到图书实体
CreateMap<CreateBookDto, Book>();
// 映射更新DTO到图书实体
CreateMap<UpdateBookDto, Book>();
// 映射图书实体到图书查找DTO
CreateMap<Book, BookLookupDto>();
// 映射作者实体到作者查找DTO
CreateMap<Author, AuthorLookupDto>();
}
}
}
在模块中注册AutoMapper配置:
using Volo.Abp.AutoMapper;
using Volo.Abp.Modularity;
namespace BookStore.Application
{
[DependsOn(
typeof(AbpAutoMapperModule),
typeof(BookStoreDomainModule)
)]
public class BookStoreApplicationModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
// 配置AutoMapper
Configure<AbpAutoMapperOptions>(options =>
{
options.AddMaps<BookStoreApplicationModule>();
});
// 其他配置…
}
}
}
3.2.5 业务逻辑实现
应用服务的核心是实现业务逻辑,包括数据处理、事务管理和权限控制。
3.2.5.1 基本应用服务实现
实现图书应用服务:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using BookStore.Domain.Books;
using BookStore.Domain.Authors;
namespace BookStore.Application.Books
{
// 图书应用服务实现
public class BookAppService : ApplicationService, IBookAppService
{
// 注入仓储和领域服务
private readonly IBookRepository _bookRepository;
private readonly IAuthorRepository _authorRepository;
private readonly BookManager _bookManager;
public BookAppService(
IBookRepository bookRepository,
IAuthorRepository authorRepository,
BookManager bookManager)
{
_bookRepository = bookRepository;
_authorRepository = authorRepository;
_bookManager = bookManager;
}
// 获取图书列表
public async Task<PagedResultDto<BookDto>> GetListAsync(GetBookListDto input)
{
// 构建查询
var queryable = await _bookRepository.GetQueryableAsync();
// 应用过滤条件
if (!string.IsNullOrWhiteSpace(input.Filter))
{
queryable = queryable.Where(b =>
b.Name.Contains(input.Filter) ||
b.Isbn.Contains(input.Filter) ||
b.Author.Name.Contains(input.Filter));
}
if (input.AuthorId.HasValue)
{
queryable = queryable.Where(b => b.AuthorId == input.AuthorId.Value);
}
if (input.IsBorrowed.HasValue)
{
queryable = queryable.Where(b => b.IsBorrowed == input.IsBorrowed.Value);
}
// 应用排序
if (string.IsNullOrWhiteSpace(input.Sorting))
{
queryable = queryable.OrderBy(b => b.Name);
}
else
{
queryable = queryable.OrderBy(input.Sorting);
}
// 执行分页查询
var totalCount = await AsyncExecuter.CountAsync(queryable);
var items = await AsyncExecuter.ToListAsync(queryable
.Skip(input.SkipCount)
.Take(input.MaxResultCount));
// 映射到DTO并返回
return new PagedResultDto<BookDto>(
totalCount,
ObjectMapper.Map<List<Book>, List<BookDto>>(items)
);
}
// 获取单个图书
public async Task<BookDto> GetAsync(Guid id)
{
var book = await _bookRepository.GetAsync(id);
return ObjectMapper.Map<Book, BookDto>(book);
}
// 创建图书
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
// 使用领域服务创建图书
var book = await _bookManager.CreateBookAsync(
input.Name,
input.Isbn,
input.Price,
input.AuthorId
);
// 保存到数据库
await _bookRepository.InsertAsync(book);
return ObjectMapper.Map<Book, BookDto>(book);
}
// 更新图书
public async Task<BookDto> UpdateAsync(Guid id, UpdateBookDto input)
{
// 获取图书实体
var book = await _bookRepository.GetAsync(id);
// 更新图书信息
book = await _bookManager.UpdateBookAsync(
book,
input.Name,
input.Isbn,
input.Price,
input.AuthorId
);
// 保存到数据库
await _bookRepository.UpdateAsync(book);
return ObjectMapper.Map<Book, BookDto>(book);
}
// 删除图书
public async Task DeleteAsync(Guid id)
{
await _bookRepository.DeleteAsync(id);
}
// 获取可用图书列表
public async Task<List<BookDto>> GetAvailableBooksAsync()
{
var books = await _bookRepository.GetListAsync(b => !b.IsBorrowed);
return ObjectMapper.Map<List<Book>, List<BookDto>>(books);
}
// 获取图书统计信息
public async Task<BookStatisticsDto> GetStatisticsAsync()
{
var books = await _bookRepository.GetListAsync();
return new BookStatisticsDto
{
TotalBooks = books.Count,
AvailableBooks = books.Count(b => !b.IsBorrowed),
BorrowedBooks = books.Count(b => b.IsBorrowed),
TotalValue = books.Sum(b => b.Price)
};
}
}
}
3.2.5.2 继承自CrudAppService的实现
如果应用服务只需要基本的CRUD功能,可以继承自CrudAppService类:
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using BookStore.Domain.Authors;
namespace BookStore.Application.Authors
{
// 作者应用服务实现,继承自CrudAppService
public class AuthorAppService : CrudAppService<
Author, // 作者实体类型
AuthorDto, // 返回的DTO类型
Guid, // 实体主键类型
PagedAndSortedResultRequestDto, // 获取列表的输入DTO类型
CreateAuthorDto, // 创建的输入DTO类型
UpdateAuthorDto // 更新的输入DTO类型
>, IAuthorAppService
{
public AuthorAppService(IRepository<Author, Guid> repository)
: base(repository)
{
}
// 实现额外的方法
public async Task<List<AuthorLookupDto>> GetAuthorLookupAsync()
{
var authors = await Repository.GetListAsync();
return ObjectMapper.Map<List<Author>, List<AuthorLookupDto>>(authors);
}
// 可以重写基类的方法,添加自定义逻辑
protected override async Task<Author> CreateAsync(CreateAuthorDto input)
{
// 添加自定义创建逻辑
var author = await base.CreateAsync(input);
// 记录创建日志
Logger.LogInformation("Author created: {AuthorName}", author.Name);
return author;
}
}
}
3.2.6 事务管理
ABP框架提供了自动事务管理,确保应用服务方法在一个事务中执行。
3.2.6.1 自动事务管理
默认情况下,ABP框架会为所有应用服务方法自动创建事务。如果方法成功执行,事务会自动提交;如果发生异常,事务会自动回滚。
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
// 自动创建事务
var book = await _bookManager.CreateBookAsync(
input.Name,
input.Isbn,
input.Price,
input.AuthorId
);
// 保存图书
await _bookRepository.InsertAsync(book);
// 创建图书创建事件
await EventBus.PublishAsync(new BookCreatedEvent(book));
// 如果所有操作成功,事务自动提交;如果发生异常,事务自动回滚
return ObjectMapper.Map<Book, BookDto>(book);
}
3.2.6.2 手动事务管理
如果需要手动控制事务,可以使用IUnitOfWorkManager:
public async Task<BookDto> CreateBookWithManualTransactionAsync(CreateBookDto input)
{
// 获取工作单元
using (var uow = UnitOfWorkManager.Begin())
{
try
{
var book = await _bookManager.CreateBookAsync(
input.Name,
input.Isbn,
input.Price,
input.AuthorId
);
await _bookRepository.InsertAsync(book);
// 手动提交事务
await uow.CompleteAsync();
return ObjectMapper.Map<Book, BookDto>(book);
}
catch (Exception ex)
{
// 手动回滚事务(可选,发生异常时会自动回滚)
await uow.RollbackAsync();
throw;
}
}
}
3.2.6.3 事务属性配置
可以使用UnitOfWork特性配置事务的行为:
// 配置事务隔离级别和传播行为
[UnitOfWork(IsolationLevel.ReadCommitted, Propagation.Required)]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
// 事务配置会应用到这个方法
var book = await _bookManager.CreateBookAsync(
input.Name,
input.Isbn,
input.Price,
input.AuthorId
);
await _bookRepository.InsertAsync(book);
return ObjectMapper.Map<Book, BookDto>(book);
}
// 禁用事务
[UnitOfWork(IsDisabled = true)]
public async Task<List<BookDto>> GetAvailableBooksAsync()
{
// 这个方法不会在事务中执行
var books = await _bookRepository.GetListAsync(b => !b.IsBorrowed);
return ObjectMapper.Map<List<Book>, List<BookDto>>(books);
}
3.2.7 企业级最佳实践
3.2.7.1 应用服务设计最佳实践
3.2.7.2 DTO设计最佳实践
3.2.7.3 事务管理最佳实践
3.2.7.4 性能优化最佳实践
3.2.8 实际案例:图书管理系统中的应用服务实现
3.2.8.1 借阅管理应用服务
实现图书借阅管理的应用服务:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
using BookStore.Domain.Books;
using BookStore.Domain.Borrowing;
using BookStore.Domain.Users;
namespace BookStore.Application.Borrowing
{
// 借阅管理应用服务
public class BorrowingAppService : ApplicationService, IBorrowingAppService
{
private readonly IRepository<BorrowingRecord, Guid> _borrowingRecordRepository;
private readonly IRepository<Book, Guid> _bookRepository;
private readonly ICurrentUser _currentUser;
private readonly BorrowingManager _borrowingManager;
public BorrowingAppService(
IRepository<BorrowingRecord, Guid> borrowingRecordRepository,
IRepository<Book, Guid> bookRepository,
ICurrentUser currentUser,
BorrowingManager borrowingManager)
{
_borrowingRecordRepository = borrowingRecordRepository;
_bookRepository = bookRepository;
_currentUser = currentUser;
_borrowingManager = borrowingManager;
}
// 借阅图书
public async Task<BorrowingRecordDto> BorrowBookAsync(BorrowBookDto input)
{
// 获取当前用户ID
var userId = _currentUser.GetId();
// 使用领域服务处理借阅逻辑
var borrowingRecord = await _borrowingManager.BorrowBookAsync(
input.BookId,
userId,
input.Remarks
);
// 保存借阅记录
await _borrowingRecordRepository.InsertAsync(borrowingRecord);
return ObjectMapper.Map<BorrowingRecord, BorrowingRecordDto>(borrowingRecord);
}
// 归还图书
public async Task<BorrowingRecordDto> ReturnBookAsync(Guid id)
{
// 获取借阅记录
var borrowingRecord = await _borrowingRecordRepository.GetAsync(id);
// 使用领域服务处理归还逻辑
await _borrowingManager.ReturnBookAsync(borrowingRecord);
// 更新借阅记录
await _borrowingRecordRepository.UpdateAsync(borrowingRecord);
return ObjectMapper.Map<BorrowingRecord, BorrowingRecordDto>(borrowingRecord);
}
// 获取用户借阅记录
public async Task<PagedResultDto<BorrowingRecordDto>> GetUserBorrowingRecordsAsync(GetBorrowingRecordsDto input)
{
var userId = _currentUser.GetId();
// 构建查询
var queryable = await _borrowingRecordRepository.GetQueryableAsync();
// 过滤当前用户的借阅记录
queryable = queryable.Where(br => br.UserId == userId);
// 应用其他过滤条件
if (input.BookId.HasValue)
{
queryable = queryable.Where(br => br.BookId == input.BookId.Value);
}
if (input.IsReturned.HasValue)
{
queryable = input.IsReturned.Value
? queryable.Where(br => br.ReturnDate.HasValue)
: queryable.Where(br => !br.ReturnDate.HasValue);
}
// 应用排序
if (string.IsNullOrWhiteSpace(input.Sorting))
{
queryable = queryable.OrderByDescending(br => br.BorrowDate);
}
else
{
queryable = queryable.OrderBy(input.Sorting);
}
// 执行分页查询
var totalCount = await AsyncExecuter.CountAsync(queryable);
var items = await AsyncExecuter.ToListAsync(queryable
.Skip(input.SkipCount)
.Take(input.MaxResultCount));
return new PagedResultDto<BorrowingRecordDto>(
totalCount,
ObjectMapper.Map<List<BorrowingRecord>, List<BorrowingRecordDto>>(items)
);
}
// 获取所有借阅记录(管理员使用)
[Authorize(BorrowingPermissions.BorrowingRecords.ViewAll)]
public async Task<PagedResultDto<BorrowingRecordDto>> GetAllBorrowingRecordsAsync(GetBorrowingRecordsDto input)
{
// 构建查询
var queryable = await _borrowingRecordRepository.GetQueryableAsync();
// 应用过滤条件
if (input.BookId.HasValue)
{
queryable = queryable.Where(br => br.BookId == input.BookId.Value);
}
if (input.UserId.HasValue)
{
queryable = queryable.Where(br => br.UserId == input.UserId.Value);
}
if (input.IsReturned.HasValue)
{
queryable = input.IsReturned.Value
? queryable.Where(br => br.ReturnDate.HasValue)
: queryable.Where(br => !br.ReturnDate.HasValue);
}
// 应用排序
if (string.IsNullOrWhiteSpace(input.Sorting))
{
queryable = queryable.OrderByDescending(br => br.BorrowDate);
}
else
{
queryable = queryable.OrderBy(input.Sorting);
}
// 执行分页查询
var totalCount = await AsyncExecuter.CountAsync(queryable);
var items = await AsyncExecuter.ToListAsync(queryable
.Skip(input.SkipCount)
.Take(input.MaxResultCount));
return new PagedResultDto<BorrowingRecordDto>(
totalCount,
ObjectMapper.Map<List<BorrowingRecord>, List<BorrowingRecordDto>>(items)
);
}
}
}
3.2.8.2 应用服务权限控制
在应用服务中实现权限控制:
using Volo.Abp.Application.Services;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Domain.Repositories;
using BookStore.Domain.Books;
namespace BookStore.Application.Books
{
public class BookAppService : ApplicationService, IBookAppService
{
private readonly IPermissionChecker _permissionChecker;
private readonly IBookRepository _bookRepository;
public BookAppService(
IPermissionChecker permissionChecker,
IBookRepository bookRepository)
{
_permissionChecker = permissionChecker;
_bookRepository = bookRepository;
}
public async Task DeleteAsync(Guid id)
{
// 检查删除权限
if (!await _permissionChecker.IsGrantedAsync(BookStorePermissions.Books.Delete))
{
throw new AbpAuthorizationException("You don't have permission to delete books.");
}
await _bookRepository.DeleteAsync(id);
}
// 使用特性实现权限控制
[Authorize(BookStorePermissions.Books.Create)]
public async Task<BookDto> CreateAsync(CreateBookDto input)
{
// 权限检查由特性自动处理
// …
}
}
}
3.2.9 实践练习
3.2.10 总结与进阶指南
3.2.10.1 本章总结
- 应用服务是连接领域层和表示层的桥梁,负责处理业务逻辑、数据转换和事务管理
- ABP框架提供了一系列应用服务基类,简化了应用服务的创建和管理
- 应用服务接口定义了应用服务提供的功能,是表示层与应用服务层的契约
- DTO用于在应用服务层和表示层之间传输数据,避免直接暴露领域对象
- ABP框架集成了AutoMapper,用于自动映射领域对象和DTO
- 应用服务方法默认在事务中执行,确保业务操作的原子性
- 应用服务层应该实现权限控制,确保只有授权用户可以执行操作
3.2.10.2 进阶学习资源
通过本章的学习,你已经掌握了ABP框架中应用服务开发的核心概念和最佳实践。在实际开发中,合理设计和实现应用服务可以大大提高系统的可维护性、可扩展性和性能。
下一章,我们将学习MVC控制器与视图开发,了解如何实现表示层的功能。




