简体   繁体   中英

FluentValidation: Comparing a value with an aggregate of values?

I find FluentValidation straightforward when making simple rules for properties on a single class, but as soon as I need to compare values from collections (eg a List<T> property), it gets super-hairy. Assume the follwing two minimal classes:

public class PurchaseOrder
{
    public List<LineItem> LineItems { get; set; }
    public decimal Total { get; set; }

    public PurchaseOrder()
    {
        LineItems = new List<LineItem>();
    }
}

public class LineItem
{
    public decimal Price { get; set; }
}

And this class:

class Program
{
    static void Main(string[] args)
    {
        PurchaseOrder order = new PurchaseOrder();
        order.LineItems.Add(new LineItem() { Price = 12m });
        order.LineItems.Add(new LineItem() { Price = 14m });
        order.Total = 26m;

        PurchaseOrderValidator validator = new PurchaseOrderValidator();
        ValidationResult result = validator.Validate(order);
    }
}

How would PurchaseOrderValidator look like to make sure that the sum of all LineItem.Price equals the PurchaseOrder.Total ? Here's a stub (although I'm not sure Must() is the way to go):

public class PurchaseOrderValidator : AbstractValidator<PurchaseOrder>
{
    public PurchaseOrderValidator()
    {
        RuleFor(x => x.Total).Must(MatchSumOfLineItems);
    }

    private bool MatchSumOfLineItems(decimal arg)
    {
        return true;
    }
}

Just create validation method that takes PurchaseOrder as input and do whatever you want with it

public class PurchaseOrderValidator : AbstractValidator<PurchaseOrder>
{
    public PurchaseOrderValidator()
    {
        RuleFor(x => x).Must(MatchSumOfLineItems);
    }

    private bool MatchSumOfLineItems(PurchaseOrder arg)
    {
        return arg.Total == arg.LineItems.Sum(i => i.Price);
    }
}

or if you want to add validation specifically for Total property you can use another Must overload accepts Func<decimal, PurchaseOrder, bool>

public class PurchaseOrderValidator : AbstractValidator<PurchaseOrder>
{
    public PurchaseOrderValidator()
    {
        RuleFor(x => x.Total).Must(MatchSumOfLineItems);
    }

    private bool MatchSumOfLineItems(PurchaseOrder order, decimal sum)
    {
        return sum == order.LineItems.Sum(i => i.Price);
    }
}

Why not use .Equal()

public class PurchaseOrderValidator : AbstractValidator<PurchaseOrder>
{
    public PurchaseOrderValidator()
    {
        RuleFor(x => x.Total).Equal(x => x.LineItems.Sum(item => item.Price));
    }
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM