从表示层(可选)调用应用服务,DTO () 作为参数. 返回(可选)DTO给表示层.

示例

假设你有一个实体(聚合根), 如下所示:

  • Book实体中定义MaxNameLength限制Name属性的最大长度.
  • Book构造函数与ChangeName确保Name属性值的有效性. 请注意, Name的setter不是public.

ABP不会强制开发者这样设计实体, 可以将所有的属性设置Public set/get. 由你来决定是否全面实施DDD.

IBookAppService接口

在ABP中应用程序服务应该实现IApplicationService接口. 推荐每个应用程序服务创建一个接口:

  1. public interface IBookAppService : IApplicationService
  2. {
  3. Task CreateAsync(CreateBookDto input);
  4. }

我们将实现Create方法作为示例. CreateBookDto定义如下:

  1. public class CreateBookDto
  2. {
  3. [Required]
  4. [StringLength(Book.MaxNameLength)]
  5. public string Name { get; set; }
  6. public BookType Type { get; set; }
  7. public float? Price { get; set; }
  8. }

有关DTO更的教程,请参见数据传输对象文档

BookAppService(实现)

  1. public class BookAppService : ApplicationService, IBookAppService
  2. {
  3. private readonly IRepository<Book, Guid> _bookRepository;
  4. public BookAppService(IRepository<Book, Guid> bookRepository)
  5. {
  6. _bookRepository = bookRepository;
  7. }
  8. public async Task CreateAsync(CreateBookDto input)
  9. {
  10. var book = new Book(
  11. GuidGenerator.Create(),
  12. input.Name,
  13. input.Type,
  14. input.Price
  15. );
  16. await _bookRepository.InsertAsync(book);
  17. }
  18. }
  • BookAppService继承了基类ApplicationService· 这不是必需的, 但是ApplicationService提供了应用服务常见的需求(比如本示例服务中使用的GuidGenerator). 如果不继承它, 我们需要在服务中手动注入IGuidGenerator(参见Guid生成文档)
  • BookAppService 注入了 IRepository<Book, Guid>(请参见)在CreateAsync方法内部使用仓储将新实体插入数据库.
  • CreateAsync使用Book实体的构造函数从给定的Input值创建新的Book对象

应用服务使用并返回DTO而不是实体. ABP不会强制执行此规则. 但是将实体暴露给表示层(或远程客户端)存在重大问题, 所以不建议返回实体.

有关更多信息, 请参见DTO文档.

对象到对象映射

CreateBook方法使用参数CreateBookDto对象手动创建Book实体. 因为Book实体的构造函数强制执行(我们是这样设计的).

但是在很多情况下使用自动对象映射从相似对象设置对象的属性更加方便实用. ABP提供了一个对象到对象映射基础设施,使其变得更加容易.

让我们创建另一种获取Book的方法. 首先,在IBookAppService接口中定义方法:

  1. {
  2. Task CreateAsync(CreateBookDto input);
  3. Task<BookDto> GetAsync(Guid id); //New method
  4. }

BookDto是一个简单的类, 定义如下:

  1. public class MyProfile : Profile
  2. {
  3. public MyProfile()
  4. {
  5. CreateMap<Book, BookDto>();
  6. }
  7. }

然后使用AbpAutoMapperOptions注册配置文件:

  1. [DependsOn(typeof(AbpAutoMapperModule))]
  2. public class MyModule : AbpModule
  3. {
  4. public override void ConfigureServices(ServiceConfigurationContext context)
  5. {
  6. Configure<AbpAutoMapperOptions>(options =>
  7. {
  8. //Add all mappings defined in the assembly of the MyModule class
  9. options.AddMaps<MyModule>();
  10. });
  11. }
  12. }

AddMaps 注册给定类的程序集中所有的配置类,通常使用模块类. 它还会注册 attribute 映射. 更多信息请参考文档

然后你可以实现GetAsync方法. 如下所示:

  1. public async Task<BookDto> GetAsync(Guid id)
  2. {
  3. var book = await _bookRepository.GetAsync(id);
  4. return book.MapTo<BookDto>();
  5. }

