ABP Validate
ABP Validate
2023/6/1
➡️

Validate

拦截器验证

ObjectValidationContext 验证上下文,持有验证的结果

public class ObjectValidationContext
{
    [NotNull]
    public object ValidatingObject { get; }

    public List<ValidationResult> Errors { get; }

    public ObjectValidationContext([NotNull] object validatingObject)
    {
        ValidatingObject = Check.NotNull(validatingObject, nameof(validatingObject));
        Errors = new List<ValidationResult>();
    }
}

AbpValidationOptions 验证配置项

public class AbpValidationOptions
{
    public List<Type> IgnoredTypes { get; }

    public ITypeList<IObjectValidationContributor>
     ObjectValidationContributors { get; set; }

    public AbpValidationOptions()
    {
        IgnoredTypes = new List<Type>();
        ObjectValidationContributors =
        new TypeList<IObjectValidationContributor>();
    }
}

IObjectValidator 验证类

namespace Volo.Abp.Validation;

public interface IObjectValidator
{
    Task ValidateAsync(
        object validatingObject,
        string? name = null,
        bool allowNull = false
    );

    Task<List<ValidationResult>> GetErrorsAsync(
        object validatingObject,
        string? name = null,
        bool allowNull = false
    );
}

获取所有的 IObjectValidationContributor,用它们去验证

public class ObjectValidator : IObjectValidator, ITransientDependency
{
    protected IServiceScopeFactory ServiceScopeFactory { get; }
    protected AbpValidationOptions Options { get; }

    public ObjectValidator(IOptions<AbpValidationOptions> options,
    IServiceScopeFactory serviceScopeFactory)
    {
        ServiceScopeFactory = serviceScopeFactory;
        Options = options.Value;
    }

    public virtual async Task ValidateAsync(object validatingObject,
     string? name = null, bool allowNull = false)
    {
        var errors = await GetErrorsAsync(validatingObject, name, allowNull);

        if (errors.Any())
        {
            throw new AbpValidationException(
                "Object state is not valid! See ValidationErrors for details.",
                errors
            );
        }
    }

    public virtual async Task<List<ValidationResult>> GetErrorsAsync(
      object validatingObject, string? name = null, bool allowNull = false)
    {
        if (validatingObject == null)
        {
            if (allowNull)
            {
                return new List<ValidationResult>();
                //TODO: Returning an array would be more performent
            }
            else
            {
                return new List<ValidationResult>
                    {
                        name == null
                            ? new ValidationResult("Given object is null!")
                            : new ValidationResult(name + " is null!", new[] {name})
                    };
            }
        }

        var context = new ObjectValidationContext(validatingObject);

        //获取所有的 IObjectValidationContributor,用它们去验证
        using (var scope = ServiceScopeFactory.CreateScope())
        {
            foreach (var contributorType in Options.ObjectValidationContributors)
            {
                var contributor = (IObjectValidationContributor)
                    scope.ServiceProvider.GetRequiredService(contributorType);
                await contributor.AddErrorsAsync(context);
            }
        }

        return context.Errors;
    }
}

IObjectValidationContributor

DataAnnotationObjectValidationContributor 通过数据注解验证

