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

作者:唐青枫日期:2025/10/21

简介

  • FluentValidation 是一个基于“流式 API”(Fluent API)的 .NET 验证框架,用于在应用层对模型(DTO、ViewModel、Entity 等)进行声明式验证。
  • 核心优势:
    • 高可读性:通过链式方法配置验证规则,逻辑清晰;
    • 可复用:将验证代码从业务逻辑中分离,易于单元测试;
    • 丰富的内置规则:邮箱、长度、正则、多字段联动、集合验证等;
    • 可扩展:支持自定义验证器、异步验证、跨属性验证。
  • 适用场景:
    • Web API 模型验证
    • 复杂业务规则验证
    • 需要高度可定制验证逻辑的系统
    • 多语言验证消息需求
    • 需要测试覆盖的验证逻辑

安装与基础配置

  • NuGet
1Install-Package FluentValidation
2Install-Package FluentValidation.DependencyInjectionExtensions
3
  • 引用命名空间
1using FluentValidation;
2

核心用法

定义 Model 与 Validator

1public class UserDto
2{
3    public string Username { get; set; }
4    public string Email    { get; set; }
5    public int    Age      { get; set; }
6}
7
8public class UserDtoValidator : AbstractValidator<UserDto>
9{
10    public UserDtoValidator()
11    {
12        // NotEmpty / NotNull
13        RuleFor(x => x.Username)
14            .NotEmpty().WithMessage("用户名不能为空")
15            .Length(3, 20).WithMessage("用户名长度须在3到20之间");
16
17        // Email 格式
18        RuleFor(x => x.Email)
19            .NotEmpty().WithMessage("邮箱不能为空")
20            .EmailAddress().WithMessage("邮箱格式不正确");
21
22        // 数值范围
23        RuleFor(x => x.Age)
24            .InclusiveBetween(18, 120)
25            .WithMessage("年龄须在18到120之间");
26    }
27}
28

执行验证

1var user = new UserDto { Username = "", Email = "bad", Age = 10 };
2var validator = new UserDtoValidator();
3var result = validator.Validate(user);
4
5if (!result.IsValid)
6{
7    foreach (var failure in result.Errors)
8    {
9        Console.WriteLine($"{failure.PropertyName}: {failure.ErrorMessage}");
10    }
11}
12
13// ASP.NET Core 自动验证
14[HttpPost]
15public IActionResult CreateUser([FromBody] UserDto user)
16{
17    // 模型绑定后自动验证
18    if (!ModelState.IsValid)
19    {
20        return BadRequest(ModelState);
21    }
22    // ...
23}
24

常用验证规则

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

进阶特性

跨属性验证

1RuleFor(x => x.EndDate)
2    .GreaterThan(x => x.StartDate)
3    .WithMessage("结束时间必须晚于开始时间");
4

条件验证

1RuleFor(x => x.Password)
2    .NotEmpty().When(x => x.RequirePassword)
3    .WithMessage("密码不能为空");
4

集合与嵌套对象

1public class OrderDto { public List<OrderItemDto> Items { get; set; } }
2public class OrderItemDto { public int Quantity { get; set; } }
3
4public class OrderDtoValidator : AbstractValidator<OrderDto>
5{
6    public OrderDtoValidator()
7    {
8        RuleForEach(x => x.Items)
9            .ChildRules(items =>
10            {
11                items.RuleFor(i => i.Quantity)
12                     .GreaterThan(0).WithMessage("数量须大于0");
13            });
14    }
15}
16

异步验证

1RuleFor(x => x.Username)
2    .MustAsync(async (name, ct) => !await userRepo.ExistsAsync(name))
3    .WithMessage("用户名已存在");
4

级联验证

使用 CascadeMode.Stop 提高性能

1RuleFor(x => x.Name).Cascade(CascadeMode.Stop).NotEmpty().MaximumLength(50);
2

验证规则组织

规则集(RuleSets)

1public class UserValidator : AbstractValidator<UserDto>
2{
3    public UserValidator()
4    {
5        // 公共规则
6        RuleFor(user => user.Name).NotEmpty();
7        
8        // 创建规则集
9        RuleSet("Admin", () => 
10        {
11            RuleFor(user => user.IsAdmin).Must(b => b == true)
12                .WithMessage("管理员用户必须设置管理员标志");
13        });
14
15        RuleSet("PaymentInfo", () => {
16            RuleFor(c => c.CreditCardNumber).NotEmpty();
17            RuleFor(c => c.CreditCardExpiry).NotEmpty();
18        });
19    }
20}
21
22// 使用指定规则集
23var result = validator.Validate(user, options => 
24{
25    options.IncludeRuleSets("Admin", "PaymentInfo");
26});
27

继承与组合

