应用服务

  应用服务将领域逻辑暴露给展示层。在展示层使用DTO(数据传输对象)作为参数调用应用服务,应用服务使用领域对象执行一些特定的业务逻辑,并返回DTO到展示层。因此,展示层与领域层是完全独立的。在一个理想的分层应用中,展示层从不直接使用领域对象。

IApplicationService接口

  在ABP中,应用服务应该实现IApplicationService接口。建议为每一个应用服务创建一个接口。所以,我们首先定义一个应用服务的接口,如下所示:

public interface IPersonAppService : IApplicationService
{
void CreatePerson(CreatePersonInput input);
}

  IPsersonAppService只有一个方法。它被展示层用来创建一个新的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对象。它是一个input DTO,自动被ABP校验。参见DTO验证文档了解详情。

ApplicationService类

  应用服务应该实现IApplicationService接口。也可以选择派生子ApplicationService基类。因此,IApplicationService自然也就被实现了。ApplicationService类有一些基本的功能,可以很容易的实现日志、本地化等。建议为应用服务创建一个特别的扩展了ApplicationSerivice的基类。这样,就可以为应用服务添加一些通用的功能。应用服务类实例如下:

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。这样,你就不用再所有的服务类里重复定义它。关于这个话题可以参见logginglocalization文档了解更多信息。

CrudService和AsyncCrudAppService类

  如果你创建的应用服务对于一个特定的实体包含Create,Update,Delete,Get,GetAll方法,你可以继承CrudAppService(或AsyncCrudAppService如果你创建异步方法)类。CrudAppService基类是泛型的,它接收相关的实体和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应用服务

GettingList

  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);
}
}

  首先,我们添加了GetAllTaskInput作为AsyncCrudAppService类的第四个泛型参数(第三个是实体的PK类型)。然后我们重写了CreateFilteredQuery方法来应用自定义过滤器。这个方法是AsyncCrudAppService类的一个自定义扩展点(WhereIf是ABP的一个扩展方法用来简化条件过滤。实际上我们简化了过滤IQueryable接口)。

  注意:如果你创建了应用服务接口,你也应该为这个接口添加同样的泛型参数。

Create和Update

  注意,我们为getting,creating和updating任务使用同样的DTO(TaskDto),这对真实的应用来说可能并不合适。所以,我们想自定义create和update DTOs。让我们先从创建一个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; }
}

  我想继承CreateTaskInput来包含更新操作所需要的所有属性(但是你或许想要不一样的)。这里,实现IEntity(或IEntity<PrimaryKey>来实现除int之外类型的PK)是需要的,因为我们需要知道哪个实体将要被更新。最后,我添加了一个额外的属性,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 DTOs,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(),CheckUpdatePermission(),CheckDeletePermission()。默认,他们都使用相关的权限名称调用CheckPermission(...)方法,它只是简单的调用了IPermissionChecker.Authorize(...)方法。

工作单元

  在ABP中,应用服务方法默认为一个工作单元。因此,任何应用服务方法都是事务的并在方法结束的时候自动保存数据库的更改。

  参见工作单元文档了解更多。

应用服务生命周期

  所有的应用服务实例都是瞬态的。意味着,他们每次使用时实例化。参见依赖注入文档了解更多信息。

返回主目录

