应用服务

  应用服务将领域逻辑暴露给展示层。在展示层使用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. LibreOJ β Round #2 E. 数论只会 GCD

    传送门 题解 题解里面说得很清楚了. 大约就是单独考虑每个数的贡献,然后看一下每个序列里有多少区间是没有这个数的,乘起来就好了. 为了处理修改我们需要每个值建一棵线段树来搞,但是窝zz了,写了线段树套 ...

  2. UnityShader 表面着色器简单例程集合

    0.前言 这些简单的shader程序都是写于2015年的暑假.当时实验室空调坏了,30多个人在实验室中挥汗如雨,闷热中学习shader的日子还历历在目.这些文章闲置在我个人博客中,一年将过,师弟也到了 ...

  3. html5 文本格式化

    通常标签 <strong> 替换加粗标签 <b> 来使用, <em> 替换 <i>标签使用.然而,这些标签的含义是不同的:<b> 与< ...

  4. mysql按照天统计报表,当天没有数据,填0

    1.问题复现: 按照天数统计每天的总数,如果其中有几天没有数据,那么group by 返回会忽略那几天,如何填充0?如下图,统计的10-3~10-10 7天的数据,其中只有8号和10号有数据,这样返回 ...

  5. PHP性能分析工具xhprof的安装使用与注意事项

    前言 xhprof由facebook开源出来的一个PHP性能监控工具,占用资源很少,甚至能够在生产环境中进行部署. 它可以结合graphviz使用,能够以图片的形式很直观的展示代码执行耗时. 下面主要 ...

  6. Ajax 跨域,这应该是最全的解决方案了

    https://segmentfault.com/a/1190000012469713 前言 从刚接触前端开发起,跨域这个词就一直以很高的频率在身边重复出现,一直到现在,已经调试过N个跨域相关的问题了 ...

  7. php对数组进行分页

      3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 ...

  8. 版本控制——TortoiseSVN (4)多版本并行开发 B

    =================================版权声明================================= 版权声明:原创文章 禁止转载  请通过右侧公告中的“联系邮 ...

  9. node.js进阶话题

    < h3>notes_控制流 //forloopi.js var fs = require('fs'); var files = ['a.txt', 'b.txt', 'c.txt']; ...

  10. File类实现文件夹和文件复制

    package com.jcy.copy; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFou ...