自定义RequiredParentAttribute:获取父对象名称优化验证提示
我开发了一个继承自RequiredAttribute的自定义验证特性RequiredParentAttribute,代码如下:
// 自定义特性 public class RequiredParentAttribute: RequiredAttribute { public RequiredParentAttribute() { ErrorMessageResourceType = typeof(ResourceFW); ErrorMessageResourceName = nameof(ResourceFW.RequiredParentError); } public override string FormatErrorMessage(string name) { return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, getParentName()); } private string getParentName() { return "parentName;"; } }
现有Order类包含两个Address类型的属性:
public class Order { public Address BillingAddress {get;set;} public Address ShippingAddress {get;set;} } public class Address { [RequiredParentAttribute] public string City {get;set;} [RequiredParentAttribute] public string ZipCode {get;set;} }
当前验证时,错误提示无法区分字段所属的父对象:
City is required
ZipCode is required
City is required
ZipCode is required
期望的提示格式为:
City in Billing Address is required
ZipCode in Billing Address is required
City in Shipping Address is required
ZipCode in Shipping Address is required
由于Address类会被多个实体复用,需要通用的解决方案。尝试自定义特性实现时无法获取父对象信息,且了解到.NET无法直接知晓特性调用者,特此求助。
要实现通用的父对象名称识别,可利用.NET验证系统的ValidationContext上下文信息,通过父级验证上下文提取属性名称并转换成友好格式,具体实现如下:
1. 修改自定义特性,获取父级属性信息
针对.NET 6+版本,可直接使用ValidationContext.ParentContext属性提取父级属性名称,转换为友好格式后保存,用于后续错误消息格式化:
using System.Globalization; using System.Text.RegularExpressions; using System.ComponentModel.DataAnnotations; public class RequiredParentAttribute : RequiredAttribute { private string _parentFriendlyName; public RequiredParentAttribute() { ErrorMessageResourceType = typeof(ResourceFW); // 确保资源文件错误消息格式为: "{0} in {1} is required" ErrorMessageResourceName = nameof(ResourceFW.RequiredParentError); } protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (validationContext.ParentContext != null) { var parentPropertyName = validationContext.ParentContext.DisplayName; // 将驼峰命名转换为友好格式(如BillingAddress → Billing Address) _parentFriendlyName = Regex.Replace(parentPropertyName, "(?<!^)([A-Z])", " $1"); } else { // 无父级时的兜底文本 _parentFriendlyName = "unknown parent"; } return base.IsValid(value, validationContext); } public override string FormatErrorMessage(string name) { return string.Format(CultureInfo.CurrentCulture, ErrorMessageString, name, _parentFriendlyName); } }
2. 配置资源文件错误消息格式
在ResourceFW资源文件中,RequiredParentError的内容需设置为:
{0} in {1} is required
3. .NET 5及以下版本兼容方案
若使用.NET 5或更早版本,ValidationContext无ParentContext属性,可通过ValidationContext.Items获取完整属性路径,拆分后提取父级名称:
protected override ValidationResult IsValid(object value, ValidationContext validationContext) { if (validationContext.Items.TryGetValue("Validation.PropertyPath", out var pathObj) && pathObj is string fullPropertyPath) { var pathParts = fullPropertyPath.Split('.'); if (pathParts.Length >= 2) { // 提取父级属性名并转换为友好格式 var parentPropertyName = pathParts[pathParts.Length - 2]; _parentFriendlyName = Regex.Replace(parentPropertyName, "(?<!^)([A-Z])", " $1"); } } else { _parentFriendlyName = "unknown parent"; } return base.IsValid(value, validationContext); }
效果说明
使用上述修改后的特性,验证Order对象时,错误提示将自动区分父对象,完全符合预期格式,且无需修改Address或其他使用Address的实体类,具备通用性。
内容的提问来源于stack exchange,提问作者Hicham Bouchikhi