1// 基础验证器
2public class PersonValidator : AbstractValidator<PersonDto>
3{
4    public PersonValidator()
5    {
6        RuleFor(p => p.Name).NotEmpty();
7        RuleFor(p => p.BirthDate).LessThan(DateTime.Now);
8    }
9}
10
11// 继承扩展
12public class EmployeeValidator : PersonValidator
13{
14    public EmployeeValidator()
15    {
16        Include(new PersonValidator()); // 包含基础规则
17        RuleFor(e => e.EmployeeId).NotEmpty();
18        RuleFor(e => e.Department).NotEmpty();
19    }
20}
21
22// 组合验证
23public class AdvancedUserValidator : AbstractValidator<UserDto>
24{
25    public AdvancedUserValidator()
26    {
27        Include(new UserValidator());
28        RuleFor(u => u.SecurityLevel).InclusiveBetween(1, 5);
29    }
30}
31

自定义扩展

自定义验证器

1public static class CustomValidators
2{
3    public static IRuleBuilderOptions<T, string> ValidIdCard<T>(this IRuleBuilder<T, string> ruleBuilder)
4    {
5        return ruleBuilder.Must(id => Regex.IsMatch(id, @"^[1-9]\d{16}[0-9X]$"))
6                          .WithMessage("身份证格式不正确");
7    }
8}
9
10// 使用
11RuleFor(x => x.IdCard).ValidIdCard();
12

自定义属性比较器

1public class DateRangeValidator : PropertyValidator
2{
3    public DateRangeValidator() : base("{PropertyName} 时间范围不合法") { }
4
5    protected override bool IsValid(PropertyValidatorContext context)
6    {
7        var dto = (MyDto)context.InstanceToValidate;
8        return dto.End > dto.Start;
9    }
10}
11
12// 在 Validator 中
13RuleFor(x => x.Start).SetValidator(new DateRangeValidator());
14

ASP.NET Core 集成

注册服务(Program.cs)

1builder.Services
2    .AddControllers()
3    .AddFluentValidation(cfg =>
4    {
5        // 自动注册当前程序集所有继承 AbstractValidator 的类型
6        cfg.RegisterValidatorsFromAssemblyContaining<Startup>();
7        // 禁用 DataAnnotations 验证(可选)
8        cfg.RunDefaultMvcValidationAfterFluentValidationExecutes = false;
9    });
10

自动触发

  • ASP.NET Core 在模型绑定后会自动调用对应 Validator,并将错误添加到 ModelState
  • Controller 中可直接检查 if (!ModelState.IsValid) 或依赖 [ApiController] 的自动返回行为。

自定义错误响应

1// 配置全局异常处理
2services.Configure<ApiBehaviorOptions>(options =>
3{
4    options.InvalidModelStateResponseFactory = context =>
5    {
6        var errors = context.ModelState
7            .Where(e => e.Value.Errors.Count > 0)
8            .ToDictionary(
9                kvp => kvp.Key,
10                kvp => kvp.Value.Errors.Select(e => e.ErrorMessage).ToArray()
11            );
12        
13        return new BadRequestObjectResult(new
14        {
15            Code = 400,
16            Message = "请求验证失败",
17            Errors = errors
18        });
19    };
20});
21

最佳实践

  • 分层组织规则:
    • 针对同一模型,可拆分多个 Validator(或使用 Include()),保持单一职责。
  • 复用规则集:
    • 对于常见字段(如邮箱、手机号),可定义公共规则并通过扩展方法重用。
  • 错误消息国际化:
    • 将消息文本放入资源文件,使用 .WithMessage(x => Resources.FieldRequired)
  • 性能考虑:
    • 异步验证会序列化执行,若有多个异步规则,可合并或避免不必要的数据库调用。
  • 测试验证器:
    • 为每个 Validator 编写单元测试,覆盖正常和边界情况,确保规则生效。
  • 日志与监控:
    • 在全局捕获验证失败日志,统计常见错误,优化用户体验。

资源与扩展


C#.NET FluentValidation 全面解析:优雅实现对象验证》 是转载文章,点击查看原文


相关推荐


解密prompt系列62. Agent Memory一览 - MATTS & CFGM & MIRIX
风雨中的小七2025/10/20

今天我们再来聊聊AI智能体中至关重要的组件——记忆系统,它能有效避免的Agent像只只有7秒记忆的金鱼,不断重复错误,循环往复。 记忆的两种面孔:LLM Memory vs Agent Memory 之前我们探讨过Mem0和LlamaIndex对大模型记忆的工程化实现,但这两个库更侧重于LLM Memory而非Agent Memory。这两者有何不同?本质上Agent Memory是包含了LLM Memory的。那增量的差异来自 LLM Memory:更像是事实备忘录,记录对话中的具体事实和场


Docker 实战教程(7) | 镜像管理和仓库操作
致宏Rex2025/10/18

前边篇章我们已经介绍了 Docker 基础概念和安装Docker 常用命令实践Docker 网络机制详解Docker 数据卷和挂载Dockerfile 编写和镜像构建Docker Compose 多容器编排 本篇为系列最后一章,介绍 Docker 的镜像管理和仓库操作。 本教程侧重于命令实践和理解,提供可在本地环境测试的实例,每章结束都有总结要点。 7.1 Docker 镜像管理基础 我们之前介绍过一期自建镜像站的若干方案: Docker 管理 | 代理配置、内网共享和 Ha


