1. 当我们谈论ABP验证时在谈什么

在餐厅点餐时(假设你正用手机下单),系统会提醒:"牛排熟度不能为空""优惠券已过期"。这些看似简单的提示背后,是复杂的业务规则验证体系。ABP框架自带的验证机制就像餐厅的自动点餐机,能处理牛排分量是否超出库存这样的基础校验,但当遇到"周三下午茶套餐只能在14:00-17:00使用"这类特殊业务规则时,就需要我们来自定义验证逻辑。

使用环境:本文示例基于ABP 7.4框架 + .NET 6 + Entity Framework Core技术栈。涉及技术点包含数据注解、对象验证、FluentValidation集成等。

2. 三种武器库:ABP验证的进阶之路

2.1 基础改装:扩展数据注解

在用户注册场景中,除了标准的Email格式验证,我们需要增加"禁止使用临时邮箱"的业务规则:

public class RegisterUserDto
{
    [Required]
    [DisposableEmailDomain(ErrorMessage = "临时邮箱不可用")]
    public string Email { get; set; }
}

// 自定义验证特性
public class DisposableEmailDomainAttribute : ValidationAttribute
{
    private static readonly string[] _disposableDomains = { 
        "tempmail.com", "10minutemail.net", "trashmail.org"
    };

    protected override ValidationResult? IsValid(
        object? value, 
        ValidationContext context)
    {
        if (value is string email)
        {
            var domainPart = email.Split('@').LastOrDefault();
            if (_disposableDomains.Any(d => d.Equals(domainPart, StringComparison.OrdinalIgnoreCase)))
            {
                return new ValidationResult(ErrorMessage);
            }
        }
        return ValidationResult.Success;
    }
}

应用技巧:

  • 优先继承ValidationAttribute而非使用现成特性
  • 通过ValidationContext获取DI容器实例(需要注册为scoped服务)

2.2 核弹级武器:FluentValidation集成

处理电商系统中的复杂订单验证时:

public class OrderDtoValidator : AbstractValidator<OrderDto>
{
    public OrderDtoValidator(IProductRepository productRepo)
    {
        RuleFor(o => o.Items)
            .Must(items => items.Sum(i => i.Quantity) <= 50)
            .WithMessage("单次购买商品总数不能超过50件");
            
        RuleForEach(o => o.Items)
            .ChildRules(item => {
                item.RuleFor(i => i.ProductId)
                    .MustAsync(async (id,cancel) => 
                        await productRepo.GetStockAsync(id) > 0)
                    .WithMessage("该商品已无库存");
            });
    }
}

实战经验:

  1. 在模块的ConfigureServices中注册验证器:
services.AddTransient<IValidator<OrderDto>, OrderDtoValidator>();
  1. ABP会自动识别并应用验证规则
  2. 支持异步验证的MustAsync方法处理数据库查询

2.3 终极防御:全局验证拦截器

当需要处理如"VIP用户可跳过地址验证"这类动态规则时:

public class CustomValidationInterceptor : IValidationInterceptor
{
    public Task BeforeValidationAsync(ValidationContext context)
    {
        // 获取当前用户信息
        var currentUser = context.ServiceProvider.GetRequiredService<ICurrentUser>();
        
        if (context.ValidatedObject is OrderDto order && 
            currentUser.IsInRole("VIP"))
        {
            context.ValidationResult.Errors
                .RemoveAll(e => e.PropertyName == nameof(OrderDto.ShippingAddress));
        }
        return Task.CompletedTask;
    }
}

实现要点:

  • 在模块初始化时注册拦截器
  • 可修改/屏蔽已有的验证错误
  • 注意线程安全问题,避免直接修改已验证对象

3. 黄金三角:核心技术的横向对比

方式 适用场景 执行顺序 DI支持 异步支持
数据注解 简单静态规则 最先 有限
FluentValidation 复杂业务规则 中间 完整
全局拦截器 动态覆盖/调整规则 最后 完整

典型应用链:注册用户时先验证邮箱格式(数据注解)→ 检查邀请码有效性(FluentValidation)→ 根据IP区域调整验证强度(全局拦截器)

4. 在现实战场中的布防策略

4.1 应用场景全景图

  • 合规性验证:验证身份证号与姓名是否匹配(需要调用第三方API)
  • 组合规则校验:当选择货到付款时,必须填写至少两个联系电话
  • 状态关联验证:退货申请必须在签收后7天内提交
  • 动态规则配置:活动期间放宽部分验证规则

4.2 防御体系的搭建指南

代码组织建议

└── Validation
    ├── Attributes          // 自定义数据注解
    ├── Validators          // Fluent验证器
    ├── Interceptors       // 全局拦截器
    └── Rules              // 可复用验证逻辑

性能注意事项

  • 避免在数据注解中进行数据库查询
  • 对于高频调用的验证规则,考虑添加内存缓存
  • 使用ValidationStrategy控制验证深度

错误信息国际化

// 在资源文件中定义
"MyValidation:DuplicateCode" : "编码 {0} 已存在,当前可用编码建议:{1}"

// 验证器中引用
RuleFor(x => x.Code)
    .Must(BeUniqueCode)
    .WithMessage(L["MyValidation:DuplicateCode", x => x.Code, GetAvailableCodes()]);

5. 来自一线的血泪经验

曾经在支付系统对接中,我们采用全局拦截器实现风控验证,却遭遇了这些问题:

  • 拦截器未正确处理异步操作导致验证结果不可靠
  • 调试困难,因为错误信息不会体现在具体属性上
  • 当多个拦截器存在时,执行顺序不可控

最终解决方案:

  1. 对涉及外部调用的验证改用FluentValidation异步验证
  2. 为关键业务验证添加专门的验证结果日志
  3. 使用[InterceptorOrder]特性控制执行顺序

6. 验证体系的黑暗森林法则

在实施自定义验证时,要警惕这些"捕熊陷阱":

  • 循环依赖:在验证器中注入依赖其他验证的服务
  • 性能黑洞:频繁的全量数据校验影响接口响应速度
  • 安全漏洞:不恰当的规则绕过导致业务漏洞
  • 验证污染:全局验证对某些特殊接口造成干扰

应对策略:

  • 使用ABP的UnitOfWork系统管理数据库连接
  • 对批量操作接口禁用部分验证规则
  • 为敏感业务添加二次验证机制

7. 结语:构建坚不可摧的规则之盾

ABP的验证体系就像乐高积木,基础模块(数据注解)提供标准化零件,扩展模块(FluentValidation)带来无限创意可能,而全局拦截器则是隐藏的万能连接器。但真正的艺术在于根据业务场景选择合适的组合方式。切记:过度的验证会扼杀用户体验,不足的验证则会暴露业务漏洞,找到那个完美的平衡点,才是架构师的终极追求。