ABP中的拦截器之ValidationInterceptor(下)
在上篇我分析了整个ABP中ValitationInterceptor的整个过程,就其中涉及到的Validator过程没有详细的论述,这篇文章就这个过程进行详细的论述,另外任何一个重要的特性如何应用是最关键的部分,这篇文章就通过介绍具体的应用来说用到底在实际的项目中如何使用这些特性。
在上篇中我们知道MethodInvocationValidator中有一个重要的函数就是SetValidationErrors(validatingObject),这个过程就是查找ABP中的所有Validator类型,然后将当前待验证的validatingObject传入到Validator中进行相关的验证,具体的验证过程我们会通过后面的实例来进行论述,我们先来看看之前的SetValidationErrors函数。
protected virtual void SetValidationErrors(object validatingObject) { foreach (var validatorType in _configuration.Validators) { if (ShouldValidateUsingValidator(validatingObject, validatorType)) { using (var validator = _iocResolver.ResolveAsDisposable<IMethodParameterValidator>(validatorType)) { var validationResults = validator.Object.Validate(validatingObject); ValidationErrors.AddRange(validationResults); } } } }
我们先来从ABP中如何添加Validator说起,然后来分别介绍每一种Validator的实现和作用,我们知道AbpKernelModule是整个ABP系统中最先进行加载的模块,我们来看看这个模块中是如何初始化Validator的,在AbpKernelModule中会按照PreInitialize()、Initialize()、PostInitialize()方法依次进行执行,在PreInitialize()方法中会增加AddMethodParameterValidators()这个方法,在这个方法中会默认添加三种类型的Validator,即DataAnnotationsValidator、ValidatableObjectValidator、CustomValidator这几个类型是ABP系统中默认的Validator类型。
private void AddMethodParameterValidators() { Configuration.Validation.Validators.Add<DataAnnotationsValidator>(); Configuration.Validation.Validators.Add<ValidatableObjectValidator>(); Configuration.Validation.Validators.Add<CustomValidator>(); }
一 DataAnnotationsValidator
这个Validator主要是对一个对象中的参数进行验证的,它主要是继承自IMethodParameterValidator,我们先来看看这个接口是怎么定义的。
public interface IMethodParameterValidator : ITransientDependency { IReadOnlyList<ValidationResult> Validate(object validatingObject); }
在这个接口中定义了一个Validate方法用来验证当前的validatingObject,验证之后会返回一个IReadOnlyList<ValidationResult>的验证结果。
public class DataAnnotationsValidator : IMethodParameterValidator { public virtual IReadOnlyList<ValidationResult> Validate(object validatingObject) { return GetDataAnnotationAttributeErrors(validatingObject); } /// <summary> /// Checks all properties for DataAnnotations attributes. /// </summary> protected virtual List<ValidationResult> GetDataAnnotationAttributeErrors(object validatingObject) { var validationErrors = new List<ValidationResult>(); var properties = TypeDescriptor.GetProperties(validatingObject).Cast<PropertyDescriptor>(); foreach (var property in properties) { var validationAttributes = property.Attributes.OfType<ValidationAttribute>().ToArray(); if (validationAttributes.IsNullOrEmpty()) { continue; } var validationContext = new ValidationContext(validatingObject) { DisplayName = property.DisplayName, MemberName = property.Name }; foreach (var attribute in validationAttributes) { var result = attribute.GetValidationResult(property.GetValue(validatingObject), validationContext); if (result != null) { validationErrors.Add(result); } } } return validationErrors; } }
在这个类中我们重点来看看它定义的子方法GetDataAnnotationAttributeErrors,在这个方法中首先获取当前待验证的对象validationObject中所有的属性,然后再看每一个属性是否定义了ValidationAttribute,如果没有定义这个属性那么循环接着继续,然后再定义一个ValidationContext的验证上下文,这个是定义在一个系统级别的程序集中,其默认的命名空间为System.ComponentModel.DataAnnotations,后面通过循环获取定义了ValidationAttribute验证属性的验证结果,并将最后验证的结果返回到之前在接口中定义的IReadOnlyList<ValidationResult>集合中,从而完成最终的验证结果,其实这个是最好理解的,这个在验证一些串属性的长度等方面是非常有用的,特别是使用EntityFrameworkCore框架时,当我们定义领域层Model时,这个对象的属性经常要和数据库中的字段一一对应,如果数据库中的字段定义了长度,那么我们也需要对数据的长度进行验证,这个是非常重要的一个参数验证方式。
在我们的系统中首先需要验证的就是定义的各种DTO,比如常用的有StringLengthAttribute,这个是继承自ValidationAttribute的一个自定义属性,通常用来验证字符串属性的长度,另外RequiredAttribute也是常见的自定义属性,定义了RequiredAttribute属性,那么当前Dto中这个属性就要求必须赋值,如果值为null,那么就会通过上面定义的DataAnnotationsValidator来进行相关的验证,并将最终的验证结果放到 List<ValidationResult>集合中,然后最终由ABP抛出这些异常信息。这里我们来举出常见的属性的应用。
public class CreateRoleDto { [Required] [StringLength(AbpRoleBase.MaxNameLength)] public string Name { get; set; } [Required] [StringLength(AbpRoleBase.MaxDisplayNameLength)] public string DisplayName { get; set; } public string NormalizedName { get; set; } [StringLength(Role.MaxDescriptionLength)] public string Description { get; set; } public bool IsStatic { get; set; } public List<string> Permissions { get; set; } }
二 ValidatableObjectValidator
这个Validator也是用来验证validationObject的,这个对象也是继承自IMethodParameterValidator的,所以这个Validator也实现了里面定义的Validate方法用来验证当前待验证的对象,但是这里还有一个重要的限制就是待验证的这个对象必须继承自IValidatableObject接口,否则是不能进行相关验证的,我们来看看这个接口。
这个接口也是系统级别的程序集中定义的接口,默认命名空间为System.ComponentModel.DataAnnotations,其内部也只定义了一个Validate方法,用来对当前的参数进行验证并返回最终的验证结果。
public class ValidatableObjectValidator : IMethodParameterValidator { public virtual IReadOnlyList<ValidationResult> Validate(object validatingObject) { var validationErrors = new List<ValidationResult>(); if (validatingObject is IValidatableObject o) { validationErrors.AddRange(o.Validate(new ValidationContext(o))); } return validationErrors; } }
这个Abp中定义的验证器需要我们定义的Dto继承自一个IValidatableObject,这个接口中也定义了一个Validate(ValidationContext validationContext)方法,在这个方法中是一个ValidationContext的上下文对象,在这个上下文中有一个重要的对象就是ObjectInstance,这个对象表示当前验证的DTO对象SelfAddDto,这里面有这个对象所有的属性,在验证的时候我们只需要将最终验证的结果返回就能够进行返回,这里我们来举一个具体的实例来说明。
[AutoMap(typeof(SelfAddedModel))] public class SelfAddDto:Entity<int>,IHasPerson,IValidatableObject { public long UserId { get; set; } public string UserName { get; set; } public string Country { get; set; } public string Province { get; set; } public string City { get; set; } public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { if (string.IsNullOrWhiteSpace(UserName)) { return new List<ValidationResult>() { new ValidationResult("当前输入的参数用户名不能为空") }; } return new List<ValidationResult>(); } }
在运行当前项目中,如果当前DTO中UserName输入为空时,就会由ABP中抛出该错误,我们来看看最后的结果。
这个是在Swagger中定义的WebAPI,我们来看最终的结果。
三 CustomerValidator
这个是ABP系统中另外定义的一个Validator,顾名思义是一个自定义的Validator,我们首先来看看这个Validator的实现,然后再来一步步去分析。
public class CustomValidator : IMethodParameterValidator { private readonly IIocResolver _iocResolver; public CustomValidator(IIocResolver iocResolver) { _iocResolver = iocResolver; } public IReadOnlyList<ValidationResult> Validate(object validatingObject) { var validationErrors = new List<ValidationResult>(); if (validatingObject is ICustomValidate customValidateObject) { var context = new CustomValidationContext(validationErrors, _iocResolver); customValidateObject.AddValidationErrors(context); } return validationErrors; } }
这个Validator需要你待验证的Dto需要继承自ICustomValidate这个接口,这个接口中也定义了一个AddValidationErrors(CustomValidationContext context)方法,我们来看看这个接口中的定义。
public class CustomValidator : IMethodParameterValidator { private readonly IIocResolver _iocResolver; public CustomValidator(IIocResolver iocResolver) { _iocResolver = iocResolver; } public IReadOnlyList<ValidationResult> Validate(object validatingObject) { var validationErrors = new List<ValidationResult>(); if (validatingObject is ICustomValidate customValidateObject) { var context = new CustomValidationContext(validationErrors, _iocResolver); customValidateObject.AddValidationErrors(context); } return validationErrors; } }
ICustomValidate接口定义。
public interface ICustomValidate { /// <summary> /// This method is used to validate the object. /// </summary> /// <param name="context">Validation context.</param> void AddValidationErrors(CustomValidationContext context); }
那么我们会发现这个接口和上面IValidatableObject这个接口有明显的不同,那么具体体现在用法上有什么不同呢?这个也要我们用上面同样的例子来进行说明。
public class SelfAddDto:Entity<int>,IHasPerson,ICustomValidate { public long UserId { get; set; } public string UserName { get; set; } public string Country { get; set; } public string Province { get; set; } public string City { get; set; } public void AddValidationErrors(CustomValidationContext context) { if (string.IsNullOrWhiteSpace(UserName)) { context.Results.Add(new ValidationResult("当前输入的参数用户名不能为空")); } } }
通过ValidatableObjectValidator中相同的例子来进行验证的话会得到相同的效果,这里的CustomerValidator完全是ABP中自定义的一个验证器,同时也是对ValidatableObjectValidator的一个有效的补充,通过这几个例子应该是能够加强你对整个ABP系统中的验证的机制有一个更加清楚的认识,如果对于本篇还有些不太理解的最好先读上篇从而对整个ABP中的Validation过程有一个基础的理解,本篇就到这里了。
最后,点击这里返回整个ABP系列的主目录。