应用服务

  应用服务将领域逻辑暴露给展示层。在展示层使用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. codeforces 767A Snacktower(模拟)

    A. Snacktower time limit per test:2 seconds memory limit per test:256 megabytes input:standard input ...

  2. 洛谷 P1972 [SDOI2009]HH的项链【莫队算法学习】

    P1972 [SDOI2009]HH的项链 题目背景 无 题目描述 HH 有一串由各种漂亮的贝壳组成的项链.HH 相信不同的贝壳会带来好运,所以每次散步完后,他都会随意取出一段贝壳,思考它们所表达的含 ...

  3. 51Nod 1004 n^n的末位数字(日常复习快速幂,莫名的有毒,卡mod值)

    1004 n^n的末位数字 题目来源: Author Ignatius.L (Hdu 1061) 基准时间限制:1 秒 空间限制:131072 KB 分值: 5 难度:1级算法题 给出一个整数N,输出 ...

  4. 一些有用的stl知识《acm程序设计》

    accepted           通过 Presentation  Error   输出格式错误 Wrong Answer  答案错误 Runtime Error   多为数组访问越界 程序运行时 ...

  5. mui 区域三级联动

    <link href="../../css/mui.picker.css" rel="stylesheet" /><link href=&qu ...

  6. linux日志查看命令

    tail tail 命令用于显示文本文件的末尾几行, 对于监控文件日志特别有用 tail example.txt #显示文件 example.txt 的后十行内容: tail -n 20 exampl ...

  7. Spark性能调优之合理设置并行度

    Spark性能调优之合理设置并行度 1.Spark的并行度指的是什么?     spark作业中,各个stage的task的数量,也就代表了spark作业在各个阶段stage的并行度!     当分配 ...

  8. api接口token验证

    接口特点汇总: 1.因为是非开放性的,所以所有的接口都是封闭的,只对公司内部的产品有效: 2.因为是非开放性的,所以OAuth那套协议是行不通的,因为没有中间用户的授权过程: 3.有点接口需要用户登录 ...

  9. PHP面试题:HTTP中POST、GET、PUT、DELETE方式的区别

    HTTP定义了与服务器交互的不同的方法,最基本的是POST.GET.PUT.DELETE,与其比不可少的URL的全称是资源描述符,我们可以这样理解:url描述了一个网络上资源,而post.get.pu ...

  10. PHP move_uploaded_file() 函数

    PHP Filesystem 函数 定义和用法 move_uploaded_file() 函数将上传的文件移动到新位置. 若成功,则返回 true,否则返回 false. 语法 move_upload ...