我正在使用FluentValidation.AspNetcore 8.2.2,并具有一个对象模型,其中包含相同类型的子项列表。 我想使用流利的验证来验证对象。 尝试为子对象设置验证器时,我遇到了堆栈溢出异常和/或集合已更改(典型的foreach循环问题)。
为了测试和找到分辨率,我设置了一个简单的.net核心类库项目,并进行了单元测试。
基本模型
using FluentValidation;
public class BaseModelItem
{
public int ItemId { get; set; }
public string Name { get; set; }
private List<BaseModelItem> ChildItems { get; set; }
}
public class BaseModelItemValidator : AbstractValidator<BaseModelItem>
{
public BaseModelItemValidator()
{
RuleFor(i => i.ItemId).GreaterThanOrEqualTo(0).WithMessage("Item id may not be negative.");
RuleFor(i => i.Name).NotNull().NotEmpty().WithMessage("Item name cannot be empty.");
RuleFor(i => i.ChildItems).ForEach(i => i.SetValidator(new BaseModelItemValidator()));
}
}
单元测试
public class Tests
{
[Test]
public void Test_Name_Cannot_Null()
{
var item = new BaseModelItem
{
ItemId = 2,
Name = null,
ChildItems = new List<BaseModelItem>()
};
var validator = new BaseModelItemValidator();
validator.ShouldHaveValidationErrorFor(t => t.Name, item);
Assert.Pass();
}
}
此测试将导致堆栈溢出异常。 我尝试使用后备字段,初始化甚至更改为数组。 我可以使用自定义验证器成功否定stackover流异常。
public class BaseModelItemValidator : AbstractValidator<BaseModelItem>
{
public BaseModelItemValidator()
{
RuleFor(i => i.ItemId).GreaterThanOrEqualTo(0).WithMessage("Item id may not be negative.");
RuleFor(i => i.Name).NotNull().NotEmpty().WithMessage("Item name cannot be empty.");
RuleFor(i => i.ChildItems).Must(BeValidChildItemList);
}
private bool BeValidChildItemList(List<BaseModelItem> list)
{
if (list.Count > 0)
{
RuleFor(i => i.ChildItems).ForEach(i => i.SetValidator(new BaseModelItemValidator()));
}
return true;
}
}
允许它验证没有子项的对象。 但是,如果使用填充的子对象运行测试,则会出现错误“集合已修改;枚举操作可能无法执行”。 堆栈跟踪
StackTrace:
at System.ThrowHelper.ThrowInvalidOperationException_InvalidOperation_EnumFailedVersion()
at System.Collections.Generic.List`1.Enumerator.MoveNextRare()
at System.Linq.Enumerable.SelectManySingleSelectorIterator`2.MoveNext()
at System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()
at FluentValidation.AbstractValidator`1.Validate(ValidationContext`1 context) in ****\FluentValidation\src\FluentValidation\AbstractValidator.cs:line 115
at FluentValidation.DefaultValidatorExtensions.Validate[T](IValidator`1 validator, T instance, IValidatorSelector selector, String ruleSet) in ******\FluentValidation\src\FluentValidation\DefaultValidatorExtensions.cs:line 876
at FluentValidation.TestHelper.ValidationTestExtension.TestValidate[T,TValue](IValidator`1 validator, Expression`1 expression, T instanceToValidate, TValue value, String ruleSet, Boolean setProperty) in ******\FluentValidation\src\FluentValidation\TestHelper\ValidatorTestExtensions.cs:line 101
at FluentValidation.TestHelper.ValidationTestExtension.ShouldHaveValidationErrorFor[T,TValue](IValidator`1 validator, Expression`1 expression, T objectToTest, String ruleSet) in *******\FluentValidation\src\FluentValidation\TestHelper\ValidatorTestExtensions.cs:line 40
at Tests.Tests.Test_Name_Cannot_Null_Nested() in \FluentValidationChildern\FluentValidationChildern.Tests\UnitTest1.cs:line 55
我找不到可行的解决方案。
答案 0 :(得分:0)
尽管我无法使用SetValidator方法使Fluent正常工作,但我确实有一种可行的解决方法,并且可以对其进行改进。
我在子列表上设置了“必须”方法,然后实现了手动功能来循环子项并手动构造验证器对象并检查结果。
public class BaseModelItemValidator : AbstractValidator<BaseModelItem>
{
public BaseModelItemValidator()
{
RuleFor(i => i.ItemId).GreaterThanOrEqualTo(0).WithMessage("Item id may not be negative.");
RuleFor(i => i.Name).NotNull().NotEmpty().WithMessage("Item name cannot be empty.");
RuleFor(i => i.ChildItems).Must(BeValidChildItemList);
}
private bool BeValidChildItemList(List<BaseModelItem> list)
{
if (list == null || list.Count == 0) return true;
foreach (var child in list)
{
var validator = new BaseModelItemValidator();
var validatorResults = validator.Validate(child);
if (!validatorResults.IsValid)
{
return false;
}
}
return true;
}
}
答案 1 :(得分:0)
我有一个类似的要求,即我必须验证一个对象,该对象具有与孩子相同类型的对象列表。我所做的是我覆盖了验证器的Validate方法,并添加了类似
的逻辑public override ValidationResult Validate(ValidationContext<BaseModelItem> context)
{
var result = base.Validate(context);
var obj = context.InstanceToValidate;
if (obj.ChildItems != null && obj.ChildItems.Count > 0)
{
foreach (var item in obj.ChildItems)
{
var childResult = base.Validate(item);
foreach (var error in childResult.Errors)
{
result.Errors.Add(error);
}
}
}
return result;
}
我很想以某种方式使用SetValidator,但找不到解决方法。但是无论如何,这种覆盖会返回我期望的ValidationResult对象
答案 2 :(得分:0)
似乎解决方案是使用相同的验证器实例,如下所示:
public class BaseModelItemValidator : AbstractValidator<BaseModelItem>
{
public BaseModelItemValidator()
{
RuleFor(i => i.ItemId).GreaterThanOrEqualTo(0).WithMessage("Item id may not be negative.");
RuleFor(i => i.Name).NotNull().NotEmpty().WithMessage("Item name cannot be empty.");
RuleFor(i => i.ChildItems).ForEach(i => i.SetValidator(this));
}
}
我可以确认这对我有用。归功于此issue