Application Services用于将域逻辑公开给表示层。使用DTO(数据传输对象)作为参数从表示层调用应用服务。它还使用域对象来执行某些特定的业务逻辑,并将DTO返回给表示层。因此,表示层与域层完全隔离。
在理想的分层应用程序中,表示层永远不会直接使用域对象。
IApplicationService接口
在ASP.NET Boilerplate中,应用程序服务应实现 IApplicationService接口。 为每个Application Service 创建一个接口是很好的。
首先,让我们为应用程序服务定义一个接口:
public interface IPersonAppService : IApplicationService
{
void CreatePerson(CreatePersonInput input);
}
IPersonAppService只有一个方法。它由表示层用于创建新人。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 来执行数据库操作。它使用构造函数注入 模式,从而使用依赖注入。
PersonAppService实现IApplicationService(因为IPersonAppService扩展了IApplicationService)。它由ASP.NET Boilerplate自动注册到Dependency Injection系统,可以被其他类注入和使用。命名约定在这里很重要。有关详细信息,请参阅依赖项注入文档。
该CreatePerson方法获取CreatePersonInput。它是一个输入DTO,由ASP.NET Boilerplate自动验证。有关 详细信息,请参阅 DTO和 验证文档。
ApplicationService类
应用程序服务应实现上面声明的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和AsyncCrudAppService类
如果您需要为特定实体创建具有Create,Update,Delete,Get,GetAll方法的应用程序服务,则可以轻松地从CrudAppService类继承。您还可以使用AsyncCrudAppService类来创建异步方法。CrudAppService基类是通用的,它将相关的Entity和 DTO类型作为通用参数。这也是可扩展的,允许您在需要自定义时覆盖功能。
简单的CRUD应用服务示例
假设我们在下面定义了一个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应用程序服务
获得一份清单
Crud应用程序服务将PagedAndSortedResultRequestDto作为默认的GetAll方法的参数获取,它提供可选的排序和分页参数。您可能还想为GetAll方法添加其他参数。例如,您可能想要添加一些 自定义过滤器。在这种情况下,您可以为GetAll方法创建DTO。例:
public class GetAllTasksInput : PagedAndSortedResultRequestDto
{
public TaskState? State { get; set; }
}
这里我们继承自PagedAndSortedResultRequestInput。这不是必需的,但如果需要,可以使用基类中的分页和排序参数。我们还添加了一个可选的State属性来按状态过滤任务。有了这个,我们更改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);
}
}
首先,我们将GetAllTasksInput添加为AsyncCrudAppService类的第4个通用参数(第3个是实体的PK类型)。然后我们重写CreateFilteredQuery方法以应用自定义过滤器。此方法是AsyncCrudAppService类的扩展点。注意,WhereIf是ABP的扩展方法,以简化条件过滤。我们在这里做的只是过滤IQueryable。
注意:如果您创建了应用程序服务接口,则还需要为该接口添加相同的通用参数!
创建和更新
请注意,我们使用相同的DTO(TaskDto)来获取,创建 和更新可能对现实应用程序不利的任务,因此我们可能希望自定义创建和更新DTO。
让我们从创建CreateTaskInput类开始:
[AutoMapTo(typeof(Task))]
public class CreateTaskInput
{
[Required]
[StringLength(Task.MaxTitleLength)]
public string Title { get; set; }
[StringLength(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; }
}
这里我们继承CreateTaskInput以包含Update操作的所有属性(您可能需要不同的东西)。实施 IEntity(对于不同的PK比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方法自定义输入DTO,AsyncCrudAppService可以获得更多通用参数。基类的所有方法都是虚拟的,因此您可以覆盖它们以自定义行为。
CRUD权限
您需要授权您的CRUD方法吗?如果是,则可以设置预定义的权限属性:GetPermissionName,GetAllPermissionName,CreatePermissionName,UpdatePermissionName和DeletePermissionName。如果设置了基本CRUD类,则会自动检查权限。
在这里,您可以在构造函数中设置它:
public class TaskAppService : AsyncCrudAppService<Task, TaskDto>
{
public TaskAppService(IRepository<Task> repository)
: base(repository)
{
CreatePermissionName = "MyTaskCreationPermission";
}
}
或者,您可以覆盖相应的权限检查器方法以手动检查权限:CheckGetPermission(),CheckGetAllPermission(),CheckCreatePermission(),CheckUpdatePermission(),CheckDeletePermission()。默认情况下,它们都使用相关的权限名称调用CheckPermission(…)方法。
简单地说,这会调用IPermissionChecker.Authorize(…)方法。