ABP-Book Store Application中文讲解 - Part 8: Authors: Application Layer
ABP-Book Store Application中文讲解 - Part 8: Authors: Application Layer
本章主要讲解手撸AuthorAppService中的增删改查,而不借用CrudAppService。
ABP-Book Store Application中文讲解 - Part 7: Authors: Database Integration
1. 汇总
ABP-Book Store Application中文讲解-汇总-CSDN博客
2. 前一章
ABP-Book Store Application中文讲解 - Part 6: Authors: Domain Layer-CSDN博客
项目之间的引用关系。
注意命名规范: I***AppService,***AppService
1. 创建IAuthorAppSevice接口和Dtos
在Acme.BookStore.Application.Contracts中创建Authors目录,然后在目录中创建IAuthorAppService接口并继承IApplicationService。
IApplicationService是所有应用程序服务的常规接口,ABP会自动识别此服务。
1.1 创建DTOs
在Authors目录中创建Dtos目录,目录中创建AuthorDto.cs, CreateAuthorDto.cs, UpdateAuthorDto.cs和GetAuthorListDto.cs。
你可以发现CreateAuthorDto.cs和UpdateAuthorDto.cs字段一样,此处你爷可以只创建一个CreateOrUpdateAuthorDto.cs去替换上面两个,但是ABP推荐分开他们,避免紧耦合。
比如当我们要扩展记录LastmodifiedDate/lastModifiedBy的时候,只需要更改UpdateAuthorDto.cs即可。
using System;
using Volo.Abp.Application.Dtos;namespace Acme.BookStore.Authors.Dtos;
public class AuthorDto : EntityDto<Guid>
{public string Name { get; set; }public DateTime BirthDate { get; set; }/// <summary>/// 笔名?/// </summary>public string ShortBio { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;
using static Acme.BookStore.BookStoreConsts;namespace Acme.BookStore.Authors.Dtos;public class CreateAuthorDto
{[Required][StringLength(AuthorConsts.MaxNameLength)]public string Name { get; set; } = string.Empty;[Required]public DateTime BirthDate { get; set; }/// <summary>/// 笔名?/// </summary>public string? ShortBio { get; set; }
}
using System;
using System.ComponentModel.DataAnnotations;
using Volo.Abp.Application.Dtos;
using static Acme.BookStore.BookStoreConsts;namespace Acme.BookStore.Authors.Dtos;public class UpdateAuthorDto : EntityDto<Guid>
{[Required][StringLength(AuthorConsts.MaxNameLength)]public string Name { get; set; } = string.Empty;[Required]public DateTime BirthDate { get; set; }/// <summary>/// 笔名?/// </summary>public string? ShortBio { get; set; }
}
using Volo.Abp.Application.Dtos;namespace Acme.BookStore.Authors.Dtos;/// <summary>
/// PagedAndSortedResultRequestDto是一个标准的分页类,里面有MaxResultCount->int,SkipCount->int, Sorting->string
/// </summary>
public class GetAuthorListDto : PagedAndSortedResultRequestDto
{/// <summary>/// 用来搜索作者Author/// </summary>public string? Filter { get; set; }
}
1.2 创建IAuthorAppService
在Authors目录中创建IAuthorAppService接口,并定义增删改查functions。
using Acme.BookStore.Authors.Dtos;
using System;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services;namespace Acme.BookStore.Authors;
public interface IAuthorAppService : IApplicationService
{Task<AuthorDto> GetAsync(Guid id);Task<PagedResultDto<AuthorDto>> GetListAsync(GetAuthorListDto input);Task<AuthorDto> CreateAsync(CreateAuthorDto input);Task UpdateAsync(UpdateAuthorDto input);Task DeleteAsync(Guid id);
}
目录结构如下:
2. 创建IAuthorAppService的实现类AuthorAppService
在 Acme.BookStore.Application中创建Authors目录,然后创建IAuthorAppService的实现类AuthorAppService.cs,并继承BookStoreAppService.
注意BookStoreAppService需要放在接口IAuthorAppService的前面,否则报错CS1722。
2.1 AutoMapper
在Acme.BookStore.Application中的BookStoreApplicationAutoMapperProfile.cs 中添加一下代码:
CreateMap<Author, AuthorDto>();
2.2 创建构造函数,利用DI引入IAuthorRepository和AuthorManager
在构造函数中引入 IAuthorRepository和AuthorManager。
private readonly IAuthorRepository _authorRepository;private readonly AuthorManager _authorManager;public AuthorAppService(IAuthorRepository authorRepository, AuthorManager authorManager){_authorRepository = authorRepository;_authorManager = authorManager;}
2.3 方法实现
2.3.1 GetAsync根据id查询Author
public async Task<AuthorDto> GetAsync(Guid id){var author = await _authorRepository.GetAsync(id);// 此处如果移除await会报CS1988的警告,虽然代码不报错,但是会被API被截断的风险。return ObjectMapper.Map<Author, AuthorDto>(author);}
2.3.2 GetListAsync
public async Task<PagedResultDto<AuthorDto>> GetListAsync(GetAuthorListDto input){if (string.IsNullOrEmpty(input.Sorting)){input.Sorting = nameof(Author.Name);}var authors = await _authorRepository.GetListAsync(skipCount: input.SkipCount, maxResultCount: input.MaxResultCount, sorting: input.Sorting, filter: input.Filter);var totalCount = string.IsNullOrEmpty(input.Filter) ?await _authorRepository.CountAsync(): await _authorRepository.CountAsync(x => x.Name.Contains(input.Filter));return new PagedResultDto<AuthorDto>(totalCount, ObjectMapper.Map<List<Author>, List<AuthorDto>>(authors));}
2.3.3 CreateAsync
public async Task<AuthorDto> CreateAsync(CreateAuthorDto input){// 利用Domain Service创建Authorvar author = await _authorManager.CreateAsync(name: input.Name, birthDate: input.BirthDate, shortBio: input.ShortBio);// 利用Repository插入数据await _authorRepository.InsertAsync(author);// 会自动回填Id到authorreturn ObjectMapper.Map<Author, AuthorDto>(author);}
2.3.4 UpdateAsync
public async Task UpdateAsync(UpdateAuthorDto input){var author = await _authorRepository.GetAsync(input.Id);if (author.Name != input.Name){// 利用Domain Service更新名字await _authorManager.ChangeNameAsync(author, input.Name);}author.BirthDate = input.BirthDate;author.ShortBio = input.ShortBio;await _authorRepository.UpdateAsync(author);}
2.3.5 DeleteAsync
public async Task DeleteAsync(Guid id){await _authorRepository.DeleteAsync(id);}
2.3 定义Permissions
2.3.1 定义Permission
在Acme.BookStore.Application.Contracts项目中的Permissions中,打开BookStorePermissions.cs,添加如下代码:
/// <summary>/// 定义Authors权限/// </summary>public static class Authors{public const string Default = GroupName + ".Authors";// 控制Authors页面权限public const string Create = Default + ".Create";// 控制Create button的隐藏显示public const string Edit = Default + ".Edit";// 控制Edit button的隐藏显示public const string Delete = Default + ".Delete";// Delete button的隐藏显示}
2.3.2 添加本地化资源
在Acme.BookStore.Domain.Shared中的Localization\BookStore目录中找到en.json和zh-Hans.json,分别添加如下内容:
en.json
"Permission:Authors": "Author Management","Permission:Authors.Create": "Creating new authors","Permission:Authors.Edit": "Editing the authors","Permission:Authors.Delete": "Deleting the authors"
zh-Hans.json
"Permission:Authors": "作者管理","Permission:Authors.Create": "新建作者","Permission:Authors.Edit": "编辑作者","Permission:Authors.Delete": "删除作者"
2.3.3 加入Permissions组
在Acme.BookStore.Application.Contracts项目中的Permissions中,打开BookStorePermissionDefinitionProvider.cs,添加如下代码:
var authorsPermission = bookStoreGroup.AddPermission(BookStorePermissions.Authors.Default, L("Permission:Authors"));authorsPermission.AddChild(BookStorePermissions.Authors.Create, L("Permission:Authors.Create"));authorsPermission.AddChild(BookStorePermissions.Authors.Edit, L("Permission:Authors.Edit"));authorsPermission.AddChild(BookStorePermissions.Authors.Delete, L("Permission:Authors.Delete"));
2.3.4 BookStorePermissions完整代码
namespace Acme.BookStore.Permissions;public static class BookStorePermissions
{public const string GroupName = "BookStore";// other permissions.../// <summary>/// 定义Books权限/// </summary>public static class Books{public const string Default = GroupName + ".Books";// 控制Book页面权限public const string Create = Default + ".Create";// 控制Create button的隐藏显示public const string Edit = Default + ".Edit";// 控制Edit button的隐藏显示public const string Delete = Default + ".Delete";// Delete button的隐藏显示}/// <summary>/// 定义Authors权限/// </summary>public static class Authors{public const string Default = GroupName + ".Authors";// 控制Authors页面权限public const string Create = Default + ".Create";// 控制Create button的隐藏显示public const string Edit = Default + ".Edit";// 控制Edit button的隐藏显示public const string Delete = Default + ".Delete";// Delete button的隐藏显示}
}
2.3.5 BookStorePermissionDefinitionProvider完整代码
using Acme.BookStore.Localization;
using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;namespace Acme.BookStore.Permissions;public class BookStorePermissionDefinitionProvider : PermissionDefinitionProvider
{public override void Define(IPermissionDefinitionContext context){var bookStoreGroup = context.AddGroup(BookStorePermissions.GroupName, L("Permission:BookStore"));var booksPermission = bookStoreGroup.AddPermission(BookStorePermissions.Books.Default, L("Permission:Books"));booksPermission.AddChild(BookStorePermissions.Books.Create, L("Permission:Books.Create"));booksPermission.AddChild(BookStorePermissions.Books.Edit, L("Permission:Books.Edit"));booksPermission.AddChild(BookStorePermissions.Books.Delete, L("Permission:Books.Delete"));var authorsPermission = bookStoreGroup.AddPermission(BookStorePermissions.Authors.Default, L("Permission:Authors"));authorsPermission.AddChild(BookStorePermissions.Authors.Create, L("Permission:Authors.Create"));authorsPermission.AddChild(BookStorePermissions.Authors.Edit, L("Permission:Authors.Edit"));authorsPermission.AddChild(BookStorePermissions.Authors.Delete, L("Permission:Authors.Delete"));}private static LocalizableString L(string name){return LocalizableString.Create<BookStoreResource>(name);}
}
2.4 添加Authorize属性到AuthorAppService
在AuthorAppService.cs类上添加[Authorize(BookStorePermissions.Authors.Default)]
在CreateAsync上面添加 [Authorize(BookStorePermissions.Authors.Create)]
在UpdateAsync上面添加 [Authorize(BookStorePermissions.Authors.Edit)]
在DeleteAsync上面添加 [Authorize(BookStorePermissions.Authors.Delete)]
AuthorAppService.cs完整代码:
using Acme.BookStore.Authors.Dtos;
using Acme.BookStore.Permissions;
using Microsoft.AspNetCore.Authorization;
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Volo.Abp.Application.Dtos;
using Volo.Abp.Domain.Repositories;namespace Acme.BookStore.Authors;[Authorize(BookStorePermissions.Authors.Default)]
public class AuthorAppService : BookStoreAppService, IAuthorAppService
{private readonly IAuthorRepository _authorRepository;private readonly AuthorManager _authorManager;public AuthorAppService(IAuthorRepository authorRepository, AuthorManager authorManager){_authorRepository = authorRepository;_authorManager = authorManager;}public async Task<AuthorDto> GetAsync(Guid id){var author = await _authorRepository.GetAsync(id);// 此处如果移除await会报CS1988的警告,虽然代码不报错,但是会被API被截断的风险。return ObjectMapper.Map<Author, AuthorDto>(author);}public async Task<PagedResultDto<AuthorDto>> GetListAsync(GetAuthorListDto input){if (string.IsNullOrEmpty(input.Sorting)){input.Sorting = nameof(Author.Name);}var authors = await _authorRepository.GetListAsync(skipCount: input.SkipCount, maxResultCount: input.MaxResultCount, sorting: input.Sorting, filter: input.Filter);var totalCount = string.IsNullOrEmpty(input.Filter) ?await _authorRepository.CountAsync(): await _authorRepository.CountAsync(x => x.Name.Contains(input.Filter));return new PagedResultDto<AuthorDto>(totalCount, ObjectMapper.Map<List<Author>, List<AuthorDto>>(authors));}[Authorize(BookStorePermissions.Authors.Create)]public async Task<AuthorDto> CreateAsync(CreateAuthorDto input){// 利用Domain Service创建Authorvar author = await _authorManager.CreateAsync(name: input.Name, birthDate: input.BirthDate, shortBio: input.ShortBio);// 利用Repository插入数据await _authorRepository.InsertAsync(author);// 会自动回填Id到authorreturn ObjectMapper.Map<Author, AuthorDto>(author);}[Authorize(BookStorePermissions.Authors.Edit)]public async Task UpdateAsync(UpdateAuthorDto input){var author = await _authorRepository.GetAsync(input.Id);if (author.Name != input.Name){// 利用Domain Service更新名字await _authorManager.ChangeNameAsync(author, input.Name);}author.BirthDate = input.BirthDate;author.ShortBio = input.ShortBio;await _authorRepository.UpdateAsync(author);}[Authorize(BookStorePermissions.Authors.Delete)]public async Task DeleteAsync(Guid id){await _authorRepository.DeleteAsync(id);}
}
3. 初始化作者数据
在Acme.BookStore.Domain中的Data目录下找到BookStoreDataSeederContributor.cs,
在构造函数中注入IAuthorRepository authorRepository, AuthorManager authorManager。
private readonly IAuthorRepository _authorRepository;private readonly AuthorManager _authorManager;public BookStoreDataSeederContributor(IRepository<Book, Guid> bookRepository, IAuthorRepository authorRepository, AuthorManager authorManager){_bookRepository = bookRepository;_authorRepository = authorRepository;_authorManager = authorManager;}
在SeedAsync添加如下代码:
// 添加Author初始化数据if (await _authorRepository.GetCountAsync() <= 0){var author = await _authorManager.CreateAsync("刘慈欣", new DateTime(1963, 6, 1), "刘慈欣被誉为中国科幻的领军人物,他的作品“三体三部曲”是中国科幻文学的里程碑之作,将中国科幻推上了世界的高度。");await _authorRepository.InsertAsync(author);var author2 = await _authorManager.CreateAsync("梁晓声", new DateTime(1949, 9, 22), "梁晓声,原名梁绍生,中国当代著名作家,中国作家协会会员,1949年9月22日出生于黑龙江省哈尔滨市,毕业于复旦大学 [3] [50],祖籍山东威海市泊于镇温泉寨。他曾创作出版过大量有影响的小说、散文、随笔及影视作品,为中国现当代以知青文学成名的代表作家之一。现居北京,任教于北京语言大学人文学院汉语言文学专业。");await _authorRepository.InsertAsync(author2);}
4. 将Acme.BookStore.DbMigrator设为启动项
将Acme.BookStore.DbMigrator设为启动项,然后F5运行,初始化数据。
5. Tests
5.1 命名规则
为了项目测试用例的统一性,我们需要遵循一定的命名规则,该命名规则有利于我们在用pipeline或者本地Run tests运行测试用例时可以快速定位到那个类或者哪个方法运行报错了。
class的命名规则:****_Tests
例如测试BookAppService.cs,则创建的对应测试用例的class名字是BookAppService_Tests.cs
方法名:Should_***_of_***
例如测试function的名字是BookAppService.cs中的GetList或者GetListAsync, 则名字是Should_Get_List_Of_Book
针对Dto中的Validation,例如Name不能为空,或者长度不能大于多少,也需要遵循统一的命名规则。
例如测试function的名字是BookAppService.cs中的Create或者CreateAsync, 则名字是Should_Not_Create_A_Book_Without_Name
具体详见:ABP-Book Store Application中文讲解 - Part 4: Integration Tests-CSDN博客
5.2 新建Acme.BookStore.Application.Tests
在Acme.BookStore.Application.Tests中新建文件夹Authors,然后在文件夹中创建AuthorAppService_Tests.cs。代码如下:
using System;
using System.Threading.Tasks;
using Acme.BookStore.Authors.Dtos;
using Shouldly;
using Volo.Abp.Modularity;
using Xunit;namespace Acme.BookStore.Authors;public abstract class AuthorAppService_Tests<TStartupModule> : BookStoreApplicationTestBase<TStartupModule>where TStartupModule : IAbpModule
{private readonly IAuthorAppService _authorAppService;protected AuthorAppService_Tests(){_authorAppService = GetRequiredService<IAuthorAppService>();}[Fact]public async Task Should_Get_All_Authors_Without_Any_Filter(){var result = await _authorAppService.GetListAsync(new GetAuthorListDto());result.TotalCount.ShouldBeGreaterThanOrEqualTo(2);result.Items.ShouldContain(author => author.Name == "刘慈欣");result.Items.ShouldContain(author => author.Name == "梁晓声");}[Fact]public async Task Should_Get_Filtered_Authors(){var result = await _authorAppService.GetListAsync(new GetAuthorListDto { Filter = "刘慈欣" });result.TotalCount.ShouldBeGreaterThanOrEqualTo(1);result.Items.ShouldContain(author => author.Name == "刘慈欣");}[Fact]public async Task Should_Create_A_New_Author(){var name = "Evan Wang";var authorDto = await _authorAppService.CreateAsync(new CreateAuthorDto{Name = name,BirthDate = new DateTime(1987, 01, 07),ShortBio = "A Developer"});authorDto.Id.ShouldNotBe(Guid.Empty);authorDto.Name.ShouldBe(name);}[Fact]public async Task Should_Not_Allow_To_Create_Duplicate_Author(){await Assert.ThrowsAsync<AuthorAlreadyExistsException>(async () =>{await _authorAppService.CreateAsync(new CreateAuthorDto{Name = "刘慈欣",BirthDate = DateTime.Now,ShortBio = "..."});});}//TODO: Test other methods...
}