How can I access the collection item being validated when using RuleForEach?

后端 未结 1 1795
我寻月下人不归
我寻月下人不归 2020-12-24 12:49

I\'m using FluentValidation to validate an object, and because this object has a collection member I\'m trying to use RuleForEach. For example, suppose we have

相关标签:
1条回答
  • 2020-12-24 13:21

    Currently there are no functionality in FluentValidation, that allows to set validation state the way you want. RuleForEach was designed to prevent creation of trivial validators for simple collection items, and it's implementation didn't cover all possible use cases.

    You can create separate validator class for Order and apply it using SetCollectionValidator method. To access Customer.MaxOrderValue in Must method — add property to Order, that references backward to 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
        }
    }
    

    If you still want to use RuleForEach method, you can use error message instead of custom state, because it have access to both parent and child item entity objects in one of overloads:

    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);
        }
    }
    

    If you need to collect all indexes (or identifiers) of failed orders — you can do it with Custom rule, like here:

    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;
        });
    }
    

    P.S.

    Do not forget that your goal is to implement validation, not to use FluentValidation everywhere. Sometimes we implement validation logic as a separate method, that works with ViewModel and fill ModelState in ASP.NET MVC.

    If you can't find solution, that match your requirements, then manual implementation would be better than crutchful implementation with library.

    0 讨论(0)
提交回复
热议问题