ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

FluentValidation 内置验证器完全指南:开箱即用的 20+ 属性校验规则与消息占位符体系

FluentValidation 内置验证器完全指南:开箱即用的 20+ 属性校验规则与消息占位符体系 FluentValidation 内置验证器完全指南开箱即用的 20 属性校验规则与消息占位符体系【免费下载链接】FluentValidationA popular .NET validation library for building strongly-typed validation rules.项目地址: https://gitcode.com/gh_mirrors/fl/FluentValidation导读本文以 FluentValidation 官方文档 docs/built-in-validators.md 为骨架系统讲解 NotNull、NotEmpty、Equal/NotEqual、长度类、比较类、区间类、正则、Email、信用卡、枚举、PrecisionScale 等全部内置验证器的用法、默认错误消息与格式化占位符并结合仓库源码如 DefaultValidatorExtensions.cs 及各验证器实现说明每个规则的底层判定逻辑。读完本文你将掌握如何为 .NET 业务对象快速配置强类型校验规则以及如何利用{PropertyName}、{ComparisonValue}等占位符定制符合业务语义的错误消息。一、内置验证器总览与工作原理FluentValidation 为RuleFor(...)返回的规则构建器提供了一整套扩展方法这些扩展方法定义在 DefaultValidatorExtensions.cs 中约 40 个方法每个方法内部都通过SetValidator(...)将对应的验证器实例挂载到规则上。例如public static IRuleBuilderOptionsT, TProperty NotNullT, TProperty(this IRuleBuilderT, TProperty ruleBuilder) ruleBuilder.SetValidator(new NotNullValidatorT, TProperty());也就是说每一个内置验证器对应src/FluentValidation/Validators/目录下一个独立的验证器类它们都继承自PropertyValidatorT, TProperty并通过重写IsValid(ValidationContextT context, TProperty value)实现各自的判定逻辑。错误消息与占位符机制每个验证器的错误消息中可以包含特殊占位符在消息构造时被实际值替换。几乎所有验证器都支持以下三个通用占位符占位符含义{PropertyName}被校验的属性名{PropertyValue}属性的当前值{PropertyPath}属性的完整路径如嵌套对象中的Address.PostCode各验证器还会追加自己的专属占位符如{ComparisonValue}、{MinLength}、{MaxLength}、{From}、{To}等。从源码看这些参数由ValidationContextT.MessageFormatter.AppendArgument(...)写入例如 LengthValidator.cs 在失败时追加MinLength、MaxLength、TotalLength三个参数。默认消息模板则通过GetDefaultMessageTemplate(errorCode)返回它调用Localized(errorCode, Name)从 LanguageManager.cs 按验证器名称即错误码查找本地化文本——这一点与 docs/error-codes.md 中“ErrorCode 即消息查找键”的机制是同一套体系。二、空值与默认值系列NotNull、NotEmpty、Null、EmptyNotNull 验证器确保指定属性不为 nullRuleFor(customer customer.Surname).NotNull();失败示例消息Surname must not be empty.支持的占位符{PropertyName}、{PropertyValue}、{PropertyPath}。NotEmpty 验证器确保属性不为 null、不是空字符串、不是纯空白字符串对值类型而言还要求不等于该类型的默认值例如int的0。当作用于IEnumerable数组、集合、列表等时要求集合不为空。RuleFor(customer customer.Surname).NotEmpty();失败示例消息Surname should not be empty.从源码 NotEmptyValidator.cs 可以看到完整的判定顺序值为null→ 失败值为string且string.IsNullOrWhiteSpace(s)→ 失败值为ICollection且Count 0→ 失败值为IEnumerable且没有元素通过MoveNext()判断→ 失败否则检查!EqualityComparerTProperty.Default.Equals(value, default)即不等于类型默认值才通过。因此NotEmpty对0、default(int)、Guid.Empty等“等于默认值”的情况都会报错用途比NotNull更严格。Empty 验证器Empty是NotEmpty的反向验证器要求属性为 null或等于该类型的默认值作用于IEnumerable时要求集合为空。其判定逻辑与 NotEmpty 一一对应见 EmptyValidator.csRuleFor(x x.Surname).Empty();失败示例消息Surname must be empty.Null 验证器Null是NotNull的反向验证器只要求属性值为 nullRuleFor(x x.Surname).Null();三、相等与不等系列Equal、NotEqualEqual 验证器确保属性的值等于某个特定值或等于另一个属性的值// 等于特定值 RuleFor(customer customer.Surname).Equal(Foo); // 等于另一属性跨属性比较 RuleFor(customer customer.Password).Equal(customer customer.PasswordConfirmation);失败示例消息Surname should be equal to FooNotEqual 验证器确保属性的值不等于某个特定值或不等于另一个属性的值// 不等于特定值 RuleFor(customer customer.Surname).NotEqual(Foo); // 不等于另一属性 RuleFor(customer customer.Surname).NotEqual(customer customer.Forename);失败示例消息Surname should not be equal to Foo两者的专属占位符一致占位符含义{PropertyName}被校验的属性名{ComparisonValue}参与比较的值{ComparisonProperty}被比较的另一个属性名若有{PropertyValue}属性的当前值{PropertyPath}属性的完整路径字符串比较的自定义 Comparer对字符串默认采用序号Ordinal比较见 DefaultValidatorExtensions.cs 中comparer ?? StringComparer.Ordinal。需要文化相关的比较时可显式传入比较器RuleFor(customer customer.Surname).NotEqual(Foo, StringComparer.OrdinalIgnoreCase); RuleFor(customer customer.Surname).Equal(Foo, StringComparer.OrdinalIgnoreCase);如果希望进行文化特定的比较将第二个参数换成StringComparer.CurrentCulture即可。此外Equal/NotEqual的跨属性重载接收ExpressionFuncT, TProperty的版本也支持第三个IEqualityComparerTProperty参数从源码 EqualValidator.cs 看比较逻辑为传入 comparer 时用_comparer.Equals(...)否则回退到Equals(...)。四、字符串长度系列Length、MaximumLength、MinimumLengthLength 验证器确保字符串属性的长度在指定范围内含边界RuleFor(customer customer.Surname).Length(1, 250); // 长度必须在 1 到 250 之间含失败示例消息Surname must be between 1 and 250 characters. You entered 251 characters.注意Length 仅适用于字符串属性且它不检查属性是否为 null——null 值会直接通过源码 LengthValidator.cs 中if (value null) return true;。如果需要“非空且限长”应叠加NotNull()或NotEmpty()。占位符占位符含义{PropertyName}被校验的属性名{MinLength}最小长度{MaxLength}最大长度{TotalLength}实际输入字符数{PropertyValue}属性的当前值{PropertyPath}属性的完整路径MaximumLength 与 MinimumLength 验证器RuleFor(customer customer.Surname).MaximumLength(250); // 最多 250 个字符 RuleFor(customer customer.Surname).MinimumLength(10); // 至少 10 个字符失败示例消息分别为The length of Surname must be 250 characters or fewer. You entered 251 characters.The length of Surname must be at least 10 characters. You entered 5 characters.从源码看三者是同一继承体系MaximumLengthValidatorT等价于Length(0, max)MinimumLengthValidatorT等价于Length(min, -1)而ExactLengthValidatorT对应Length(int exactLength)单参重载等价于Length(len, len)——其中max -1表示不设上限见 LengthValidator.cs 的判定length max max ! -1。因此 MinimumLength 只限制下限不会限制字符串过长。动态长度Func 重载Length 系列还提供接收FuncT, int的重载长度上下限可根据被校验对象实例动态计算在IsValid中通过context.InstanceToValidate求值见 LengthValidator.csRuleFor(customer customer.Surname).Length(customer customer.MinLen, customer customer.MaxLen);五、数值/可比较类型系列LessThan、LessThanOrEqualTo、GreaterThan、GreaterThanOrEqualTo四个比较验证器分别要求属性值小于、小于等于、大于、大于等于某个特定值或另一个属性的值// 小于特定值 RuleFor(customer customer.CreditLimit).LessThan(100); // 小于另一属性 RuleFor(customer customer.CreditLimit).LessThan(customer customer.MaxCreditLimit); // 小于等于 RuleFor(customer customer.CreditLimit).LessThanOrEqualTo(100); RuleFor(customer customer.CreditLimit).LessThanOrEqualTo(customer customer.MaxCreditLimit); // 大于 RuleFor(customer customer.CreditLimit).GreaterThan(0); RuleFor(customer customer.CreditLimit).GreaterThan(customer customer.MinimumCreditLimit); // 大于等于 RuleFor(customer customer.CreditLimit).GreaterThanOrEqualTo(1); RuleFor(customer customer.CreditLimit).GreaterThanOrEqualTo(customer customer.MinimumCreditLimit);失败示例消息分别为Credit Limit must be less than 100.Credit Limit must be less than or equal to 100.Credit Limit must be greater than 0.Credit Limit must be greater than or equal to 1.适用前提仅对实现IComparableT的类型有效。四个验证器共享基类 AbstractComparisonValidator.cs其中有两个值得注意的底层行为null 值直接通过IsValid中if (propertyValue null) return true;注释明确说明如果还要保证非空需另外叠加NotNull()规则见该文件 第 69-74 行。基类维护Comparison枚举Equal/NotEqual/LessThan/GreaterThan/GreaterThanOrEqual/LessThanOrEqual供客户端校验如 WebAPI 集成与元数据读取使用。占位符占位符含义{PropertyName}被校验的属性名{ComparisonValue}被比较的值{ComparisonProperty}被比较的属性名若有{PropertyValue}属性的当前值{PropertyPath}属性的完整路径六、区间系列InclusiveBetween、ExclusiveBetweenInclusiveBetween 验证器闭区间要求属性值位于两个指定值之间含端点RuleFor(x x.Id).InclusiveBetween(1, 10);失败示例消息Id must be between 1 and 10. You entered 0.ExclusiveBetween 验证器开区间要求属性值位于两个指定值之间不含端点RuleFor(x x.Id).ExclusiveBetween(1, 10);失败示例消息Id must be between 1 and 10 (exclusive). You entered 1.因为 1 是下界开区间下不合法两者的占位符一致占位符含义{PropertyName}被校验的属性名{From}区间下界{To}区间上界{PropertyValue}属性的当前值{PropertyPath}属性的完整路径从 DefaultValidatorExtensions.cs 可见这两个方法有多组重载默认要求TProperty : IComparableTProperty, IComparable另有无约束的IComparerTProperty版本可自定义比较逻辑以及针对NullableTProperty的版本。可选比较器示例RuleFor(x x.Amount).InclusiveBetween(1m, 10m, Comparerdecimal.Default);七、谓词与正则Must、MatchesPredicate 验证器MustMust将一个委托传给属性值执行自定义校验逻辑委托返回false即校验失败RuleFor(customer customer.Surname).Must(surname surname Foo);失败示例消息The specified condition was not met for Surname还有一个重载会额外传入被校验的父对象实例便于在谓词内部与其他属性比较RuleFor(customer customer.Surname).Must((customer, surname) surname ! customer.Forename);文档特别提示上面这个例子其实用跨属性版本的NotEqual更合适——能用内置验证器表达的需求优先用内置验证器。从源码 DefaultValidatorExtensions.cs 看Must共有三个同步重载语义逐级增强Must(FuncTProperty, bool)—— 只接收属性值Must(FuncT, TProperty, bool)—— 接收父对象与属性值Must(FuncT, TProperty, ValidationContextT, bool)—— 额外接收ValidationContextT可在谓词中访问上下文如注入的自定义状态。对应地还提供MustAsync系列接收CancellationToken返回Taskbool用于异步自定义校验详见 docs/async.md。占位符{PropertyName}、{PropertyValue}、{PropertyPath}。Regular Expression 验证器Matches确保字符串属性匹配给定的正则表达式RuleFor(customer customer.Surname).Matches(some regex here);失败示例消息Surname is not in the correct format.占位符占位符含义{PropertyName}被校验的属性名{PropertyValue}属性的当前值{RegularExpression}未被匹配到的正则表达式{PropertyPath}属性的完整路径Matches的重载同样丰富见 DefaultValidatorExtensions.cs字符串模式 可选RegexOptions直接传Regex实例FuncT, string/FuncT, Regex形式按实例动态计算正则在 .NET 7 上字符串参数标注了[StringSyntax(StringSyntaxAttribute.Regex)]IDE 会提供正则语法提示。八、格式校验EmailAddress、CreditCardEmail 验证器确保属性值符合邮箱地址格式RuleFor(customer customer.Email).EmailAddress();失败示例消息Email is not a valid email address.Email 验证器有两种工作模式由EmailValidationMode枚举控制定义在 EmailValidator.cs默认模式AspNetCoreCompatible只做简单检查——字符串包含一个符号且既不在开头也不在结尾。这是有意为之的简化校验目的是与 ASP.NET Core 的EmailAddressAttribute行为保持一致。社区对此的官方解释是“检查故意保持简单因为要做到万无一失非常困难邮箱最终应通过发送确认邮件的流程来验证该校验属性只用于拦截明显错误的值。”对应实现AspNetCoreCompatibleEmailValidatorT的IsValid只有四行逻辑EmailValidator.cs的下标必须 0、不等于末位下标、且与最后一次出现的下标相同即只有一个。旧模式Net4xRegex使用与 .NET 4.x 版本 ASP.NETEmailAddressAttribute一致的正则表达式RuleFor(x x.Email).EmailAddress(EmailValidationMode.Net4xRegex);注意该模式已标记[Obsolete]会产生编译警告因为基于正则的邮箱校验不被推荐。版本差异FluentValidation 9 起ASP.NET Core 兼容的“简单检查”成为默认模式在 8.x 及更早版本中Regex 模式才是默认值。Credit Card 验证器检查字符串属性是否可能是有效的信用卡卡号RuleFor(x x.CreditCard).CreditCard();失败示例消息Credit Card is not a valid credit card number.从源码 CreditCardValidator.cs 看其算法逻辑来自 ASP.NET MVC3 的CreditCardAttribute先移除字符串中的-与空格再从右向左遍历数字执行Luhn 校验算法隔位乘 2、逐位求和、最终checksum % 10 0才算通过任何非数字字符都会直接导致失败。它校验的是“数学上合法”的卡号而非真实存在的卡。占位符{PropertyName}、{PropertyValue}、{PropertyPath}。九、枚举系列IsInEnum、IsEnumNameEnum 验证器IsInEnum检查数值是否是枚举中定义的合法值。这个验证器解决的是 C# 的一个经典陷阱——把不存在的数值强转为枚举类型时编译器不会报错public enum ErrorLevel { Error 1, Warning 2, Notice 3 } public class Model { public ErrorLevel ErrorLevel { get; set; } } var model new Model(); model.ErrorLevel (ErrorLevel)4; // 编译通过但 4 对 ErrorLevel 而言是非法值IsInEnum可以阻止这种情况发生RuleFor(x x.ErrorLevel).IsInEnum();失败示例消息Error Level has a range of values which does not include 4.从源码 EnumValidator.cs 看其判定逻辑为值为 null → 通过自动解包可空枚举Nullable.GetUnderlyingType非枚举类型 → 失败带[Flags]特性的枚举走专门的位运算判定IsFlagsEnumDefined/EvaluateFlagEnumValues逐位与操作验证组合值是否可由已定义项组成普通枚举则用Enum.IsDefined判定。Enum Name 验证器IsEnumName检查字符串是否是合法的枚举名称// 区分大小写比较 RuleFor(x x.ErrorLevelName).IsEnumName(typeof(ErrorLevel)); // 不区分大小写比较 RuleFor(x x.ErrorLevelName).IsEnumName(typeof(ErrorLevel), caseSensitive: false);失败示例消息Error Level has a range of values which does not include Foo.实现细节见 StringEnumValidator.cs构造时通过Enum.GetNames(enumType)缓存枚举名列表若传入类型不是枚举会抛出ArgumentOutOfRangeException比较使用StringComparison.Ordinal区分大小写或StringComparison.OrdinalIgnoreCase不区分大小写故意复用 EnumValidator 的错误消息模板Localized(errorCode, EnumValidator)。十、数值精度PrecisionScale检查 decimal 值是否满足指定的精度precision总位数与标度scale小数位数RuleFor(x x.Amount).PrecisionScale(4, 2, false);失败示例消息Amount must not be more than 4 digits in total, with allowance for 2 decimals. 5 digits and 3 decimals were found.占位符占位符含义{PropertyName}被校验的属性名{ExpectedPrecision}期望精度总位数{ExpectedScale}期望标度小数位数{Digits}属性值的实际总位数{ActualScale}属性值的实际小数位数{PropertyValue}属性的当前值{PropertyPath}属性的完整路径第三个参数 ignoreTrailingZeros第三个参数ignoreTrailingZeros表示是否忽略小数点后的尾部零为false时123.4500被视为精度 7、标度 4为true时123.4500被视为精度 5、标度 2。隐含的可接受取值范围该验证器还隐含规定了可接受的值域。例如.PrecisionScale(3, 1)只接受-99.9到99.9含端点之间的值——因为整数部分最多只能有3 - 1 2位且这一限制与ignoreTrailingZeros无关。对应源码 PrecisionScaleValidator.cs 的判定即为实际标度超过期望标度或实际整数位数超过Precision - Scale即失败。构造器还会校验参数合法性scale与precision必须为非负整数且precision scale否则抛ArgumentOutOfRangeException。命名变更在 FluentValidation11.4 之前该方法名为ScalePrecision且两个参数顺序相反先 scale 后 precision。升级到 11.4 时需要注意参数顺序调整。十一、占位符、错误码与本地化的统一视角回顾全文可以看到所有内置验证器共享同一套错误消息体系每个验证器有一个名称Name即默认错误码如NotNullValidator、EmailValidator、EnumValidator默认消息模板由错误码作为键从LanguageManager的本地化资源中查找仓库 Resources/Languages 下提供了 50 种语言实现占位符在失败时通过MessageFormatter.AppendArgument填充实际值。这套机制与自定义错误码直接打通。按 docs/error-codes.md 的说明你可以用WithErrorCode覆盖错误码例如RuleFor(person person.Surname).NotNull().WithErrorCode(ERR1234);此时ValidationFailure.ErrorCode即为ERR1234若想复用某个验证器的默认消息例如给自定义Must()规则套用NotNull的默认消息直接WithErrorCode(NotNullValidator)即可。自定义消息模板中同样可以使用上述占位符详见 docs/custom-validators.md 与 docs/localization.md。十二、如何在仓库中验证这些行为仓库自带完整的测试套件 src/FluentValidation.Tests/可对照验证本文涉及的各验证器行为NotEmptyTester.cs、EmptyTester.cs、NotNullTester.cs、NullTester.cs —— 空值系列EqualValidatorTests.cs、NotEqualValidatorTests.cs —— 相等系列含StringComparer用例LengthValidatorTests.cs、ExactLengthValidatorTester.cs —— 长度系列GreaterThanValidatorTester.cs、LessThanValidatorTester.cs、GreaterThanOrEqualToValidatorTester.cs、LessThanOrEqualToValidatorTester.cs —— 比较系列InclusiveBetweenValidatorTests.cs、ExclusiveBetweenValidatorTests.cs —— 区间系列PredicateValidatorTester.cs、RegularExpressionValidatorTests.cs —— 谓词与正则EmailValidatorTests.cs、CreditCardValidatorTests.cs —— 格式校验EnumValidatorTests.cs、StringEnumValidatorTests.cs —— 枚举系列PrecisionScaleValidatorTests.cs —— 精度校验含ignoreTrailingZeros与值域边界用例。十三、快速选型参考场景推荐验证器属性不能为 nullNotNull()字符串不能为空/空白值类型不能为默认值集合不能为空NotEmpty()属性必须为 null / 默认值 / 空集合Null()/Empty()必须/不得等于某值或另一属性Equal()/NotEqual()字符串长度范围、上限、下限、精确长度Length()/MaximumLength()/MinimumLength()数值与可比较类型的大小关系含跨属性LessThan()/LessThanOrEqualTo()/GreaterThan()/GreaterThanOrEqualTo()值域区间含/不含端点InclusiveBetween()/ExclusiveBetween()内置验证器无法表达的业务逻辑Must()同步/MustAsync()异步正则匹配Matches()邮箱格式EmailAddress()信用卡卡号格式CreditCard()数值必须是枚举合法成员IsInEnum()字符串必须是枚举名称IsEnumName()decimal 精度/小数位数约束PrecisionScale()结语FluentValidation 的内置验证器覆盖了业务校验的绝大多数常见场景且每一个验证器都提供了可格式化的默认消息、完整的占位符参数与本地化支持。理解这些验证器的底层判定逻辑如 NotEmpty 对ICollection/IEnumerable的特殊处理、比较类验证器对 null 的直接放行、Email 默认模式的“简单检查”哲学、Flags 枚举的位运算判定、PrecisionScale 的隐含值域能帮助你写出更精准、更符合预期的校验规则避免“规则生效了但行为与直觉不符”的坑。当内置验证器无法满足需求时再考虑用Must或自定义验证器扩展详见 docs/custom-validators.md。【免费下载链接】FluentValidationA popular .NET validation library for building strongly-typed validation rules.项目地址: https://gitcode.com/gh_mirrors/fl/FluentValidation创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表