4.3 ABP应用层 - 数据传输对象验证

应用程序的输入首先应该被验证是否有效。输入的数据能够被用户或者其它应用发送。在web应用中,验证通常被实现两次:客户端和服务器端。客户端验证的实现主要用于用户体验。首先,最好是在客户端检验表单并且向用户展示无效的字段。但是,相对于客户端验证,服务器端验证是更重要并且不可缺失的。

服务器端的验证通常是在应用层或者控制器中被实现(通常,所有的服务都是从持久层获取数据)。应用服务中的方法首先应检测输入数据(有效性)然后使用这些数据。ABP为所有应用的输入数据提供了高效的基础设施来实现自动验证;如:

  • 所有应用服务的方法

  • 所有ASP.NET Core MVC Controlle Actions

  • 所有ASP.NET MVC 和 Web API Controller Actions

如果需要禁用验证,请查看禁用验证章节。

4.3.1 使用数据注解

ABP可以使用数据注解来检验数据的有效性。假设我们正在开发一个创建任务的应用服务,并且得到了一个输入,如下所示:

public class CreateTaskInput
{
    public int? AssignedPersonId { get; set; }

    [Required]
    public string Description { get; set; }
}

在这里 Description 属性被标记为 Required。AssignedPersonId 是可选的。在 System.ComponentModel.DataAnnotations 命名空间中,还有很多这样的特性 ( 例如: MaxLength, MinLength, RegularExpression 等等 )。如下所示:

public class TaskAppService : ITaskAppService
{
    private readonly ITaskRepository _taskRepository;
    private readonly IPersonRepository _personRepository;

    public TaskAppService(ITaskRepository taskRepository, IPersonRepository personRepository)
    {
        _taskRepository = taskRepository;
        _personRepository = personRepository;
    }

    public void CreateTask(CreateTaskInput input)
    {
        var task = new Task { Description = input.Description };

        if (input.AssignedPersonId.HasValue)
        {
            task.AssignedPerson = _personRepository.Load(input.AssignedPersonId.Value);
        }

        _taskRepository.Insert(task);
    }
}

正如你所看到的,这里没有写任何的数据验证代码,因为ABP会自动去验证数据。ABP也会检验输入数据是否为null。如果为空则会抛出AbpValidationException 异常。所以你不需要写检验数据是否为null值的代码。如果有任何属性的输入数据是无效的它也会抛出相同的异常。

这个机制近似于 ASP.NET MVC 的验证功能,注意:这里的应用服务类不是继承自Controller,它是用在Web应用的一个普通类,即使不是一个WEB应用它也能够正常工作。

4.3.2 自定义检验

如果数据注解的方式不能满足你的需求,你可以实现ICustomValidate接口,请看下面示例:

public class CreateTaskInput : ICustomValidate
{
    public int? AssignedPersonId { get; set; }

    public bool SendEmailToAssignedPerson { get; set; }

    [Required]
    public string Description { get; set; }

    public void AddValidationErrors(CustomValidatationContext context)
    {
        if (SendEmailToAssignedPerson && (!AssignedPersonId.HasValue || AssignedPersonId.Value <= 0))
        {
            context.Results.Add(new ValidationResult("AssignedPersonId must be set if SendEmailToAssignedPerson is true!"));
        }
    }
}

ICustomValidate 接口声明了一个可被实现的 AddValidationErrors 方法。如果有验证错误的话,我们必须添加 ValidationResult 对象到 context.Results 列表。如果需要的话,你也可以使用 context.IocResolver 来解决依赖关系。

除了 ICustomValidate 接口,ABP也对.NET标准的 IValidatableObject 接口提供支持。你也可以实现它来执行自定义验证。如果你实现了这两个接口,那么这两个接口的方法都会被调用。

4.3.3 禁用验证

对于自动验证类,你可以使用这些特性来控制验证:

  • DisableValidation 特性能够被用来对DTO的类,方法或者属性来禁用验证

  • EnableValidation 特性仅能够被用来开启都某个方法的验证;如果对某个类禁用验证,但是想对该类的某个方法开启验证

4.3.4 标准化

在验证数据输入后,我们需要执行一个额外的操作来组织DTO参数。ABP定义了一个 IShouldNormalize 接口,这个接口声明了一个 Normalize 的方法。如果你实现了这个接口,在数据验证后,Normalize 方法就会被调用。假设我们的DTO需要一个排序方向的属性。如果这个Sorting属性没有值,但是我们想要设置一个默认值;如下所示:

public class GetTasksInput : IShouldNormalize
{
    public string Sorting { get; set; }

    public void Normalize()
    {
        if (string.IsNullOrWhiteSpace(Sorting))
        {
            Sorting = "Name ASC";
        }
    }
}