C#.NET FluentValidation 全面解析:优雅实现对象验证

简介

  • FluentValidation 是一个基于"流式 API"(Fluent API)的 .NET 验证框架,用于在应用层对模型(DTO、ViewModel、Entity 等)进行声明式验证。

  • 核心优势:

    • 高可读性:通过链式方法配置验证规则,逻辑清晰;

    • 可复用:将验证代码从业务逻辑中分离,易于单元测试;

    • 丰富的内置规则:邮箱、长度、正则、多字段联动、集合验证等;

    • 可扩展:支持自定义验证器、异步验证、跨属性验证。

  • 适用场景:

    • Web API 模型验证

    • 复杂业务规则验证

    • 需要高度可定制验证逻辑的系统

    • 多语言验证消息需求

    • 需要测试覆盖的验证逻辑

安装与基础配置

  • NuGet
shell 复制代码
Install-Package FluentValidation
Install-Package FluentValidation.DependencyInjectionExtensions
  • 引用命名空间
shell 复制代码
using FluentValidation;

核心用法

定义 Model 与 Validator

csharp 复制代码
public class UserDto
{
    public string Username { get; set; }
    public string Email    { get; set; }
    public int    Age      { get; set; }
}

public class UserDtoValidator : AbstractValidator<UserDto>
{
    public UserDtoValidator()
    {
        // NotEmpty / NotNull
        RuleFor(x => x.Username)
            .NotEmpty().WithMessage("用户名不能为空")
            .Length(3, 20).WithMessage("用户名长度须在3到20之间");

        // Email 格式
        RuleFor(x => x.Email)
            .NotEmpty().WithMessage("邮箱不能为空")
            .EmailAddress().WithMessage("邮箱格式不正确");

        // 数值范围
        RuleFor(x => x.Age)
            .InclusiveBetween(18, 120)
            .WithMessage("年龄须在18到120之间");
    }
}

执行验证

csharp 复制代码
var user = new UserDto { Username = "", Email = "bad", Age = 10 };
var validator = new UserDtoValidator();
var result = validator.Validate(user);

if (!result.IsValid)
{
    foreach (var failure in result.Errors)
    {
        Console.WriteLine($"{failure.PropertyName}: {failure.ErrorMessage}");
    }
}

// ASP.NET Core 自动验证
[HttpPost]
public IActionResult CreateUser([FromBody] UserDto user)
{
    // 模型绑定后自动验证
    if (!ModelState.IsValid)
    {
        return BadRequest(ModelState);
    }
    // ...
}

常用验证规则

方法 作用
NotNull() / NotEmpty() 非空或非空串
Length(min, max) 字符串长度范围
EmailAddress() 邮箱格式
Matches(regex) 正则匹配
InclusiveBetween(min, max) 数值范围
GreaterThan(x) / LessThan(x) 大小比较
Must(predicate) 自定义同步条件
MustAsync(asyncPredicate) 自定义异步条件

进阶特性

跨属性验证

csharp 复制代码
RuleFor(x => x.EndDate)
    .GreaterThan(x => x.StartDate)
    .WithMessage("结束时间必须晚于开始时间");

条件验证

csharp 复制代码
RuleFor(x => x.Password)
    .NotEmpty().When(x => x.RequirePassword)
    .WithMessage("密码不能为空");

集合与嵌套对象

csharp 复制代码
public class OrderDto { public List<OrderItemDto> Items { get; set; } }
public class OrderItemDto { public int Quantity { get; set; } }

public class OrderDtoValidator : AbstractValidator<OrderDto>
{
    public OrderDtoValidator()
    {
        RuleForEach(x => x.Items)
            .ChildRules(items =>
            {
                items.RuleFor(i => i.Quantity)
                     .GreaterThan(0).WithMessage("数量须大于0");
            });
    }
}

异步验证

csharp 复制代码
RuleFor(x => x.Username)
    .MustAsync(async (name, ct) => !await userRepo.ExistsAsync(name))
    .WithMessage("用户名已存在");

级联验证

使用 CascadeMode.Stop 提高性能

csharp 复制代码
RuleFor(x => x.Name).Cascade(CascadeMode.Stop).NotEmpty().MaximumLength(50);

验证规则组织

规则集(RuleSets)

csharp 复制代码
public class UserValidator : AbstractValidator<UserDto>
{
    public UserValidator()
    {
        // 公共规则
        RuleFor(user => user.Name).NotEmpty();
        
        // 创建规则集
        RuleSet("Admin", () => 
        {
            RuleFor(user => user.IsAdmin).Must(b => b == true)
                .WithMessage("管理员用户必须设置管理员标志");
        });

        RuleSet("PaymentInfo", () => {
            RuleFor(c => c.CreditCardNumber).NotEmpty();
            RuleFor(c => c.CreditCardExpiry).NotEmpty();
        });
    }
}

// 使用指定规则集
var result = validator.Validate(user, options => 
{
    options.IncludeRuleSets("Admin", "PaymentInfo");
});

继承与组合

csharp 复制代码
// 基础验证器
public class PersonValidator : AbstractValidator<PersonDto>
{
    public PersonValidator()
    {
        RuleFor(p => p.Name).NotEmpty();
        RuleFor(p => p.BirthDate).LessThan(DateTime.Now);
    }
}