public class DataAnnotationObjectValidationContributor :
IObjectValidationContributor, ITransientDependency
{
    public const int MaxRecursiveParameterValidationDepth = 8;

    protected IServiceProvider ServiceProvider { get; }
    protected AbpValidationOptions Options { get; }

    public DataAnnotationObjectValidationContributor(
        IOptions<AbpValidationOptions> options,
        IServiceProvider serviceProvider)
    {
        ServiceProvider = serviceProvider;
        Options = options.Value;
    }

    //递归验证模型的属性
    public Task AddErrorsAsync(ObjectValidationContext context)
    {
        ValidateObjectRecursively(context.Errors,
        context.ValidatingObject, currentDepth: 1);
        return Task.CompletedTask;
    }

    //因为模型可能套模型,所有需要递归处理,处理好嵌套的深度就可以
    protected virtual void ValidateObjectRecursively(
      List<ValidationResult> errors, object? validatingObject, int currentDepth)
    {
        //退出递归
        if (currentDepth > MaxRecursiveParameterValidationDepth)
        {
            return;
        }

        //退出递归
        if (validatingObject == null)
        {
            return;
        }

        //验证模型属性,验证的错误加入 errors
        AddErrors(errors, validatingObject);

        //Validate items of enumerable
        if (validatingObject is IEnumerable enumerable)
        {
            if (!(enumerable is IQueryable))
            {
                foreach (var item in enumerable)
                {
                    // Do not recursively validate for primitive objects
                    // 简单类型退出递归
                    if (item == null || TypeHelper.IsPrimitiveExtended(item.GetType()))
                    {
                        break;
                    }
                    //继续递归
                    ValidateObjectRecursively(errors, item, currentDepth + 1);
                }
            }

            return;
        }

        var validatingObjectType = validatingObject.GetType();

        // Do not recursively validate for primitive objects
        //简单类型退出递归
        if (TypeHelper.IsPrimitiveExtended(validatingObjectType))
        {
            return;
        }

        // 不需要验证的也退出递归
        if (Options.IgnoredTypes.Any(t => t.IsInstanceOfType(validatingObject)))
        {
            return;
        }

        //验证属性
        var properties = TypeDescriptor.GetProperties(validatingObject).
        Cast<PropertyDescriptor>();
        foreach (var property in properties)
        {
            if (property.Attributes.OfType<DisableValidationAttribute>().Any())
            {
                continue;
            }
            //递归
            ValidateObjectRecursively(errors, property.GetValue(
              validatingObject), currentDepth + 1);
        }
    }

    //验证 模型的所有属性及 IValidatableObject 接口
    public void AddErrors(List<ValidationResult> errors, object validatingObject)
    {
        var properties = TypeDescriptor.GetProperties(
          validatingObject).Cast<PropertyDescriptor>();

        //对验证对象的属性
        foreach (var property in properties)
        {
            AddPropertyErrors(validatingObject, property, errors);
        }

        // IValidatableObject 验证模型实现的 IValidatableObject 验证
        if (validatingObject is IValidatableObject validatableObject)
        {
            errors.AddRange(
                validatableObject.Validate(
                  new ValidationContext(validatableObject, ServiceProvider, null))
            );
        }
    }

    //属性验证
    protected virtual void AddPropertyErrors(
      object validatingObject, PropertyDescriptor property, List<ValidationResult> errors)
    {
        var validationAttributes = property.Attributes.OfType<ValidationAttribute>().ToArray();
        if (validationAttributes.IsNullOrEmpty())
        {
            return;
        }

        //生成验证上下文,传递给 IAttributeValidationResultProvider 用于验证
        var validationContext = new ValidationContext(validatingObject, ServiceProvider, null)
        {
            DisplayName = property.DisplayName,
            MemberName = property.Name
        };

        var attributeValidationResultProvider = ServiceProvider.
        GetRequiredService<IAttributeValidationResultProvider>();
        foreach (var attribute in validationAttributes)
        {
            //通过 IAttributeValidationResultProvider 进行验证
            var result = attributeValidationResultProvider.
            GetOrDefault(attribute, property.GetValue(validatingObject), validationContext);
            if (result != null)
            {
                errors.Add(result);
            }
        }
    }
}

AbpMvcAttributeValidationResultProvider 数据注解验证的提供者

AbpMvcAttributeValidationResultProvider 实现了注解验证的多语言

namespace Volo.Abp.Validation;

public class DefaultAttributeValidationResultProvider :
 IAttributeValidationResultProvider, ITransientDependency
{
    public virtual ValidationResult? GetOrDefault(
      ValidationAttribute validationAttribute, object?
      validatingObject, ValidationContext validationContext)
    {
        return validationAttribute.GetValidationResult(validatingObject, validationContext);
    }
}

// Volo.Abp.AspNetCore.Mvc 模块里定义
// 注入的类型进行替换为 AbpMvcAttributeValidationResultProvider
namespace Volo.Abp.AspNetCore.Mvc.Localization;

[Dependency(ReplaceServices = true)]
public class AbpMvcAttributeValidationResultProvider :
DefaultAttributeValidationResultProvider
{
    private readonly AbpMvcDataAnnotationsLocalizationOptions
     _abpMvcDataAnnotationsLocalizationOptions;
    private readonly IStringLocalizerFactory
    _stringLocalizerFactory;

    public AbpMvcAttributeValidationResultProvider(
        IOptions<AbpMvcDataAnnotationsLocalizationOptions>
        abpMvcDataAnnotationsLocalizationOptions,
        IStringLocalizerFactory stringLocalizerFactory)
    {
        _abpMvcDataAnnotationsLocalizationOptions =
        abpMvcDataAnnotationsLocalizationOptions.Value;
        _stringLocalizerFactory = stringLocalizerFactory;
    }

    public override ValidationResult? GetOrDefault(ValidationAttribute validationAttribute,
     object? validatingObject, ValidationContext validationContext)
    {
        var resourceSource = _abpMvcDataAnnotationsLocalizationOptions.AssemblyResources.
        GetOrDefault(validationContext.ObjectType.Assembly);
        if (resourceSource == null)
        {
            return base.GetOrDefault(validationAttribute, validatingObject, validationContext);
        }

        if (validationAttribute.ErrorMessage == null)
        {
            ValidationAttributeHelper.SetDefaultErrorMessage(validationAttribute);
        }

        if (validationAttribute.ErrorMessage != null)
        {
            //本地化错误消息
            validationAttribute.ErrorMessage = _stringLocalizerFactory.Create(
              resourceSource)[validationAttribute.ErrorMessage];
        }

        return base.GetOrDefault(validationAttribute, validatingObject, validationContext);
    }
}