ABP官方文档翻译 4.1 应用服务的更多相关文章

  1. ABP官方文档翻译 1.1 介绍

    介绍 介绍 快速示例 其他 启动模板 如何使用 介绍 我们通常会根据不同的需求来创建不同的应用程序.但是对于一些通用相似的结构总是一遍又一遍的实现,至少在某种程度上是这样的.常见的通用模块如授权.验证 ...

  2. ABP官方文档翻译 10.1 ABP Nuget包

    ABP Nuget包 Packages Abp Abp.AspNetCore Abp.Web.Common Abp.Web Abp.Web.Mvc Abp.Web.Api Abp.Web.Api.OD ...

  3. ABP官方文档翻译 6.3 本地化

    本地化 介绍 应用程序语言 本地化源 XML文件 注册XML本地化源 JSON文件 注册JSON本地化源 资源文件 自定义源 当前语言是如何决定的 ASP.NET Core ASP.NET MVC 5 ...

  4. ABP官方文档翻译 6.2.1 ASP.NET Core集成

    ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Acti ...

  5. ABP官方文档翻译 5.2 动态We API层

    动态Web APID层 创建动态Web API控制器 ForAll方法 重写ForAll ForMethods Http动词 WithVerb方法 HTTP特性 命名约定 API管理器 RemoteS ...

  6. ABP官方文档翻译 4.6 审计日志

    审计日志 介绍 关于IAuditingStore 配置 通过特性启用/禁用 注意事项 介绍 维基百科:“审计追踪(也称为审计日志)是与安全相关的按时间先后的记录.记录集合.记录的目的地和源,提供一系列 ...

  7. ABP官方文档翻译 4.5 特征管理

    特征管理 介绍 关于IFeatureValueStore 特征类型 Boolean特征 Value特征 定义特征 基本特征属性 其他特征属性 特征层级 检查特征 使用RequiresFeature特性 ...

  8. ABP官方文档翻译 4.4 授权

    授权 介绍 关于IPermissionChecker 定义权限 检查权限 使用AbpAuthorize特性 AbpAuthorize特性注意点 抑制授权 使用IPermissionChecker 在R ...

  9. ABP官方文档翻译 4.3 校验数据传输对象

    校验数据传输对象 校验简介 使用数据标注 自定义校验 禁用校验 标准化 校验简介 应用的输入首先应该被校验.输入可以是用户的也可以是其他应用的.在一个web应用中,校验通常实现两次:客户端和服务端.客 ...

随机推荐

  1. bzoj 1935: [Shoi2007]Tree 园丁的烦恼

    Description 很久很久以前,在遥远的大陆上有一个美丽的国家.统治着这个美丽国家的国王是一个园艺爱好者,在他的皇家花园里种植着各种奇花异草.有一天国王漫步在花园里,若有所思,他问一个园丁道: ...

  2. HDU 1019 Least Common Multiple【gcd+lcm+水+多个数的lcm】

    Least Common Multiple Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 65536/32768 K (Java/Ot ...

  3. noip2015 提高组 解题报告

    完美退役...说好的不卡常呢QAQ day1: T1:模拟题?..考察选手将题目描述翻译成代码的能力233 //其实真相是考验rp..论代码雷同的危害233 T2:简单图论,每个点出度为1所以是基环内 ...

  4. 浏览器的统一指针事件:Pointer Event

    在早期的浏览器,输入的事件其实相对单纯,只有考虑到鼠标和键盘两种:而当时的鼠标事件,其实就是 click.mousedown.mouseup 等等的事件.但是当手机.平板开始流行时候,再移动装置上的主 ...

  5. sql中查询同一列所有值出现的次数

    尊重原创:http://blog.csdn.net/love_java_cc/article/details/52234889 有表如下table3: 需要查询country中各个国家出现的次数 SQ ...

  6. HTML5 Canvas 数据持久化存储之属性列表

    属性列表想必大家都不会陌生,正常用 HTML5 来做的属性列表大概就是用下拉菜单之类的,而且很多情况下,下拉列表还不够好看,怎么办?我试着用 HT for Web 来实现属性栏点击按钮弹出多功能选框, ...

  7. dedecms的include文件夹是干什么的?

    include是DEDECMS的系统文件夹,里面放的是DEDECMS系统下的一些系统功能函数文件和功能定义与说明以及参数的文件. include目录文件作用解析 arc.archives.class ...

  8. 数据库 MySQL进阶之索引

    数据库的索引非常重要,基本面试数据库的问题都在索引上,所以这里小编整理出来,一方面为了自己复习,一方面也方便大家. 一,索引前传 在了解数据库索引之前,首先有必要了解一下数据库索引的数据结构基础,那么 ...

  9. Java之IO流学习总结【下】

    2.字节流 |-- InputStream(读) |-- OutputStream(写) 由于字节是二进制数据,所以字节流可以操作任何类型的数据,值得注意的是字符流使用的是字符数组char[]而字节流 ...

  10. MYSQL Nested Join Optimization

    table_factor的语法和标准sql比较,后者只接受table_reference,每个逗号项都等于一个inner Join,e.g. SELECT * FROM t1 LEFT JOIN (t ...