ABP-Book Store Application中文讲解 - Part 8: Authors: Application Layer

发布于:2025-06-05 ⋅ 阅读:(20) ⋅ 点赞:(0)

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创建Author
        var author = await _authorManager.CreateAsync(name: input.Name, birthDate: input.BirthDate, shortBio: input.ShortBio);

        // 利用Repository插入数据
        await _authorRepository.InsertAsync(author);// 会自动回填Id到author
        return 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创建Author
        var author = await _authorManager.CreateAsync(name: input.Name, birthDate: input.BirthDate, shortBio: input.ShortBio);

        // 利用Repository插入数据
        await _authorRepository.InsertAsync(author);// 会自动回填Id到author
        return 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...
}

 

6. 继续学习


网站公告

今日签到

点亮在社区的每一天
去签到