本节内容:
应用服务把领域逻辑暴露给展现层。一个应用服务被展现层使用一个DTO(数据传输对象)参数所调用,使用领域对象执行一些特殊业务逻辑并返回一个DTO给展现层。因此,展现层是完全独立于领域层,在一个理想的分层应用里,展现层从不直接使用领域对象。
在ABP里,一个应用服务应当实现IApplicationService接口,为每个应用服务创建一个接口是好的做法,所以我们先为一个应用服务创建一个接口,如下所示:
public interface IPersonAppService : IApplicationService { void CreatePerson(CreatePersonInput input); }
IPersonAppService只有一个方法,它被展现层用来创建一个新的person,CreatePersonInput是一个DTO对象,如下所示:
public class CreatePersonInput { [Required] public string Name { get; set; } public string EmailAddress { get; set; } }
然后我们可以实现IPersonAppService:
public class PersonAppService : IPersonAppService { private readonly IRepository<Person> _personRepository; public PersonAppService(IRepository<Person> personRepository) { _personRepository = personRepository; } public void CreatePerson(CreatePersonInput input) { var person = _personRepository.FirstOrDefault(p => p.EmailAddress == input.EmailAddress); if (person != null) { throw new UserFriendlyException("There is already a person with given email address"); } person = new Person { Name = input.Name, EmailAddress = input.EmailAddress }; _personRepository.Insert(person); } }
这里有几个关键点:
- PersonAppService使用IRepository<Person>执行数据库操作,它使用构造器注入模式,这里我们使用依赖注入。
- PersonAppService实现IApplicationService(因为IPersonAppService扩展了IApplicationService),它自动被ABP注入到依赖注入系统,然后被其它类注入并使用。这里命名约定很重要,查看依赖注入文档获取更多信息。
- CreatePerson方法取得CreatePersonInput,它是一个输入的DTO并被ABP自动验证,更多细节查看DTO和验证文档。
一个应用服务应当实现IApplicationService接口,如上所示,也可随意的继承于ApplicationService基类,因为它内部实现了IApplicationService,同时ApplicationService有些基本的功能,使得日志、本地化等更容易。建议为你的应用服务创建一个特殊的继承于ApplicationService类的基类,从而可以为所有应用服务添加一些通用的功能,一个应用服务示例如下:
public class TaskAppService : ApplicationService, ITaskAppService { public TaskAppService() { LocalizationSourceName = "SimpleTaskSystem"; } public void CreateTask(CreateTaskInput input) { //Write some logs (Logger is defined in ApplicationService class) Logger.Info("Creating a new task with description: " + input.Description); //Get a localized text (L is a shortcut for LocalizationHelper.GetString(...), defined in ApplicationService class) var text = L("SampleLocalizableTextKey"); //TODO: Add new task to database... } }
你有一个在构造器里定义了LocalizationSourceName的基类,这样,你就不用为所有的服务类重复定义了,这个主题的更多信息请查看日志和本地化文档。
CrudAppService 和 AsyncCrueAppService 类
如果你需要为一个特定的实体创建一个包含Create、Update、Delete、GetAll方法的应用服务,可以从CrudAppService(或AsyncCrudAppService,创建异步方法)继承,CrudAppService基类是一个以相关Entity和DTO类型为参数的泛型,并可通过重写功能进行你需要的定制。
假设你有一个Task实体,定义如下:
public class Task : Entity, IHasCreationTime { public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } public Person AssignedPerson { get; set; } public Guid? AssignedPersonId { get; set; } public Task() { CreationTime = Clock.Now; State = TaskState.Open; } }
我们为该实体创建一个DTO:
[AutoMap(typeof(Task))] public class TaskDto : EntityDto, IHasCreationTime { public string Title { get; set; } public string Description { get; set; } public DateTime CreationTime { get; set; } public TaskState State { get; set; } public Guid? AssignedPersonId { get; set; } public string AssignedPersonName { get; set; } }
AutoMap特性创建实体与DTO之间的映射配置,现在,我们可以创建一个应用服务,如:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto> { public TaskAppService(IRepository<Task> repository) : base(repository) { } }
我们注入仓储并把它传给基类(如果要使用同步的方法,我们可以从CrudAppService继承)。这就是所有代码!TaskAppService现在就已经有了简单的CRUD方法,如果你想为这个应用服务创建一个接口,可以像下面这样:
public interface ITaskAppService : IAsyncCrudAppService<TaskDto> { }
注意:IAsyncCrudAppService没有以实体(Task)作为泛型参数,因为实体与实现相关,不应该包含在公开的接口里,接下来,我们就可以为TaskAppService类实现ITaskAppService接口:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto>, ITaskAppService { public TaskAppService(IRepository<Task> repository) : base(repository) { } }
Crud应用服务的GetAll方法默认以PagedAndSortedResultRequestInput为参数,该参数提供可选的排序和分页参数,但你可能想为GetAll方法添加另一个参数,例如:你想添加一些自定义过滤,这种情况,你可以为GetAll方法创建一个DTO,如:
public class GetAllTasksInput : PagedAndSortedResultRequestInput { public TaskState? State { get; set; } }
我们从PagedAndSortedResultRequestInput继承(不是必须,但可以直接从基类得到paging和sorting参数),并添加一个可空的State属性,用来过滤task。现在我们应该修改TaskAppService,使它接受自定义过滤:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto, int, GetAllTasksInput> { public TaskAppService(IRepository<Task> repository) : base(repository) { } protected override IQueryable<Task> CreateFilteredQuery(GetAllTasksInput input) { return base.CreateFilteredQuery(input) .WhereIf(input.State.HasValue, t => t.State == input.State.Value); } }
首先,我们添加GetAllTasksInputAsyncCrudAppService第4个泛型参数(第三个是实体主键的类型),然后重写CreateFilteredQuery方法实现自定义过滤,该方法是AsyncCrudAppService类的一个扩展点(WhereIf是ABP的一个扩展方法,用来简化条件过滤,但实质上我们所做的是简单过滤一个IQueryable)。
注意:如果你已创建应用服务接口,那么你就需要为接口添加相同的泛型参数。
注意:我们为获取、创建和更新Task使用相同的DTO(TaskDto),在现实应用里,这可能不太好,所以我们想定制创建和更新的DTO,让我们从创建一个CreateTaskInput类开始:
[AutoMapTo(typeof(Task))] public class CreateTaskInput { [Required] [MaxLength(Task.MaxTitleLength)] public string Title { get; set; } [MaxLength(Task.MaxDescriptionLength)] public string Description { get; set; } public Guid? AssignedPersonId { get; set; } }
接着创建一个UpdateTaskInput DTO:
[AutoMapTo(typeof(Task))] public class UpdateTaskInput : CreateTaskInput, IEntityDto { public int Id { get; set; } public TaskState State { get; set; } }
我们为Update操作从CreateTaskInput上继承所有属性(但你也可以不这么做),此处必须实现实现IEntity(或不同于int类型的主键的IEntity<PrimaryKey>),因为我们需要知道哪一个实体需要更新,最后,我添加了一个额外的属性State,它不包含在CreateTaskInput里。
接下来,我们可以使用这些DTO类作为AsyncCrudAppService类的泛型接口,如下所示:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto, int, GetAllTasksInput, CreateTaskInput, UpdateTaskInput> { public TaskAppService(IRepository<Task> repository) : base(repository) { } protected override IQueryable<Task> CreateFilteredQuery(GetAllTasksInput input) { return base.CreateFilteredQuery(input) .WhereIf(input.State.HasValue, t => t.State == input.State.Value); } }
不需要修改其它的代码。
如果你想定制Get和Delete方法的input的DTO,AsyncCrudAppService可以接受更多的泛型参数。同样,所有的基类方法都是virtual,所以你可以重写它们进行行为定制。
在ABP里一个应用服务方法就是一个工作单元,因此,任何一个应用服务方法都是事务性的,并自动在方法结束时保存修改到数据库。
更多信息查看工作单元文档。
所有应用服务实体都是Transient(短暂的),也就说:它们都是暂存于每次使用里。更多信息查看依赖注入文档。