MapTo扩展方法通过复制具有相同命名的所有属性将Book对象转换为BookDto对象.

MapTo的另一种替代方法是使用IObjectMapper服务:

  1. public async Task<BookDto> GetAsync(Guid id)
  2. {
  3. var book = await _bookRepository.GetAsync(id);
  4. return ObjectMapper.Map<Book, BookDto>(book);
  5. }

虽然第二种语法编写起来有点困难,但是如果你编写单元测试,它会更好地工作. 有关更多信息,请参阅对象到对象映射文档.

验证

自动验证应用服务方法的输入(如ASP.NET Core 控制器的actions). 你可以使用标准数据注释属性或自定义验证方法来执行验证. ABP还确保输入不为空.

请参阅验证文档了解更多信息.

可以对应用程序服务方法使用声明性和命令式授权.

请参阅文档了解更多信息.

CRUD应用服务

如果需要创建具有Create,Update,Delete和Get方法的简单CRUD应用服务,则可以使用ABP的基类轻松构建服务. 你可以继承CrudAppService.

示例:

ICrudAppService 有泛型参数来获取实体的主键类型和CRUD操作的DTO类型(它不获取实体类型,因为实体类型未向客户端公开使用此接口).

ICrudAppService声明以下方法:

  1. public interface ICrudAppService<
  2. TEntityDto,
  3. in TKey,
  4. in TGetListInput,
  5. in TCreateInput,
  6. in TUpdateInput>
  7. : IApplicationService
  8. where TEntityDto : IEntityDto<TKey>
  9. {
  10. Task<TEntityDto> GetAsync(TKey id);
  11. Task<PagedResultDto<TEntityDto>> GetListAsync(TGetListInput input);
  12. Task DeleteAsync(TKey id);
  13. }

示例中使用的DTO类是BookDtoCreateUpdateBookDto:

  1. public class BookDto : AuditedEntityDto<Guid>
  2. {
  3. public string Name { get; set; }
  4. public BookType Type { get; set; }
  5. public float Price { get; set; }
  6. }
  7. public class CreateUpdateBookDto
  8. {
  9. [Required]
  10. [StringLength(128)]
  11. public string Name { get; set; }
  12. [Required]
  13. public BookType Type { get; set; } = BookType.Undefined;
  14. [Required]
  15. public float Price { get; set; }
  16. }

DTO类的类.

  1. public class MyProfile : Profile
  2. {
  3. public MyProfile()
  4. {
  5. CreateMap<Book, BookDto>();
  6. CreateMap<CreateUpdateBookDto, Book>();
  7. }
  8. }
  • CreateUpdateBookDto由创建和更新操作共享,但你也可以使用单独的DTO类.

最后BookAppService实现非常简单:

  1. public class BookAppService :
  2. CrudAppService<Book, BookDto, Guid, PagedAndSortedResultRequestDto,
  3. CreateUpdateBookDto, CreateUpdateBookDto>,
  4. IBookAppService
  5. {
  6. public BookAppService(IRepository<Book, Guid> repository)
  7. : base(repository)
  8. {
  9. }
  10. }

CrudAppService实现了ICrudAppService接口中声明的所有方法. 然后,你可以添加自己的自定义方法或覆盖和自定义实现.

CrudAppService 有不同数量泛型参数的版本,你可以选择适合的使用.

AbstractKeyCrudAppService

CrudAppService 要求你的实体拥有一个Id属性做为主键. 如果你使用的是复合主键,那么你无法使用它.

AbstractKeyCrudAppService 实现了相同的 ICrudAppService 接口,但它没有假设你的主键.

示例

假设你有实体 District,它的CityIdName 做为复合主键,使用 AbstractKeyCrudAppService 时需要你自己实现 DeleteByIdAsyncGetEntityByIdAsync 方法:

这个实现需要你创建一个类做为复合键:

  1. public class DistrictKey
  2. {
  3. public Guid CityId { get; set; }