應用服務
應用服務實現應用程式的用例, 將領域層邏輯公開給表示層.
從表示層(可選)呼叫應用服務,DTO (資料傳輸物件) 作為引數. 返回(可選)DTO給表示層.
示例Book實體(聚合根)
public class Book : AggregateRoot<Guid>{ public const int MaxNameLength = 128; public virtual string Name { get; protected set; } public virtual BookType Type { get; set; } public virtual float? Price { get; set; } protected Book() { } public Book(Guid id, [NotNull] string name, BookType type, float? price = 0) { Id = id; Name = CheckName(name); Type = type; Price = price; } public virtual void ChangeName([NotNull] string name) { Name = CheckName(name); } private static string CheckName(string name) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentException($"name can not be empty or white space!"); } if (name.Length > MaxNameLength) { throw new ArgumentException($"name can not be longer than {MaxNameLength} chars!"); } return name; }}
Book實體中定義MaxNameLength限制Name屬性的最大長度.Book建構函式與ChangeName確保Name屬性值的有效性. 請注意, Name的setter不是public.ABP不會強制開發者這樣設計實體, 可以將所有的屬性設定Public set/get. 由你來決定是否全面實施DDD.
IBookAppService介面
在ABP中應用程式服務應該實現IApplicationService介面. 推薦每個應用程式服務建立一個介面:
public interface IBookAppService : IApplicationService{ Task CreateAsync(CreateBookDto input);}
我們將實現Create方法作為示例. CreateBookDto定義如下:
public class CreateBookDto{ [Required] [StringLength(Book.MaxNameLength)] public string Name { get; set; } public BookType Type { get; set; } public float? Price { get; set; }}
BookAppService實現
public class BookAppService : ApplicationService, IBookAppService{ private readonly IRepository<Book, Guid> _bookRepository; public BookAppService(IRepository<Book, Guid> bookRepository) { _bookRepository = bookRepository; } public async Task CreateAsync(CreateBookDto input) { var book = new Book( GuidGenerator.Create(), input.Name, input.Type, input.Price ); await _bookRepository.InsertAsync(book); }}
BookAppService繼承了基類ApplicationService· 這不是必需的, 但是ApplicationService提供了應用服務常見的需求(比如本示例服務中使用的GuidGenerator). 如果不繼承它, 我們需要在服務中手動注入IGuidGenerator.BookAppService按照預期實現了IBookAppServiceBookAppService 注入了 IRepository<Book, Guid>, 在CreateAsync方法內部使用倉儲將新實體插入資料庫.CreateAsync使用Book實體的建構函式從給定的Input值建立新的Book物件資料傳輸物件應用服務使用並返回DTO而不是實體. ABP不會強制執行此規則. 但是將實體暴露給表示層(或遠端客戶端)存在重大問題, 所以不建議返回實體.
物件到物件對映CreateBook方法使用引數CreateBookDto物件手動建立Book實體. 因為Book實體的建構函式強制執行(我們是這樣設計的).
但是在很多情況下使用自動物件對映從相似物件設定物件的屬性更加方便實用. ABP提供了一個物件到物件對映基礎設施,使其變得更加容易.
讓我們建立另一種獲取Book的方法. 首先,在IBookAppService介面中定義方法:
public interface IBookAppService : IApplicationService{ Task CreateAsync(CreateBookDto input); Task<BookDto> GetAsync(Guid id); //New method}
BookDto是一個簡單的DTO類, 定義如下:
public class BookDto{ public Guid Id { get; set; } public string Name { get; set; } public BookType Type { get; set; } public float? Price { get; set; }}
我們建立一個Automapper的Profile類. 例如:
public class MyProfile : Profile{ public MyProfile() { CreateMap<Book, BookDto>(); }}
然後使用AbpAutoMapperOptions註冊配置檔案:
[DependsOn(typeof(AbpAutoMapperModule))]public class MyModule : AbpModule{ public override void ConfigureServices(ServiceConfigurationContext context) { Configure<AbpAutoMapperOptions>(options => { //Add all mappings defined in the assembly of the MyModule class options.AddMaps<MyModule>(); }); }}
AddMaps 註冊給定類的程式集中所有的配置類,通常使用模組類. 它還會註冊 attribute 對映.
然後你可以實現GetAsync方法. 如下所示:
public async Task<BookDto> GetAsync(Guid id){ var book = await _bookRepository.GetAsync(id); return book.MapTo<BookDto>();}
MapTo擴充套件方法透過複製具有相同命名的所有屬性將Book物件轉換為BookDto物件.
MapTo的另一種替代方法是使用IObjectMapper服務:
public async Task<BookDto> GetAsync(Guid id){ var book = await _bookRepository.GetAsync(id); return ObjectMapper.Map<Book, BookDto>(book);}
CRUD應用服務
如果需要建立具有Create,Update,Delete和Get方法的簡單CRUD應用服務,則可以使用ABP的基類輕鬆構建服務. 你可以繼承CrudAppService.
示例:
建立繼承ICrudAppService介面的IBookAppService介面.
public interface IBookAppService : ICrudAppService< //Defines CRUD methods BookDto, //Used to show books Guid, //Primary key of the book entity PagedAndSortedResultRequestDto, //Used for paging/sorting on getting a list of books CreateUpdateBookDto, //Used to create a new book CreateUpdateBookDto> //Used to update a book{}
ICrudAppService 有泛型引數來獲取實體的主鍵型別和CRUD操作的DTO型別(它不獲取實體型別,因為實體型別未向客戶端公開使用此介面).
ICrudAppService宣告以下方法:
public interface ICrudAppService< TEntityDto, in TKey, in TGetListInput, in TCreateInput, in TUpdateInput> : IApplicationService where TEntityDto : IEntityDto<TKey>{ Task<TEntityDto> GetAsync(TKey id); Task<PagedResultDto<TEntityDto>> GetListAsync(TGetListInput input); Task<TEntityDto> CreateAsync(TCreateInput input); Task<TEntityDto> UpdateAsync(TKey id, TUpdateInput input); Task DeleteAsync(TKey id);}
示例中使用的DTO類是BookDto和CreateUpdateBookDto:
public class BookDto : AuditedEntityDto<Guid>{ public string Name { get; set; } public BookType Type { get; set; } public float Price { get; set; }}public class CreateUpdateBookDto{ [Required] [StringLength(128)] public string Name { get; set; } [Required] public BookType Type { get; set; } = BookType.Undefined; [Required] public float Price { get; set; }}
DTO類的Profile類.
public class MyProfile : Profile{ public MyProfile() { CreateMap<Book, BookDto>(); CreateMap<CreateUpdateBookDto, Book>(); }}
CreateUpdateBookDto由建立和更新操作共享,但你也可以使用單獨的DTO類.
BookAppService的實現:
public class BookAppService : CrudAppService< Book, BookDto, Guid, PagedAndSortedResultRequestDto, CreateUpdateBookDto, CreateUpdateBookDto>,IBookAppService{ public BookAppService(IRepository<Book, Guid> repository) : base(repository) { }}
CrudAppService實現了ICrudAppService介面中宣告的所有方法. 然後,你可以新增自己的自定義方法或覆蓋和自定義實現.
CrudAppService 有不同數量泛型引數的版本,你可以選擇適合的使用.
AbstractKeyCrudAppServiceCrudAppService 要求你的實體擁有一個Id屬性作為主鍵. 如果你使用的是複合主鍵,那麼你無法使用它.
AbstractKeyCrudAppService 實現了相同的 ICrudAppService 介面,但它沒有假設你的主鍵.
假設你有實體 District,它的CityId 和 Name 作為複合主鍵,使用 AbstractKeyCrudAppService 時需要你自己實現 DeleteByIdAsync 和 GetEntityByIdAsync 方法:
public class DistrictAppService : AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>{ public DistrictAppService(IRepository<District> repository) : base(repository) { } protected async override Task DeleteByIdAsync(DistrictKey id) { await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name); } protected async override Task<District> GetEntityByIdAsync(DistrictKey id) { return await AsyncQueryableExecuter.FirstOrDefaultAsync( Repository.Where(d => d.CityId == id.CityId && d.Name == id.Name) ); }}
這個實現需要你建立一個類作為複合鍵:
public class DistrictKey{ public Guid CityId { get; set; } public string Name { get; set; }}
生命週期
應用服務的生命週期是transient的,它們會自動註冊到依賴注入系統.