应用服务

Application Services用于将域逻辑公开给表示层。使用DTO(数据传输对象)作为参数从表示层调用应用服务。它还使用域对象来执行某些特定的业务逻辑,并将DTO返回给表示层。因此,表示层与域层完全隔离。

在理想的分层应用程序中,表示层永远不会直接使用域对象。

IApplicationService接口

在ASP.NET Boilerplate中,应用程序服务实现 IApplicationService接口。 为每个应用程序服务创建一个接口是很好的

首先,让我们为应用程序服务定义一个接口:

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

IPersonAppService只有一种方法。它由表示层用于创建新人。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);
    }
}

这里有一些重要的要点:

ApplicationService类

应用程序服务应该实现上面声明的IApplicationService接口。可选地,它可以从ApplicationService派生 基类因此,IApplicationService本身就是实现的。

ApplicationService类具有一些基本功能,可以轻松进行日志记录, 本地化等等...建议您为扩展ApplicationService类的应用程序服务创建一个特殊的基类。这样,您可以为所有应用程序服务添加一些常用功能。示例应用程序服务类如下所示:

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的基类这样您就不会为所有服务类重复它。有关此主题的更多信息,请参阅日志记录和 本地化文档。

CrudAppService和AsyncCrudAppService类

如果您需要特定实体创建具有Create,Update,Delete,Get,GetAll方法的应用程序服务,则可以轻松地从CrudAppService继承您还可以使用AsyncCrudAppService类来创建异步方法。CrudAppService基类是通用的,它将相关的Entity和 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应用程序服务

获取清单

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

首先,我们将GetAllTasksInput添加为AsyncCrudAppService类的第4个通用参数(第3个是实体的PK类型)。然后我们重写CreateFilteredQuery方法以应用自定义过滤器。此方法是AsyncCrudAppService类的扩展点。注意,WhereIf是ABP的扩展方法,以简化条件过滤。我们在这里做的只是过滤IQueryable。

注意:如果您创建了应用程序服务接口,则还需要为该接口添加相同的通用参数!

创建和更新

请注意,我们使用相同的DTO(TaskDto)来获取,创建 和更新可能对现实应用程序不利的任务,因此我们可能希望自定义创建和更新DTO

让我们从创建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以包含Update操作的所有属性(您可能需要不同的东西)。实施 IEntity(对于不同的PK比int或IEntity <的PrimaryKey>)被 要求在这里,因为我们需要知道哪个实体正在更新。最后,我们添加了一个附加属性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);
    }
}

无需任何额外的代码更改!

其他方法参数

如果要为GetDelete方法自定义输入DTO,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(),CheckCreatePermission(),CheckUpdatePermission(),CheckDeletePermission()。默认情况下,它们都使用相关的权限名称调用CheckPermission(...)方法。
简单地说,这称为IPermissionChecker.Authorize(...)方法。

工作单位

默认情况下,应用程序服务方法是ASP.NET Boilerplate中的一个工作单元因此,应用程序服务方法是事务性的,并在每个方法结束时自动保存所有数据库更改。 

有关更多信息,请参阅工作单元文档。

应用程序服务的生命周期

所有应用程序服务实例都是Transient这意味着它们每次都被实例化。

有关更多信息,请参阅依赖注入文档。

nidie.com.cn - 用心与你沟通