自定义类型如何使用.NET框架标准验证数据注解?
当然可以实现让StringLength、RegularExpression、Required这类标准数据注解作用于自定义类型的Value属性,但你遇到的异常其实是有原因的:这些标准验证属性内部会严格检查目标值的类型——比如StringLengthAttribute的IsValid方法会先尝试将值转为string,如果转换失败(自定义类型显然不是string),就直接抛出ArgumentException,重写ToString()根本走不到后续的验证逻辑。
下面提供几种可行的解决方案,你可以根据自己的场景选择:
方法一:自定义验证属性(简单直接,针对性强)
创建继承自标准验证属性的自定义版本,重写IsValid方法来处理你的自定义类型,提取Value属性进行验证。
比如针对StringLengthAttribute:
public class CustomStringLengthAttribute : StringLengthAttribute { public CustomStringLengthAttribute(int maximumLength) : base(maximumLength) { } public override bool IsValid(object value) { // 处理自定义类型,这里假设你的自定义类型叫MyCustomType if (value is MyCustomType customObj) { return base.IsValid(customObj.Value); } // 其他类型按默认逻辑处理 return base.IsValid(value); } }
用同样的方式封装RegularExpressionAttribute和RequiredAttribute:
// 自定义Required验证 public class CustomRequiredAttribute : RequiredAttribute { public override bool IsValid(object value) { if (value is MyCustomType customObj) { return base.IsValid(customObj.Value); } return base.IsValid(value); } } // 自定义正则验证 public class CustomRegularExpressionAttribute : RegularExpressionAttribute { public CustomRegularExpressionAttribute(string pattern) : base(pattern) { } public override bool IsValid(object value) { if (value is MyCustomType customObj) { return base.IsValid(customObj.Value); } return base.IsValid(value); } }
之后在模型中直接使用这些自定义注解即可:
public class MyModel { [CustomRequired] [CustomStringLength(10)] [CustomRegularExpression(@"^\w+$")] public MyCustomType MyProperty { get; set; } }
优点:代码简单,不需要全局配置,只针对需要的自定义类型处理;缺点:每个标准注解都要写对应的自定义版本,扩展性一般。
方法二:自定义模型验证提供者(通用灵活,适合多场景)
如果你的项目中有多个自定义类型需要支持标准注解,或者不想重复写自定义属性,可以通过自定义ModelValidatorProvider来全局拦截验证逻辑,自动提取自定义类型的Value属性进行验证。
1. 创建自定义验证提供者
public class CustomTypeValidatorProvider : DataAnnotationsModelValidatorProvider { protected override IEnumerable<ModelValidator> GetValidators(ModelMetadata metadata, ControllerContext context, IEnumerable<Attribute> attributes) { // 只处理目标类型为MyCustomType的情况 if (metadata.ModelType == typeof(MyCustomType)) { var validators = new List<ModelValidator>(); foreach (var attr in attributes) { // 根据不同的标准注解创建对应的自定义验证器 switch (attr) { case StringLengthAttribute strAttr: validators.Add(new CustomTypeStringLengthValidator(metadata, context, strAttr)); break; case RegularExpressionAttribute regexAttr: validators.Add(new CustomTypeRegexValidator(metadata, context, regexAttr)); break; case RequiredAttribute reqAttr: validators.Add(new CustomTypeRequiredValidator(metadata, context, reqAttr)); break; // 其他需要支持的验证属性可以继续添加 default: // 不处理的属性用默认验证器 validators.AddRange(base.GetValidators(metadata, context, new[] { attr })); break; } } return validators; } // 非自定义类型按默认逻辑处理 return base.GetValidators(metadata, context, attributes); } }
2. 实现对应的验证器
以StringLength的验证器为例:
public class CustomTypeStringLengthValidator : DataAnnotationsModelValidator<StringLengthAttribute> { public CustomTypeStringLengthValidator(ModelMetadata metadata, ControllerContext context, StringLengthAttribute attribute) : base(metadata, context, attribute) { } public override IEnumerable<ModelValidationResult> Validate(object container) { var customObj = Metadata.Model as MyCustomType; if (customObj != null) { // 验证自定义类型的Value属性 if (!Attribute.IsValid(customObj.Value)) { yield return new ModelValidationResult { Message = ErrorMessage }; } } else { // 非自定义类型按默认逻辑验证 foreach (var result in base.Validate(container)) { yield return result; } } } }
Required和RegularExpression的验证器写法类似,只需要替换对应的属性类型和验证逻辑即可。
3. 注册验证提供者
在项目启动时替换默认的验证提供者:
// ASP.NET MVC(Global.asax.cs) protected void Application_Start() { // 移除默认的DataAnnotations验证提供者 var defaultProvider = ModelValidatorProviders.Providers.OfType<DataAnnotationsModelValidatorProvider>().First(); ModelValidatorProviders.Providers.Remove(defaultProvider); // 添加自定义验证提供者 ModelValidatorProviders.Providers.Add(new CustomTypeValidatorProvider()); // 其他启动配置... } // ASP.NET Core(Startup.cs) public void ConfigureServices(IServiceCollection services) { services.AddMvc(options => { var defaultProvider = options.ModelValidatorProviders.OfType<DataAnnotationsModelValidatorProvider>().First(); options.ModelValidatorProviders.Remove(defaultProvider); options.ModelValidatorProviders.Add(new CustomTypeValidatorProvider()); }); }
优点:全局生效,一次配置支持所有自定义类型的标准注解验证;缺点:需要理解模型验证的底层逻辑,代码量稍大。
方法三:使用TypeConverter(适合ASP.NET Core,结合模型绑定)
如果是在ASP.NET Core中,还可以通过实现TypeConverter,让自定义类型可以和string互相转换,这样标准验证属性就能自动识别并验证Value属性。
1. 实现TypeConverter
public class MyCustomTypeConverter : TypeConverter { // 支持从string转换为自定义类型 public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType) { return sourceType == typeof(string) || base.CanConvertFrom(context, sourceType); } public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value) { if (value is string strValue) { return new MyCustomType { Value = strValue }; } return base.ConvertFrom(context, culture, value); } // 支持从自定义类型转换为string public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType) { return destinationType == typeof(string) || base.CanConvertTo(context, destinationType); } public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType) { if (destinationType == typeof(string) && value is MyCustomType customObj) { return customObj.Value; } return base.ConvertTo(context, culture, value, destinationType); } }
2. 给自定义类型添加TypeConverterAttribute
[TypeConverter(typeof(MyCustomTypeConverter))] public class MyCustomType { public string Value { get; set; } }
之后就可以直接在模型中使用标准注解了:
public class MyModel { [Required] [StringLength(10)] [RegularExpression(@"^\w+$")] public MyCustomType MyProperty { get; set; } }
优点:不需要修改验证属性,直接复用标准注解;缺点:依赖模型绑定的类型转换逻辑,只适合ASP.NET Core场景,对复杂自定义类型的支持有限。
内容的提问来源于stack exchange,提问作者Jake Shakesworth