AbpMvcDataAnnotationsLocalizationOptions

namespace Volo.Abp.AspNetCore.Mvc.Localization;

public class AbpMvcDataAnnotationsLocalizationOptions
{
    public IDictionary<Assembly, Type> AssemblyResources { get; }

    public AbpMvcDataAnnotationsLocalizationOptions()
    {
        AssemblyResources = new Dictionary<Assembly, Type>();
    }

    //多个程序集共享资源
    public void AddAssemblyResource(
        [NotNull] Type resourceType,
        params Assembly[] assemblies)
    {
        if (assemblies.IsNullOrEmpty())
        {
            assemblies = new[] { resourceType.Assembly };
        }

        foreach (var assembly in assemblies)
        {
            AssemblyResources[assembly] = resourceType;
        }
    }
}

FluentObjectValidationContributor 通过 fluent 验证

namespace Volo.Abp.FluentValidation;

public class FluentObjectValidationContributor : IObjectValidationContributor,
ITransientDependency
{
    private readonly IServiceProvider _serviceProvider;

    public FluentObjectValidationContributor(
        IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    public virtual async Task AddErrorsAsync(ObjectValidationContext context)
    {
        //首先确保要注入 fluent IValidator 的实现者
        var serviceType = typeof(IValidator<>).MakeGenericType(
          context.ValidatingObject.GetType());
        var validator = _serviceProvider.GetService(serviceType) as IValidator;
        if (validator == null)
        {
            return;
        }

        //进行验证
        var result = await validator.ValidateAsync((IValidationContext)Activator.
        CreateInstance(
            typeof(ValidationContext<>).
            MakeGenericType(context.ValidatingObject.GetType()),
            context.ValidatingObject)!);

        if (!result.IsValid)
        {
            context.Errors.AddRange(
                result.Errors.Select(
                    error =>
                    new ValidationResult(error.ErrorMessage, new[] { error.PropertyName })
                )
            );
        }
    }
}

Volo.Abp.FluentValidation 需要实现自动注入 IValidator<> 实现

namespace Volo.Abp.FluentValidation;

public class AbpFluentValidationConventionalRegistrar : DefaultConventionalRegistrar
{
    //获取能被注入的 IValidator<> 实现
    protected override bool IsConventionalRegistrationDisabled(Type type)
    {
        return !type.GetInterfaces().Any(x => x.IsGenericType &&
         x.GetGenericTypeDefinition() == typeof(IValidator<>)) ||
               base.IsConventionalRegistrationDisabled(type);
    }

    //指定生命周期
    protected override ServiceLifetime? GetDefaultLifeTimeOrNull(Type type)
    {
        return ServiceLifetime.Transient;
    }

    //导出服务类型
    protected override List<Type> GetExposedServiceTypes(Type type)
    {
        return new List<Type>()
            {
                type,
                typeof(IValidator<>).MakeGenericType(GetFirstGenericArgumentOrNull(type, 1)!)
            };
    }

    //获取 GenericArgument 参数
    private static Type? GetFirstGenericArgumentOrNull(Type type, int depth)
    {
        const int maxFindDepth = 8;

        if (depth >= maxFindDepth)
        {
            return null;
        }

        if (type.IsGenericType && type.GetGenericArguments().Length >= 1)
        {
            return type.GetGenericArguments()[0];
        }
        //因为是单继承 AbstractValidator ,所以这里可以获取到,但是感觉这种写法不完善
        return GetFirstGenericArgumentOrNull(type.BaseType!, depth + 1);
    }
}
namespace Volo.Abp.FluentValidation;

[DependsOn(
    typeof(AbpValidationModule)
    )]
public class AbpFluentValidationModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        //加入约定注入
        context.Services.AddConventionalRegistrar(
          new AbpFluentValidationConventionalRegistrar());
    }
}

