#c# #fluentvalidation
#c# #fluentvalidation
Вопрос:
Я использую FluentValidation.AspNetCore 8.2.2 и имеют объектную модель, которая содержит список дочерних элементов того же типа. Я хотел бы использовать fluent validation для проверки объекта. При попытке установить средство проверки для дочернего объекта я либо сталкиваюсь с исключением переполнения стека, и / или коллекция изменилась (типичная проблема с циклом foreach).
Чтобы протестировать и найти разрешение, я настроил простой проект библиотеки классов .net core с модульным тестированием.
Базовая модель
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();
}
}
Этот тест вызовет исключение переполнения стека.
Я пробовал использовать резервные поля, инициализировать или даже изменять массив.
Я могу успешно отменить исключение потока стекирования с помощью пользовательского средства проверки.
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 ****FluentValidationsrcFluentValidationAbstractValidator.cs:line 115
at FluentValidation.DefaultValidatorExtensions.Validate[T](IValidator`1 validator, T instance, IValidatorSelector selector, String ruleSet) in ******FluentValidationsrcFluentValidationDefaultValidatorExtensions.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 ******FluentValidationsrcFluentValidationTestHelperValidatorTestExtensions.cs:line 101
at FluentValidation.TestHelper.ValidationTestExtension.ShouldHaveValidationErrorFor[T,TValue](IValidator`1 validator, Expression`1 expression, T objectToTest, String ruleSet) in *******FluentValidationsrcFluentValidationTestHelperValidatorTestExtensions.cs:line 40
at Tests.Tests.Test_Name_Cannot_Null_Nested() in FluentValidationChildernFluentValidationChildern.TestsUnitTest1.cs:line 55
Я не могу найти приемлемое решение.
Ответ №1:
Хотя я не смог заставить Fluent работать с использованием метода setValidator, у меня есть обходной путь, который работает и может быть улучшен.
в дочернем списке, который я установил, используйте метод ‘Must’, а затем реализуйте ручную функцию для перебора дочерних элементов и создания объекта validator вручную и проверки результата.
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;
}
}
Ответ №2:
У меня было аналогичное требование, когда мне приходилось проверять объект, у которого был список объектов того же типа, что и дочерние. Что я сделал, так это переопределил метод Validate моего валидатора и добавил логику что-то вроде
public override ValidationResult Validate(ValidationContext<BaseModelItem> context)
{
var result = base.Validate(context);
var obj = context.InstanceToValidate;
if (obj.ChildItems != null amp;amp; 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 resu<
}
Я бы хотел как-то использовать setValidator, но не смог найти способ сделать это. Но в любом случае, это переопределение возвращает объект ValidationResult, который я ожидаю
Ответ №3:
Казалось бы, решение состоит в том, чтобы использовать один и тот же экземпляр средства проверки, подобный этому:
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));
}
}
Я могу подтвердить, что это работает для меня. Заслуга в этой проблеме