ABP官方文档翻译 4.1 应用服务

应用服务

  • IApplicationService接口
  • ApplicationService类
  • CrudService和AsyncCrudAppService类
    • 简单的CRUD应用服务示例
    • 自定义CRUD应用服务
      • GettingList
      • Create和Update
      • 其他方法参数
    • CRUD权限
  • 工作单元
  • 应用服务生命周期

  应用服务将领域逻辑暴露给展示层。在展示层使用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中,应用服务方法默认为一个工作单元。因此,任何应用服务方法都是事务的并在方法结束的时候自动保存数据库的更改。

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

应用服务生命周期

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

返回主目录
时间: 2024-12-19 04:02:53

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

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

审计日志 介绍 关于IAuditingStore 配置 通过特性启用/禁用 注意事项 介绍 维基百科:“审计追踪(也称为审计日志)是与安全相关的按时间先后的记录.记录集合.记录的目的地和源,提供一系列活动的纪实证据,这些活动可能在任何时刻影响一个特定操作.过程或事件.” ABP提供了基础设施自动记录应用所有的交互.它可以记录方法调用的调用者和参数. 基本上,保存的字段有:相关的tenant id,调用者user id,调用者service name(调用方法的类),调用者method name,

ABP官方文档翻译 3.3 仓储

 仓储 默认仓储 自定义仓储 自定义仓储接口 自定义仓储实现 基础仓储方法管理数据库连接 查询 获取单个实体 获取实体列表 关于IQueryable 自定义返回值 插入 更新 删除 其他 关于异步方法 管理数据库连接 仓储生命周期 仓储最佳实践 协调领域和数据映射层,使用类集合接口访问领域对象."(Martin Fowler) 实际上,仓储用来执行领域对象的数据库操作(实体和值类型).通常,每个对象(或聚合根)使用单独的仓储. 默认仓储 在ABP中,仓储类实现IRepository<TEn

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.OData Abp.Web.Resources Abp.Web.SignalR Abp.Owin Abp.EntityFramework.Common Abp.EntityFramework Abp.EntityFramework.GraphDiff Abp.EntityFrameworkCore Ab

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

ASP.NET Core 介绍 迁移到ASP.NET Core? 启动模板 配置 启动类 模块配置 控制器 应用服务作为控制器 过滤器 授权过滤器 审计Action过滤器 校验过滤器 工作单元Action过滤器 异常过滤器 结果过滤器 Ajax请求的结果缓存 模型绑定器 视图 客户端代理 集成测试 介绍 本文档描述了ABP如何集成ASP.NET Core.ASP.NET Core通过Abp.AspNetCore nuget包实现集成. 迁移到ASP.NET Core? 如果你已经有一个工程并考虑

ABP官方文档翻译 4.4 授权

授权 介绍 关于IPermissionChecker 定义权限 检查权限 使用AbpAuthorize特性 AbpAuthorize特性注意点 抑制授权 使用IPermissionChecker 在Razor视图 客户端(Javascript) 权限管理 介绍 几乎所有的企业应用都在一定程度上使用授权.在应用中,授权用来检查用户是否允许执行一些特定的操作.ABP定义了一个基础的权限设施来实现授权. 关于IPermissionChecker 授权系统使用IPermissionChecker来检查权

ABP官方文档翻译 3.6 工作单元

工作单元 介绍 ABP中的连接和事务管理 传统的工作单元方法 控制工作单元 UnitOfWork特性 IUnitOfWorkManager 工作单元详情 禁用工作单元 无事务工作单元 一个工作单元方法调用另一个 工作单元范围 自动保存更改 IRepository.GetAll()方法 工作单元特性限制 选项 方法 SaveChanges 事件 介绍 在使用数据库的应用中,连接和事务管理是最重要的概念之一.什么时候打开连接,什么时候开始一个事务,如何释放连接等等.ABP使用工作单元系统来管理连接和

ABP官方文档翻译 1.2 N层架构

N层架构 介绍 ABP架构 其他(通用) 领域层 应用层 基础设施层 网络和展现层 其他 总结 介绍 应用程序代码库的分层架构是被广泛认可的可以减少程序复杂度.提高代码复用率的技术.为了实现分层架构,ABP遵循领域驱动设计的原则.在领域驱动设计中有四个基本层: 表现层:提供用户接口.使用应用层实现用户交互. 应用层:桥接表现层和领域层.协调业务对象来执行特定的应用任务. 领域层:包括业务对象以及业务规则.此层是整个应用的核心. 基础设施层:提供通用的技术能力来支持高层.基础设施层可以是使用ORM

ABP官方文档翻译 3.4 领域服务

领域服务 介绍 IDomainService接口和DomainService类 示例 创建接口 服务实现 使用应用服务 一些探讨 为什么只有应用服务? 如何强制使用领域服务? 介绍 领域服务(或者在DDD中单纯的服务)用来执行领域操作和业务规则.Eric Evans在他的DDD书中描述了一个好的服务有三个特征: 1. 与领域概念关联的操作,但不是实体或值对象的自然组成部分. 2. 接口的定义依照领域模型的其他元素. 3. 操作是无状态的. 不像应用服务那样获取或返回DTO,领域服务获取或返回领域

ABP官方文档翻译 6.3 本地化

本地化 介绍 应用程序语言 本地化源 XML文件 注册XML本地化源 JSON文件 注册JSON本地化源 资源文件 自定义源 当前语言是如何决定的 ASP.NET Core ASP.NET MVC 5.x 获取一个本地化文本 在服务端 在MVC控制器 在MVC视图 在Javascript 格式化参数 默认本地化源 扩展本地化源 获取语言 最佳实践 介绍 任何应用程序都会包含至少一种语言.许多的应用程序都包含多种语言.ABP提供了灵活的本地化系统. 应用程序语言 首要的事情是声明支持哪种语言.在模