ValidationInterceptor 拦截器里使用 IObjectValidator 进行自动验证

public class ValidationInterceptor : AbpInterceptor, ITransientDependency
{
    private readonly IMethodInvocationValidator _methodInvocationValidator;

    public ValidationInterceptor(IMethodInvocationValidator methodInvocationValidator)
    {
        _methodInvocationValidator = methodInvocationValidator;
    }

    public override async Task InterceptAsync(IAbpMethodInvocation invocation)
    {
        //模型验证
        await ValidateAsync(invocation);
        await invocation.ProceedAsync();
    }

    protected virtual async Task ValidateAsync(IAbpMethodInvocation invocation)
    {
        await _methodInvocationValidator.ValidateAsync(
            //验证验证的对象,对象方法及参数
            new MethodInvocationValidationContext(
                invocation.TargetObject,
                //methodInfo
                invocation.Method,
                // object[] 方法所有的参数
                invocation.Arguments
            )
        );
    }
}
public interface IMethodInvocationValidator
{
    Task ValidateAsync(MethodInvocationValidationContext context);
}

// 核心验证还是通过 IObjectValidator 验证
public class MethodInvocationValidator : IMethodInvocationValidator, ITransientDependency
{
    private readonly IObjectValidator _objectValidator;

    //注入验证的核心服务 IObjectValidator
    public MethodInvocationValidator(IObjectValidator objectValidator)
    {
        _objectValidator = objectValidator;
    }

    public virtual async Task ValidateAsync(MethodInvocationValidationContext context)
    {
        Check.NotNull(context, nameof(context));

        //没有参数
        if (context.Parameters.IsNullOrEmpty())
        {
            return;
        }

        //不能不是 public
        if (!context.Method.IsPublic)
        {
            return;
        }

        //禁止验证
        if (IsValidationDisabled(context))
        {
            return;
        }

        //参数概述不匹配
        if (context.Parameters.Length != context.ParameterValues.Length)
        {
            throw new Exception(
              "Method parameter count does not match with argument count!");
        }

        //todo: consider to remove this condition
        if (context.Errors.Any() && HasSingleNullArgument(context))
        {
            ThrowValidationError(context);
        }

        await AddMethodParameterValidationErrorsAsync(context);

        if (context.Errors.Any())
        {
            ThrowValidationError(context);
        }
    }

    //虽然方法被拦截器拦截了但是,可以选择通过验证
    // EnableValidationAttribute   DisableValidationAttribute 通过这俩个特性进行判断
    protected virtual bool IsValidationDisabled(MethodInvocationValidationContext context)
    {
        if (context.Method.IsDefined(typeof(EnableValidationAttribute), true))
        {
            return false;
        }

        if (ReflectionHelper.
        GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(
          context.Method) != null)
        {
            return true;
        }

        return false;
    }

    protected virtual bool HasSingleNullArgument(MethodInvocationValidationContext context)
    {
        return context.Parameters.Length == 1 && context.ParameterValues[0] == null;
    }

    protected virtual void ThrowValidationError(MethodInvocationValidationContext context)
    {
        throw new AbpValidationException(
            "Method arguments are not valid! See ValidationErrors for details.",
            context.Errors
        );
    }

    //每个参数进行验证
    protected virtual async Task AddMethodParameterValidationErrorsAsync(
      MethodInvocationValidationContext context)
    {
        for (var i = 0; i < context.Parameters.Length; i++)
        {
            await AddMethodParameterValidationErrorsAsync(
              context, context.Parameters[i], context.ParameterValues[i]);
        }
    }

    //对参数值进行验证
    protected virtual async Task AddMethodParameterValidationErrorsAsync(
      IAbpValidationResult context, ParameterInfo parameterInfo, object parameterValue)
    {
        //判断值是否为null
        var allowNulls = parameterInfo.IsOptional ||
                         parameterInfo.IsOut ||
                         TypeHelper.IsPrimitiveExtended(
                          parameterInfo.ParameterType, includeEnums: true);

        //进行验证
        context.Errors.AddRange(
             await _objectValidator.GetErrorsAsync(
                parameterValue,
                parameterInfo.Name,
                allowNulls
            )
        );
    }
}

IValidationEnabled 需要使用 abp 拦截器进行方法参数模型进行验证的类需要继承的接口

public interface IValidationEnabled
{

}

ValidationInterceptorRegistrar 拦截器使用的范围

namespace Volo.Abp.Validation;

