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
相关产品推荐
相关产品推荐

