【问题标题】:FluentValidation Recursive list causes stack overflowFluentValidation 递归列表导致堆栈溢出
【发布时间】:2019-04-23 04:46:44
【问题描述】:

我正在使用 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

我找不到可行的解决方案。

【问题讨论】:

    标签: c# fluentvalidation


    【解决方案1】:

    虽然我无法使用 SetValidator 方法让 Fluent 工作,但我确实有一个可行的解决方法,并且可以改进。

    在我设置的子列表上使用'Must'方法,然后实现手动函数循环子项并手动构造验证器对象并检查结果。

     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 && 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 对象

      【讨论】:

        【解决方案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));
            }
        
        }
        

        我可以确认这对我有用。感谢issue

        【讨论】:

          猜你喜欢
          • 2011-07-09
          • 2011-02-26
          • 1970-01-01
          • 2018-10-29
          • 2013-04-05
          • 2020-12-17
          相关资源
          最近更新 更多