【问题标题】:How can I access the collection item being validated when using RuleForEach?使用 RuleForEach 时如何访问正在验证的集合项?
【发布时间】:2014-11-30 12:08:48
【问题描述】:

我正在使用 FluentValidation 来验证一个对象,因为这个对象有一个集合成员,所以我正在尝试使用 RuleForEach。例如,假设我们有 CustomerOrders,并且我们希望确保没有客户订单的总价值超过该客户允许的最大值:

this.RuleForEach(customer => customer.Orders)
    .Must((customer, orders) => orders.Max(order => order.TotalValue) <= customer.MaxOrderValue)

到目前为止,一切都很好。但是,我还需要记录有关错误上下文的其他信息(例如,在数据文件中发现错误的位置)。我发现使用 FluentValidation 很难实现这一点,到目前为止,我最好的解决方案是使用 WithState 方法。例如,如果我发现客户的地址详细信息不正确,我可能会这样做:

this.RuleFor(customer => customer.Address)
    .Must(...)
    .WithState(customer => GetErrorContext(customer.Address))

(其中GetErrorContext是我提取相关细节的一种方法。

现在我遇到的问题是,当使用 RuleForEach 时,方法签名假定我将提供一个引用 Customer 的表达式,而不是导致验证失败。而且我似乎无法判断哪个订单有问题。因此,我无法存储适当的上下文信息。

换句话说,我只能这样做:

this.RuleForEach(customer => customer.Orders)
    .Must((customer, orders) => orders.Max(order => order.TotalValue) <= customer.MaxOrderValue)
    .WithState(customer => ...)

...当我真的想这样做时:

this.RuleForEach(customer => customer.Orders)
    .Must((customer, orders) => orders.Max(order => order.TotalValue) <= customer.MaxOrderValue)
    .WithState(order => ...)

真的没有办法访问失败的集合项的详细信息(甚至是索引)吗?

我想另一种看待它的方式是我希望 WithState 有一个等效的 WithStateForEach...

【问题讨论】:

    标签: c# validation fluentvalidation


    【解决方案1】:

    目前 FluentValidation 中没有允许以您想要的方式设置验证状态的功能。 RuleForEach 旨在防止为简单的集合项创建琐碎的验证器,它的实现并未涵盖所有可能的用例。

    您可以为Order 创建单独的验证器类并使用SetCollectionValidator 方法应用它。要在Must 方法中访问Customer.MaxOrderValue — 将属性添加到Order,它向后引用Customer

    public class CustomerValidator
    {
        public CustomerValidator()
        {
            RuleFor(customer => customer.Orders).SetCollectionValidator(new OrderValidator());
        }
    }
    
    public class OrderValidator
    {
        public OrderValidator()
        {
             RuleFor(order => order.TotalValue)
                 .Must((order, total) => total <= order.Customer.MaxOrderValue)
                 .WithState(order => GetErrorInfo(order)); // pass order info into state
        }
    }
    

    如果您仍想使用RuleForEach 方法,您可以使用错误消息而不是自定义状态,因为它可以在重载之一中访问父项实体对象和子项实体对象:

    public class CustomerValidator
    {
        public CustomerValidator()
        {
            RuleForEach(customer => customer.Orders)
                .Must((customer, order) => order.TotalValue) <= customer.MaxOrderValue)
                .WithMessage("order with Id = {0} have error. It's total value exceeds {1}, that is maximum for {2}",
                    (customer, order) => order.Id,
                    (customer, order) => customer.MaxOrderValue,
                    (customer, order) => customer.Name);
        }
    }
    

    如果您需要收集失败订单的所有索引(或标识符)——您可以使用Custom 规则来完成,如下所示:

    public CustomerValidator()
    {
        Custom((customer, validationContext) =>
        {
            var isValid = true;
            var failedOrders = new List<int>();
    
            for (var i = 0; i < customer.Orders.Count; i++)
            {
                if (customer.Orders[i].TotalValue > customer.MaxOrderValue)
                {
                    isValid = false;
                    failedOrders.Add(i);
                }
            }
    
            if (!isValid){
                var errorMessage = string.Format("Error: {0} orders TotalValue exceed maximum TotalValue allowed", string.Join(",", failedOrders));
                return new ValidationFailure("", errorMessage) // return indexes of orders through error message
                {
                    CustomState = GetOrdersErrorInfo(failedOrders) // set state object for parent model here
                };
            }
    
            return null;
        });
    }
    

    附言

    不要忘记您的目标是实现验证,而不是到处使用 FluentValidation。有时我们将验证逻辑实现为单独的方法,该方法与 ViewModel 一起使用,并在 ASP.NET MVC 中填充 ModelState

    如果您找不到符合您要求的解决方案,那么手动实现会比 crutchful 使用库实现更好。

    【讨论】:

    • 谢谢。两个好主意,虽然改变我的模型只是为了适应 F​​luentValidation 的限制对我来说听起来并不好。第二个想法对我来说并不理想,因为我想要一个处于我状态的对象(类),而不仅仅是一个字符串,但我相信它对某些人来说已经足够了。再次感谢。
    • 这有什么改变吗,我有完全相同的问题,规则很简单,所以我不想为它创建一个全新的验证器,我想要一个类而不仅仅是一条消息。让 WithMessage 同时接受父母和孩子但不接受 WithState 似乎有点糟糕。
    • @Kezza 添加了另一个解决方案,这可能会有所帮助。
    猜你喜欢
    • 1970-01-01
    • 2021-11-27
    • 1970-01-01
    • 1970-01-01
    • 2020-08-31
    • 1970-01-01
    • 2012-05-13
    • 2021-09-30
    • 2021-06-22
    相关资源
    最近更新 更多