FluentValidation 链属性验证问题
FluentValidation Chain Properties Validation Issue
我刚刚使用 JeremySkinner's FluentValidation 实现了 INotifyDataErrorInfo。但是我在验证复杂属性时遇到了一些困难。
例如,我想验证国籍 属性:
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.NotEmpty()
.Length(0, 255);
然而,这种看起来很平静的代码有两个主要问题:
1) SelectedItem为空时抛出空引用异常。
要是能写成这样就好了:
CustomizedRuleFor(vm => vm.Nationality.SelectedItem.Value)
.NotEmpty(); //add some stuff here
2) 错误消息中的完整 属性 路径,例如:"The specified condition was not met for 'Nationality. Selected Item. Value'
"。我只需要 'Nationality'
在错误消息中。
我知道我可以使用 WithMessage 扩展方法覆盖错误消息,但不想对每个验证规则都这样做。
你有什么建议吗?谢谢
问题一
您可以通过两种方式解决获取 NullReferenceException
的问题,这取决于客户端验证支持的必要性和更改模型的可用性 class:
修改模型的默认构造函数以创建具有空值的 SelectedItem
:
public class Nationality
{
public Nationality()
{
// use proper class instead of SelectableItem
SelectedItem = new SelectableItem { Value = null };
}
}
或您可以改用条件验证,如果 SelectedItem 在不同情况下应该为空并且这对您来说是正常情况:
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.When(vm => vm.Nationality.SelectedItem != null)
.NotEmpty()
.Length(0, 255);
在这种情况下,验证器将仅在条件为真时进行验证,但条件验证不支持客户端验证(如果您想与 ASP.NET MVC 集成)。
问题2.
要保存默认错误消息格式,请将 WithName
方法添加到规则生成器方法链中:
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.WithName("Nationality") // replace "Nationality.SelectedItem.Value" string with "Nationality" in error messages for both rules
.NotEmpty()
.Length(0, 255);
更新:通用解决方案
规则生成器的扩展方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using FluentValidation;
using FluentValidation.Attributes;
using FluentValidation.Internal;
public static class FluentValidationExtensions
{
public static IRuleBuilderOptions<TModel, TProperty> ApplyChainValidation<TModel, TProperty>(this IRuleBuilderOptions<TModel, TProperty> builder, Expression<Func<TModel, TProperty>> expr)
{
// with name string
var firstMember = PropertyChain.FromExpression(expr).ToString().Split('.')[0]; // PropertyChain is internal FluentValidation class
// create stack to collect model properties from property chain since parents to childs to check for null in appropriate order
var reversedExpressions = new Stack<Expression>();
var getMemberExp = new Func<Expression, MemberExpression>(toUnwrap =>
{
if (toUnwrap is UnaryExpression)
{
return ((UnaryExpression)toUnwrap).Operand as MemberExpression;
}
return toUnwrap as MemberExpression;
}); // lambda from PropertyChain implementation
var memberExp = getMemberExp(expr.Body);
var firstSkipped = false;
// check only parents of property to validate
while (memberExp != null)
{
if (firstSkipped)
{
reversedExpressions.Push(memberExp); // don't check target property for null
}
firstSkipped = true;
memberExp = getMemberExp(memberExp.Expression);
}
// build expression that check parent properties for null
var currentExpr = reversedExpressions.Pop();
var whenExpr = Expression.NotEqual(currentExpr, Expression.Constant(null));
while (reversedExpressions.Count > 0)
{
whenExpr = Expression.AndAlso(whenExpr, Expression.NotEqual(currentExpr, Expression.Constant(null)));
currentExpr = reversedExpressions.Pop();
}
var parameter = expr.Parameters.First();
var lambda = Expression.Lambda<Func<TModel, bool>>(whenExpr, parameter); // use parameter of source expression
var compiled = lambda.Compile();
return builder
.WithName(firstMember)
.When(model => compiled.Invoke(model));
}
}
和用法
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.NotEmpty()
.Length(0, 255)
.ApplyChainValidation(vm => vm.Nationality.SelectedItem.Value);
无法避免冗余表达式重复,因为使用内部扩展方法的When()
方法仅适用于先前定义的规则。
注意:解决方案仅适用于具有引用类型的链。
我刚刚使用 JeremySkinner's FluentValidation 实现了 INotifyDataErrorInfo。但是我在验证复杂属性时遇到了一些困难。
例如,我想验证国籍 属性:
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.NotEmpty()
.Length(0, 255);
然而,这种看起来很平静的代码有两个主要问题:
1) SelectedItem为空时抛出空引用异常。
要是能写成这样就好了:
CustomizedRuleFor(vm => vm.Nationality.SelectedItem.Value)
.NotEmpty(); //add some stuff here
2) 错误消息中的完整 属性 路径,例如:"The specified condition was not met for 'Nationality. Selected Item. Value'
"。我只需要 'Nationality'
在错误消息中。
我知道我可以使用 WithMessage 扩展方法覆盖错误消息,但不想对每个验证规则都这样做。
你有什么建议吗?谢谢
问题一
您可以通过两种方式解决获取 NullReferenceException
的问题,这取决于客户端验证支持的必要性和更改模型的可用性 class:
修改模型的默认构造函数以创建具有空值的 SelectedItem
:
public class Nationality
{
public Nationality()
{
// use proper class instead of SelectableItem
SelectedItem = new SelectableItem { Value = null };
}
}
或您可以改用条件验证,如果 SelectedItem 在不同情况下应该为空并且这对您来说是正常情况:
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.When(vm => vm.Nationality.SelectedItem != null)
.NotEmpty()
.Length(0, 255);
在这种情况下,验证器将仅在条件为真时进行验证,但条件验证不支持客户端验证(如果您想与 ASP.NET MVC 集成)。
问题2.
要保存默认错误消息格式,请将 WithName
方法添加到规则生成器方法链中:
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.WithName("Nationality") // replace "Nationality.SelectedItem.Value" string with "Nationality" in error messages for both rules
.NotEmpty()
.Length(0, 255);
更新:通用解决方案
规则生成器的扩展方法
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using FluentValidation;
using FluentValidation.Attributes;
using FluentValidation.Internal;
public static class FluentValidationExtensions
{
public static IRuleBuilderOptions<TModel, TProperty> ApplyChainValidation<TModel, TProperty>(this IRuleBuilderOptions<TModel, TProperty> builder, Expression<Func<TModel, TProperty>> expr)
{
// with name string
var firstMember = PropertyChain.FromExpression(expr).ToString().Split('.')[0]; // PropertyChain is internal FluentValidation class
// create stack to collect model properties from property chain since parents to childs to check for null in appropriate order
var reversedExpressions = new Stack<Expression>();
var getMemberExp = new Func<Expression, MemberExpression>(toUnwrap =>
{
if (toUnwrap is UnaryExpression)
{
return ((UnaryExpression)toUnwrap).Operand as MemberExpression;
}
return toUnwrap as MemberExpression;
}); // lambda from PropertyChain implementation
var memberExp = getMemberExp(expr.Body);
var firstSkipped = false;
// check only parents of property to validate
while (memberExp != null)
{
if (firstSkipped)
{
reversedExpressions.Push(memberExp); // don't check target property for null
}
firstSkipped = true;
memberExp = getMemberExp(memberExp.Expression);
}
// build expression that check parent properties for null
var currentExpr = reversedExpressions.Pop();
var whenExpr = Expression.NotEqual(currentExpr, Expression.Constant(null));
while (reversedExpressions.Count > 0)
{
whenExpr = Expression.AndAlso(whenExpr, Expression.NotEqual(currentExpr, Expression.Constant(null)));
currentExpr = reversedExpressions.Pop();
}
var parameter = expr.Parameters.First();
var lambda = Expression.Lambda<Func<TModel, bool>>(whenExpr, parameter); // use parameter of source expression
var compiled = lambda.Compile();
return builder
.WithName(firstMember)
.When(model => compiled.Invoke(model));
}
}
和用法
RuleFor(vm => vm.Nationality.SelectedItem.Value)
.NotEmpty()
.Length(0, 255)
.ApplyChainValidation(vm => vm.Nationality.SelectedItem.Value);
无法避免冗余表达式重复,因为使用内部扩展方法的When()
方法仅适用于先前定义的规则。
注意:解决方案仅适用于具有引用类型的链。