You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

FluentValidation:验证集合时如何引用父对象属性

问题场景

验证Order下的Item集合时,希望错误信息中包含对应Order的OrderId,方便客户端快速定位出错的订单。尝试通过RootContextData传递父对象ID,但发现仅能在Item验证器的Custom规则中访问该数据,且Custom规则内无法嵌套使用内置验证器(会抛出Collection was modified; enumeration operation may not execute异常)。现有代码示例如下:

public class Order
{
    public int OrderId { get; set; }
    public List<Item> Items { get; set; }
}

public class Item
{
    public string ItemDescription { get; set; }
}

public class OrderValidator : AbstractValidator<Order>
{
    protected override bool PreValidate(ValidationContext<Order> context, ValidationResult result)
    {
        if (context.InstanceToValidate == null)
        {
            result.Errors.Add(new ValidationFailure("", "Please ensure a model was supplied."));
            return false;
        }

        context.RootContextData["ParentId"] = context.InstanceToValidate.OrderId;

        return true;
    }

    public OrderValidator()
    {
        RuleForEach(x => x.Items).SetValidator(new ItemValidator());
    }
}

public class ItemValidator : AbstractValidator<Item>
{
    public ItemValidator()
    {
        RuleFor(x => x.ItemDescription).Custom((item, context) =>
        {
            var parentId = int.Parse(context.RootContextData["ParentId"].ToString());

            // 此处无法使用内置验证器,会抛出异常
            // RuleFor(item => item.ItemDescription).NotNull();
        });
    }
}
解决方案

以下两种方法均基于内置验证器实现,避免了Custom规则嵌套的问题:

方法一:为每个验证规则自定义带订单ID的错误消息

在ItemValidator中正常定义内置验证规则,通过WithMessage的委托形式访问RootContextData中的OrderId,动态生成包含订单ID的错误信息:

public class ItemValidator : AbstractValidator<Item>
{
    public ItemValidator()
    {
        RuleFor(x => x.ItemDescription)
            .NotNull()
            .WithMessage((_, context) => 
            {
                if (context.RootContextData.TryGetValue("ParentId", out var parentIdObj) && int.TryParse(parentIdObj.ToString(), out var orderId))
                {
                    return $"订单ID {orderId} 的商品描述不能为空";
                }
                return "商品描述不能为空";
            });

        RuleFor(x => x.ItemDescription)
            .NotEmpty()
            .WithMessage((_, context) => 
            {
                if (context.RootContextData.TryGetValue("ParentId", out var parentIdObj) && int.TryParse(parentIdObj.ToString(), out var orderId))
                {
                    return $"订单ID {orderId} 的商品描述不能为空白";
                }
                return "商品描述不能为空白";
            });
    }
}

方法二:统一为所有错误追加订单ID

定义完所有内置验证规则后,通过OnAnyFailure钩子统一处理错误消息,将订单ID追加到每条错误信息前:

public class ItemValidator : AbstractValidator<Item>
{
    public ItemValidator()
    {
        // 正常定义内置验证规则
        RuleFor(x => x.ItemDescription).NotNull().NotEmpty();

        // 统一注入订单ID到错误消息
        OnAnyFailure((_, context) =>
        {
            if (context.RootContextData.TryGetValue("ParentId", out var parentIdObj) && int.TryParse(parentIdObj.ToString(), out var orderId))
            {
                foreach (var failure in context.Failures)
                {
                    failure.ErrorMessage = $"订单ID {orderId}: {failure.ErrorMessage}";
                }
            }
        });
    }
}

说明

  • 两种方法均保留了OrderValidator中通过RootContextData传递OrderId的逻辑。
  • 方法一适合需要为不同规则定制差异化错误消息的场景;方法二更简洁,适合统一格式的错误输出。
  • 添加空值判断是为了兼容直接验证Item对象的场景(此时RootContextData中无ParentId)。

内容的提问来源于stack exchange,提问作者twistedbits

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.22 08:42:06