Redis(67)Redis的SETNX命令是如何工作的?
Victor3562025/10/17

SETNX 是 Redis 提供的一个原子性命令,用于在键不存在时设置键值对。SETNX 是 "SET if Not eXists" 的缩写,主要应用于实现分布式锁等场景。下面是详细的工作原理以及如何在 Java 中使用 SETNX 命令的示例。 SETNX 命令的工作原理 检查键是否存在:Redis 首先检查给定的键是否存在。 设置键值对:如果键不存在,Redis 将设置键值对,并返回 1。 不做任何操作:如果键已经存在,Redis 不会设置新的值,并返回 0。 Java 使用 SETNX


什么是断言?
你的人类朋友2025/10/15

前言 你好啊,我是你的人类朋友! 今天主要介绍断言(Assertion)相关的知识。 先定个小目标:看完你将可以用自己的语言向别人解释:啥是断言? 正文 一、初印象 断言(Assertion)在信息安全领域中,是一种用于证明身份的数字凭证。 简单来说,断言是一个【包含身份声明信息的安全令牌】,用于向服务端证明客户端的合法身份。 它通常包含三要素: 身份标识(如:AppId) 验证凭证(如:加密的 Secret) 完整性证明(如:数字签名) 二、工作流程 断言的工作过程可以分为四个核心步骤:


Kotlin互斥锁(Mutex):协程的线程安全守护神
稀有猿诉2025/10/14

本文译自「Kotlin Mutex: Thread-Safe Concurrency for Coroutines」,原文链接carrion.dev/en/posts/ko…,由Ignacio Carrión发布于2025年10月3日。 使用 Kotlin 协程构建并发应用程序时,保护共享的可变状态至关重要。虽然传统的 Java 同步工具(例如 synchronized 块和 ReentrantLock)可以正常工作,但它们会阻塞线程,并且与协程的挂起模型不兼容。因此,引入 Mutex——一


Coze源码分析-资源库-编辑数据库-前端源码-核心逻辑与接口
lypzcgf2025/10/13

编辑数据库逻辑 1. 表单验证系统 文件位置:frontend/packages/data/memory/database-v2-base/src/components/base-info-modal/index.tsx 编辑数据库表单的验证规则: // 数据库名称验证规则 const nameValidationRules = [ { required: true, whitespace: true, message: I18n.t('database_name_c


C++ 中 rfind 方法详解
oioihoii2025/10/11

rfind 是 C++ 字符串类 std::string 和 std::wstring 的成员函数,用于从字符串的末尾向前搜索指定的子字符串或字符。 函数原型 // 搜索整个字符串 size_type rfind(const basic_string& str, size_type pos = npos) const noexcept; size_type rfind(const CharT* s, size_type pos = npos) const; size_type rfind(Cha


HTML 元素帮助手册
hubenchang05152025/10/9

#HTML 元素帮助手册 转载自 MDN #主根元素 元素描述<html>表示一个 HTML 文档的根(顶级元素),所以它也被称为根元素。所有其它元素必须是此元素的后代。 #文档元数据 元素描述<base>指定用于一个文档中包含的所有相对 URL 的根 URL。一份中只能有一个该元素。<head>包含文档相关的配置信息(元数据),包括文档的标题、脚本和样式表等。<link>指定当前文档与外部资源的关系。该元素最常用于链接 CSS,此外也可以被用来创建站点图标(比如“favicon”样式图标和


Rust语言简介
xqlily2025/10/8

Rust是一种现代的系统编程语言,由Mozilla基金会开发,并于2010年首次发布。它旨在解决传统语言(如C和C++)中的常见问题,如内存安全错误和并发性挑战,同时保持高性能。Rust强调安全性、速度和并发性,使其在系统开发、嵌入式系统和WebAssembly等领域广受欢迎。下面,我将从核心特点、优势和应用场景入手,逐步介绍Rust,并附上一个简单示例。 核心特点 内存安全:Rust通过独特的“所有权系统”避免空指针解引用、缓冲区溢出等常见错误。例如,编译器在编译时检查内存访问,确保


SpringBoot安全进阶:利用门限算法加固密钥与敏感配置
风象南2025/10/7

一、背景:单点密钥的隐患 在企业信息系统中,密钥是最核心的安全资产。无论是数据库加密、支付签名,还是用户隐私保护,背后都依赖一把"超级钥匙"。 然而,现实中我们常常遇到这些场景: 单点保管风险:某个核心密钥仅由一个运维人员或系统服务持有,一旦泄露或者丢失,整个系统可能崩盘。 操作合规问题:金融或政府系统中,法规往往要求多方共同参与,才能执行高风险操作。 分布式架构挑战:在云环境或多数据中心下,如何既能保证数据安全,又能防止任何一个节点"作恶"? 一句话总结: 👉 一个人掌握所有密钥 = 系统安

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0