應用服務實現應用程序的用例, 將領域層邏輯公開給表示層.
從表示層(可選)調用應用服務,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; }
}
有關DTO更的教程,請參見數據傳輸對象文檔
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
(參見Guid生成文檔)。BookAppService
按照預期實現了IBookAppService。
BookAppService
注入了IRepository<Book, Guid>
(請參見倉儲)在CreateAsync方法內部使用倉儲將新實體插入數據庫。CreateAsync
使用Book
實體的構造函數從給定的Input值創建新的Book
對象。
數據傳輸對象
應用服務使用並返回DTO而不是實體. ABP不會強制執行此規則. 但是將實體暴露給表示層(或遠程客戶端)存在重大問題, 所以不建議返回實體。
有關更多信息, 請參見DTO文檔。
對象到對象映射
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);
}
雖然第二種語法編寫起來有點困難,但是如果你編寫單元測試,它會更好地工作. 有關更多信息,請參閱對象到對象映射文檔.
驗證
自動驗證應用服務方法的輸入(如ASP.NET Core 控制器的actions). 你可以使用標準數據註釋屬性或自定義驗證方法來執行驗證. ABP還確保輸入不爲空。
請參閱驗證文檔瞭解更多信息。
授權
可以對應用程序服務方法使用聲明性和命令式授權。
請參閱授權文檔瞭解更多信息。
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類型(它不獲取實體類型,因爲實體類型未向客戶端公開使用此接口)。
爲應用程序服務創建一個接口是最佳做法,但是ABP框架並不強制你這麼做,你可以跳過接口部分。
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
有不同數量泛型參數的版本,你可以選擇適合的使用。
AbstractKeyCrudAppService
CrudAppService
要求你的實體擁有一個Id屬性做爲主鍵. 如果你使用的是複合主鍵,那麼你無法使用它。
AbstractKeyCrudAppService
實現了相同的 ICrudAppService
接口,但它沒有假設你的主鍵。
示例
假設你有實體 District
,它的CityId
和 Name
做爲複合主鍵,使用 AbstractKeyCrudAppService
時需要你自己實現 DeleteByIdAsync
和 GetEntityByIdAsync
方法:
public class DistrictAppService
: AbstractKeyCrudAppService<District, DistrictDto, DistrictKey>
{
public DistrictAppService(IRepository<District> repository)
: base(repository)
{
}
protected override async Task DeleteByIdAsync(DistrictKey id)
{
await Repository.DeleteAsync(d => d.CityId == id.CityId && d.Name == id.Name);
}
protected override async 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; }
}