应用服务

  应用服务将领域逻辑暴露给展示层。在展示层使用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. HDU5135 dfs搜索 枚举种数

    Little Zu Chongzhi's Triangles Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 512000/512000 ...

  2. qt creator 中的"提升为..."功能简介

    1.新建一个项目 2.打开文件:mainwindow.ui ->拖一个 tree widget 控件到画布->右击弹出对话框->单击"提升为..."选项 3.输入 ...

  3. Linux系统上安装JDK和Tomcat服务器

    一.安装JDK 1.查看当前Linux系统是否已经安装java  输入命令: rpm -qa | grep java 2.卸载两个openJDK  输入命令:rpm -e --nodeps 3.上传j ...

  4. 我的flashfxp左右界面怎么变成这样了?

    如下图,flashfxp不是说左边是本地的文件夹,右边是服务器上的文件夹的吗?我不懂刚刚怎么搞了一下,变成两边都是服务器上的文件夹了,哪位大神,指点下,谢谢!!! 921050734 | 浏览 168 ...

  5. 豹哥嵌入式讲堂:ARM Cortex-M开发之文件详解(8)- 镜像文件(.bin/.hex/.s19)

    大家好,我是豹哥,猎豹的豹,犀利哥的哥.今天豹哥给大家讲的是嵌入式开发里的image文件(.bin, .hex, .s19). 今天这节课是豹哥<ARM Cortex-M开发之文件详解>主 ...

  6. destoon 默认广告位代码

    <img src="http://www.testinstrument.cn/skin/default/jiurong/img/banner.png" alt="& ...

  7. 利用xcode生成的app生成可以在iphone和itouch上运行的ipa安装包

    在编译好的真机版目录下的.app文件,至于生成真机可以运行的app的方法,有两种方式,一种是交99美元获得一个证书,另外一种是破解的方式,在此不再详述,本文假设你已经生成了真机上可以运行的app包了( ...

  8. iOS的相对路径和绝对路径

    iOS程序有固定的文件访问限制,只能在自己的沙盒内. UIImage *img=[UIImage imageNamed:@"cellicon.png"]; 这段代码从相对路径加载了 ...

  9. 用Express、MySQL搭建项目(接口以及静态文件获取、文件上传等)

    一.简介 本文将主要基于node.js使用express框架搭建一个后台环境,包括如何自定义项目目录.所用依赖以及中间件.路由以及模板引擎.接口数据获取以及文件上传等内容. 二.后台环境搭建 1.新建 ...

  10. 2017-06-20 (pwd ls cd)

    pwd pwd   显示当前所在的位置 pwd -P  如果是链接文件,显示链接文件所指的位置 ls ls 查询目录中的内容 ls  -a 显示所有的文件,包含隐藏的文件   -l 显示详细的信息   ...