【问题标题】:How to reuse data in FluentValidation如何在 FluentValidation 中重用数据
【发布时间】:2016-05-27 13:27:35
【问题描述】:

例如我有两个验证规则的验证器:

// Rule 1
RuleFor(o => o.Email).Must((email) => this.GetDataDataFromDB(email) != 0)
    .WithMessage("User with provided Email was not found in database!");

// Rule 2
RuleFor(o => o.Email).Must((email) => this.GetDataDataFromDB(email) >= 1)
    .WithMessage("There are multiple users with provided Email in database!");

如您所见,有两次使用相同方法调用数据库。如何调用一次并将数据重用于其他规则?

显示错误消息时的另一个问题:

RuleFor(o => o.Email).Must((email) => this.GetDataDataFromDB(email) >= 1)
    .WithMessage("There are multiple users with following Email '{0}' in database!",
    (model, email) => { return email; });

有没有更好的方法来显示错误消息,而不是一直编写那些 lambda 表达式来检索属性?就像将模型保存在某个地方,然后再使用它。

简单且易于实施的解决方案会很好!

【问题讨论】:

  • RuleFor(o => o.Email).Must((email) => this.GetDataDataFromDB(email)

标签: c# fluentvalidation


【解决方案1】:

对于#1,恐怕没有办法做到这一点。验证器被设计为无状态的,因此它们可以跨线程重用(事实上,强烈建议您将验证器实例创建为单例,因为它们的实例化非常昂贵。MVC 集成默认情况下会这样做)。不要乱用静态字段,因为你会遇到线程问题。

(编辑:在这个特殊的简单情况下,您可以将规则组合成一个对 Must 的调用,但一般情况下您不能在规则之间共享状态)

对于 #2,这取决于您使用的属性验证器。大多数属性验证器实际上允许您使用 {PropertyValue} 占位符,并且会自动插入该值。但是,在这种情况下,您使用的是不支持占位符的“必须”验证器 (PredicateValidator)。

我在这里列出了哪些验证器支持自定义占位符:https://github.com/JeremySkinner/FluentValidation/wiki/c.-Built-In-Validators

【讨论】:

  • 是的,我同意@Jeremy Skinner,这可能会导致线程出现一些问题。虽然我增加了一项改进。派生自AbstractValidator 的基类以及其中的getter(非静态),如果它的私有字段为null,它将从databse 获取数据。在GetDataDataFromDB 中,只需使用该属性。如果在this 验证器上下文中多次调用GetDataDataFromDB,这将至少只获取一次数据库数据。
【解决方案2】:

第 1 部分

您想将数据库调用从 2 减少到 1,因此您需要使用字段来保存数据库调用结果,因为验证器规则 code 实际上是在 "runtime" 中工作的.

验证器类:

public class MyValidator : Validator<UserAccount>
{
    private int? _countOfExistingMails;
    private string _currentEmail;
    private object locker = new object();

    public MyValidator()
    {
        CallEmailValidations();
        // other rules...
    }
}

这是邮件验证调用的单独方法。至于Must 以表达式为参数,您可以将方法名称与它的参数一起传递:

public void CallEmailValidations()
{
    RuleFor(o => o.Email).Must(x => EmailValidation(x, 0))
        .WithMessage("User with provided Email was not found in database!");

    RuleFor(o => o.Email).Must(x => EmailValidation(x, 1))
        .WithMessage("There are multiple users with provided Email in database!");
}

以及验证方法的主体:

public bool EmailValidation(string email, int requiredCount)
{
    var isValid = false;

    lock(locker)
    {
        if (email != _currentEmail || _currentEmail == null)
        {
            _currentEmail = email;
            _countOfExistingMails = (int)GetDataDataFromDB(email);
        }

        if (requiredCount == 0)
        {
            isValid = _countOfExistingMails != 0; // Rule 1
        }
        else if (requiredCount == 1)
        {
            isValid = _countOfExistingMails <= 1; // Rule 2
        }
    }
    // Rule N...

    return isValid;
}

更新: 此代码有效,但更好的方法是在数据访问层方法中实现缓存。

第 2 部分

这里是改写的规则:

RuleFor(o => o.Email).Must((email) => GetDataDataFromDB(email) >= 1)
    .WithMessage("There are multiple users with following Email '{0}' in database!", m => m.Email)

来自"C# in depth"

当 lambda 表达式只需要一个参数时,并且 参数可以隐式输入,C# 3 允许您省略 括号,所以它现在有这种形式

问题:

  1. 不要将 this 显式传递给 lambda 表达式。据我所知,它可能会导致性能问题。没有理由创建额外的封闭。

  2. 我想你在GetDataDataFromDB 方法中以某种形式使用DataContext。所以你必须控制上下文的生命周期,因为验证器对象实例化为单例。

【讨论】:

    【解决方案3】:

    在寻找更好的方法时遇到了这个问题;)

    另一种方法是覆盖ValidateAsyncValidate 方法并将结果存储在可以通过以下规则访问的本地字段中:

    public class MyValidator : AbstractValidator<MyCommand>
    {
        User _user = User.Empty;
    
        public MyValidator()
        {
            RuleFor(o => o.Email)
                .Must((_) => !_user.IsEmpty)
                .WithMessage("User with provided Email was not found in database!");
    
            // Rule 2
            //other rules which can check _user
        }
    
        public override async Task<ValidationResult> ValidateAsync(ValidationContext<MyCommand> context, CancellationToken cancellation = default)
        {
            var cmd = context.InstanceToValidate;
            // you could wrap in a try block if this throws, here I'm assuming empty user
            _user = await _repository.GetUser(cmd.Email);
            return await base.ValidateAsync(context, cancellation);
        }
    
        public override ValidationResult Validate(ValidationContext<SubmitDecisionCommand> context) => ValidateAsync(context).Result;
    }
    

    【讨论】:

      猜你喜欢
      • 2016-06-22
      • 1970-01-01
      • 1970-01-01
      • 2014-12-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多