public static class ValidationInterceptorRegistrar
{
    public static void RegisterIfNeeded(IOnServiceRegistredContext context)
    {
        if (ShouldIntercept(context.ImplementationType))
        {
            context.Interceptors.TryAdd<ValidationInterceptor>();
        }
    }

    private static bool ShouldIntercept(Type type)
    {
        return !DynamicProxyIgnoreTypes.Contains(type) &&
         typeof(IValidationEnabled).IsAssignableFrom(type);
    }
}

AbpValidationModule

namespace Volo.Abp.Validation;

[DependsOn(
    typeof(AbpValidationAbstractionsModule),
    typeof(AbpLocalizationModule)
    )]
public class AbpValidationModule : AbpModule
{
    public override void PreConfigureServices(ServiceConfigurationContext context)
    {
        //加入拦截器
        context.Services.OnRegistered(ValidationInterceptorRegistrar.RegisterIfNeeded);
        //加入验证的 Contributors,AbpValidationOptions
        AutoAddObjectValidationContributors(context.Services);
    }

    public override void ConfigureServices(ServiceConfigurationContext context)
    {

        // abp 默认提供的这对资源感觉用不上,名字难以记忆
        Configure<AbpVirtualFileSystemOptions>(options =>
        {
            options.FileSets.AddEmbedded<AbpValidationResource>();
        });

        //验证的资源
        Configure<AbpLocalizationOptions>(options =>
        {
            options.Resources
                .Add<AbpValidationResource>("en")
                .AddVirtualJson("/Volo/Abp/Validation/Localization");
        });
    }

    private static void AutoAddObjectValidationContributors(IServiceCollection services)
    {
        var contributorTypes = new List<Type>();

        services.OnRegistered(context =>
        {
            if (typeof(IObjectValidationContributor).
            IsAssignableFrom(context.ImplementationType))
            {
                contributorTypes.Add(context.ImplementationType);
            }
        });

        services.Configure<AbpValidationOptions>(options =>
        {
            options.ObjectValidationContributors.AddIfNotContains(contributorTypes);
        });
    }
}

过滤器验证 基于数据注解

实现微软的过滤器 IAsyncActionFilter

namespace Volo.Abp.AspNetCore.Mvc.Validation;

public class AbpValidationActionFilter : IAsyncActionFilter, ITransientDependency
{
    public async Task OnActionExecutionAsync(
      ActionExecutingContext context, ActionExecutionDelegate next)
    {
        if (!context.ActionDescriptor.IsControllerAction() ||
            !context.ActionDescriptor.HasObjectResult())
        {
            await next();
            return;
        }

        if (!context.GetRequiredService<IOptions<AbpAspNetCoreMvcOptions>>().
        Value.AutoModelValidation)
        {
            await next();
            return;
        }

        if (ReflectionHelper.
        GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>
        (context.ActionDescriptor.GetMethodInfo()) != null)
        {
            await next();
            return;
        }

        if (ReflectionHelper.
        GetSingleAttributeOfMemberOrDeclaringTypeOrDefault<DisableValidationAttribute>(
          context.Controller.GetType()) != null)
        {
            await next();
            return;
        }

        if (context.ActionDescriptor.GetMethodInfo().DeclaringType !=
        context.Controller.GetType())
        {
            var baseMethod = context.ActionDescriptor.GetMethodInfo();

            var overrideMethod = context.Controller.GetType().GetMethods().
            FirstOrDefault(x =>
                x.DeclaringType == context.Controller.GetType() &&
                x.Name == baseMethod.Name &&
                x.ReturnType == baseMethod.ReturnType &&
                x.GetParameters().Select(p => p.ToString()).SequenceEqual(
                  baseMethod.GetParameters().Select(p => p.ToString())));

            if (overrideMethod != null)
            {
                if (ReflectionHelper.
                GetSingleAttributeOfMemberOrDeclaringTypeOrDefault
                <DisableValidationAttribute>(overrideMethod) != null)
                {
                    await next();
                    return;
                }
            }
        }

        context.GetRequiredService<IModelStateValidator>().Validate(context.ModelState);
        await next();
    }
}

IAbpValidationResult 验证结果处理

验证结果

public interface IAbpValidationResult
{
    List<ValidationResult> Errors { get; }
}

验证异常

public class AbpValidationException : AbpException,
    IHasLogLevel,
    IHasValidationErrors,
    IExceptionWithSelfLogging
{
    public IList<ValidationResult> ValidationErrors { get; }

    public LogLevel LogLevel { get; set; }

    public AbpValidationException()
    {
        ValidationErrors = new List<ValidationResult>();
        LogLevel = LogLevel.Warning;
    }

    //...
}
👍🎉🎊