// 继承扩展
public class EmployeeValidator : PersonValidator
{
    public EmployeeValidator()
    {
        Include(new PersonValidator()); // 包含基础规则
        RuleFor(e => e.EmployeeId).NotEmpty();
        RuleFor(e => e.Department).NotEmpty();
    }
}

// 组合验证
public class AdvancedUserValidator : AbstractValidator<UserDto>
{
    public AdvancedUserValidator()
    {
        Include(new UserValidator());
        RuleFor(u => u.SecurityLevel).InclusiveBetween(1, 5);
    }
}

自定义扩展

自定义验证器

csharp 复制代码
public static class CustomValidators
{
    public static IRuleBuilderOptions<T, string> ValidIdCard<T>(this IRuleBuilder<T, string> ruleBuilder)
    {
        return ruleBuilder.Must(id => Regex.IsMatch(id, @"^[1-9]\d{16}[0-9X]$"))
                          .WithMessage("身份证格式不正确");
    }
}

// 使用
RuleFor(x => x.IdCard).ValidIdCard();

自定义属性比较器

csharp 复制代码
public class DateRangeValidator : PropertyValidator
{
    public DateRangeValidator() : base("{PropertyName} 时间范围不合法") { }

    protected override bool IsValid(PropertyValidatorContext context)
    {
        var dto = (MyDto)context.InstanceToValidate;
        return dto.End > dto.Start;
    }
}

// 在 Validator 中
RuleFor(x => x.Start).SetValidator(new DateRangeValidator());

ASP.NET Core 集成

注册服务(Program.cs)

csharp 复制代码
builder.Services
    .AddControllers()
    .AddFluentValidation(cfg =>
    {
        // 自动注册当前程序集所有继承 AbstractValidator 的类型
        cfg.RegisterValidatorsFromAssemblyContaining<Startup>();
        // 禁用 DataAnnotations 验证(可选)
        cfg.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
    });

自动触发

  • ASP.NET Core 在模型绑定后会自动调用对应 Validator,并将错误添加到 ModelState

  • Controller 中可直接检查 if (!ModelState.IsValid) 或依赖 [ApiController] 的自动返回行为。

自定义错误响应

csharp 复制代码
// 配置全局异常处理
services.Configure<ApiBehaviorOptions>(options =>
{
    options.InvalidModelStateResponseFactory = context =>
    {
        var errors = context.ModelState
            .Where(e => e.Value.Errors.Count > 0)
            .ToDictionary(
                kvp => kvp.Key,
                kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
            );
        
        return new BadRequestObjectResult(new
        {
            Code = 400,
            Message = "请求验证失败",
            Errors = errors
        });
    };
});

最佳实践

  • 分层组织规则:

    • 针对同一模型,可拆分多个 Validator(或使用 Include()),保持单一职责。
  • 复用规则集:

    • 对于常见字段(如邮箱、手机号),可定义公共规则并通过扩展方法重用。
  • 错误消息国际化:

    • 将消息文本放入资源文件,使用 .WithMessage(x => Resources.FieldRequired)
  • 性能考虑:

    • 异步验证会序列化执行,若有多个异步规则,可合并或避免不必要的数据库调用。
  • 测试验证器:

    • 为每个 Validator 编写单元测试,覆盖正常和边界情况,确保规则生效。
  • 日志与监控:

    • 在全局捕获验证失败日志,统计常见错误,优化用户体验。

资源与扩展

  • GitHub:https://github.com/FluentValidation/FluentValidation

  • 文档:https://docs.fluentvalidation.net

  • NuGet 包:

    • FluentValidation:核心验证库。

    • FluentValidation.DependencyInjectionExtensionsASP.NET Core 集成。

  • 扩展:

    • 支持 Blazor、MVCWeb API

    • 提供多语言支持和客户端验证。

相关推荐
Aevget10 小时前
DevExpress WPF中文教程:Data Grid - 如何使用虚拟源?(二)
.net·wpf·界面控件·devexpress·ui开发·数据网格
从孑开始13 小时前
ManySpeech.MoonshineAsr 使用指南
人工智能·ai·c#·.net·私有化部署·语音识别·onnx·asr·moonshine
YuanlongWang13 小时前
C# 中,依赖注入(DI)的实现方式
c#
SmartSoftHelp开发辅助优化14 小时前
C# WinForm 编程高手:程序,进程,线程。程序,窗体,UI,后台。是如何协调工作的?深度解析>SmartSoftHelp魔法精灵工作室
microsoft·ui·c#
future_studio17 小时前
聊聊 Unity(小白专享、C# 小程序 之 加密存储)
开发语言·小程序·c#
c#上位机17 小时前
MefBootstrapper在Prism引导程序中的使用
c#·wpf·prism
玩泥巴的20 小时前
.NET驾驭Word之力:基于规则自动生成及排版Word文档
c#·word·.net·com互操作
专注VB编程开发20年20 小时前
VB.NET多线程排序算法实现:LINQ与正则表达式方法
排序算法·.net·linq
SunnyDays101121 小时前
C# 实现高保真 Excel 转 PDF(无需 Office 环境)
经验分享·c#·excel转pdf