【问题标题】:Circular dependency in FluentValidationFluentValidation 中的循环依赖
【发布时间】:2021-12-07 12:04:48
【问题描述】:

我正在使用FluentValidation 来验证一些模型。 这些模型有一个循环引用,所以我试图做这样的事情:

2 个模型:

public class ObjectA
{
    public string Name { get; set; }

    public ObjectB AnotherObject { get; set; }
}

public class ObjectB
{
    public string Name { get; set; }

    public ObjectA AnotherObject { get; set; }
}

验证者:

public class ObjectAValidator : AbstractValidator<ObjectA>
{
    public ObjectAValidator(IValidator<ObjectB> validator)
    {
        this.RuleFor(c => c.Name).NotNull();
        this.RuleFor(c => c.AnotherObject).SetValidator(validator);
    }
}

public class ObjectBValidator : AbstractValidator<ObjectB>
{
    public ObjectBValidator(IValidator<ObjectA> validator)
    {
        this.RuleFor(c => c.Name).NotNull();
        this.RuleFor(c => c.AnotherObject).SetValidator(validator);
    }
}
class Program
{
    static void Main(string[] args)
    {
        var services = new ServiceCollection();
        services.AddValidatorsFromAssemblyContaining<ObjectA>();
        var serviceProvider = services.BuildServiceProvider();

        using var scope = serviceProvider.CreateScope();
        var objetoAValidator = scope.ServiceProvider.GetRequiredService<IValidator<ObjectA>>();

        ObjectA test = new()
        {
            Name = "1Level",
            AnotherObject = new()
            {
                Name = "2Level",
                AnotherObject = new()
                {
                    AnotherObject = new()
                }
            }
        };

        var validationResult = objetoAValidator.Validate(test);
    }
}

上面的代码抛出如下异常:

System.InvalidOperationException: '检测到'FluentValidation.IValidator' 类型的服务的循环依赖。 FluentValidation.IValidator(ConsoleApp9.ObjectAValidator) -> FluentValidation.IValidator(ConsoleApp9.ObjectBValidator) -> FluentValidation.IValidator'

在这种情况下如何解决循环依赖?

【问题讨论】:

  • 最简单的解决方案 - 单独验证。抓取所有 obj A 并验证集合,抓取 B 并验证集合。不要为持有 A 或 B 的内部属性分配验证器。您还可以找到一些帮助 here

标签: c# fluentvalidation


【解决方案1】:

将验证器更改为接收IServiceProvider 而不是任何其他验证器,可能会解决问题。

要打破循环依赖,您必须检查正在验证的模型是否不为空,然后使用自定义验证器对其进行验证。

示例:

public class ObjectBValidator : AbstractValidator<ObjectB>
{
    public ObjectBValidator(IServiceProvider sp)
    {

        this.RuleFor(c => c.Name).NotNull();
        this.RuleFor(c => c.AnotherObject).Custom((value, context) =>
        {
            var validator = sp.GetRequiredService<IValidator<ObjectA>>();
            var r = validator.Validate(value);
            if (!r.IsValid)
            {
                r.Errors.ForEach(context.AddFailure);
            }
        }).When(c => c.AnotherObject is not null);
    }
}

也可以通过创建 FluentValidation 扩展来简化。


public static IRuleBuilderOptionsConditions<T, TProperty> ValidateInnerType<T, TProperty>(this IRuleBuilder<T, TProperty> ruleBuilder, IServiceProvider serviceProvider)
{
    return ruleBuilder.Custom((value, context) =>
    {
        var validator = serviceProvider.GetRequiredService<IValidator<TProperty>>();
        var r = validator.Validate(value);
        if (!r.IsValid)
        {
            r.Errors.ForEach(context.AddFailure);
        }
    });
}

public class ObjectBValidator : AbstractValidator<ObjectB>
{
    public ObjectBValidator(IServiceProvider sp)
    {
        this.RuleFor(c => c.Name).NotNull();

        this.RuleFor(c => c.AnotherObject)
            .ValidateInnerType(sp)
            .When(c => c.AnotherObject is not null);
    }
}

【讨论】:

    【解决方案2】:

    当您将IServiceProvider 注入构造函数时,您会使用Service Locator Patter,这不是一个好的解决方案,因为它是一种反模式。它可能导致运行时错误和意外的应用行为,而且它隐藏了一个类依赖项,这使您的代码不清楚并且更难以维护。

    解决循环依赖的最好方法是构建你的类依赖非循环,并分别对两者进行验证。所以我强烈建议您检查您的应用程序架构。但是,如果您确定要这样保留它,那么使用 DataAnnotations attributes 可能会更好,它可以在 FluentValidation 之后执行。

    此链接可能会有所帮助:

    【讨论】:

    • 我认为可以通过实现validator factories来解决,所以这些工厂可以注入到验证器中。
    【解决方案3】:

    这里的问题是,无论何时创建对象 A 或对象 B,都必须创建另一个对象。

    这实际上创建了一个无限循环。

        ObjectA test = new()
        {
            Name = "1Level",
            AnotherObject = new ObjectB ()
            {
                Name = "2Level",
                AnotherObject = new ObjectA ()
                {
                    Name = "3Level",
                    AnotherObject = new ObjectB ()
                    {
                      AND SO ON AND SO ON...
                    }
                }
            }
        };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2010-09-12
      • 2021-10-02
      • 1970-01-01
      • 2017-04-02
      相关资源
      最近更新 更多