如何在无法继承的双类中复用DataAnnotations属性验证逻辑?
解决DataAnnotations验证逻辑复用的可行方案
针对你遇到的两个孪生类重复验证注解的问题,以下是几种实用的解决方案:
方案1:使用元数据类(MetadataTypeAttribute)
C#的MetadataTypeAttribute允许你将验证注解集中定义在一个元数据类中,然后让多个业务类关联到这个元数据类,实现注解复用。
步骤:
- 定义包含所有验证注解的元数据类
- 在
Shape和ShapeUI上标注[MetadataType]指向该元数据类
代码示例:
// 定义元数据类,集中存放验证注解 public class ShapeMetadata { [Range(3, int.MaxValue, ErrorMessage = "至少3条边")] public int N { get; set; } [Range(double.Epsilon, double.MaxValue, ErrorMessage = "边长必须为正数")] public double side { get; set; } } // 修改Shape类,关联元数据 [MetadataType(typeof(ShapeMetadata))] public class Shape : IShape { public int N { get; set; } public double side { get; set; } public Shape(int N, double S) { this.N = N; this.side = S; } public double perimeter => ShapeExtension.perimeter(this); public double area => ShapeExtension.area(this); } // 修改ShapeUI类,关联元数据 [MetadataType(typeof(ShapeMetadata))] public class ShapeUI : ViewModel, IShape, INotifyPropertyChanged { public event PropertyChangedEventHandler? PropertyChanged; public ShapeUI(int N, double S) { this._N = N; this._side = S; } private int _N; public int N { get => _N; set { _N = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(perimeter)); NotifyPropertyChanged(nameof(area)); } } private double _side; public double side { get => _side; set { if (value != this._side) { this._side = value; NotifyPropertyChanged(); NotifyPropertyChanged(nameof(perimeter)); NotifyPropertyChanged(nameof(area)); } } } public double perimeter => ShapeExtension.perimeter(this); public double area => ShapeExtension.area(this); private void NotifyPropertyChanged([CallerMemberName] String propertyName = "") { PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName)); Console.WriteLine("changing " + propertyName); } }
注意:使用元数据类时,确保元数据类的属性名、类型和业务类完全匹配,这样验证框架才能正确识别注解。
方案2:自定义共享验证特性
把通用的验证逻辑封装成自定义ValidationAttribute,这样两个类的属性上只需添加这个自定义特性,验证逻辑集中维护。
示例:针对N的自定义范围验证特性
// 自定义验证特性,封装N的验证逻辑 public class MinSidesAttribute : RangeAttribute { public MinSidesAttribute() : base(3, int.MaxValue) { ErrorMessage = "至少3条边"; } } // 自定义边长验证特性 public class PositiveSideAttribute : RangeAttribute { public PositiveSideAttribute() : base(double.Epsilon, double.MaxValue) { ErrorMessage = "边长必须为正数"; } } // 使用自定义特性的Shape类 public class Shape : IShape { [MinSides] public int N { get; set; } [PositiveSide] public double side { get; set; } // 其他代码不变 } // 使用自定义特性的ShapeUI类 public class ShapeUI : ViewModel, IShape, INotifyPropertyChanged { private int _N; [MinSides] public int N { // 属性访问器不变 } private double _side; [PositiveSide] public double side { // 属性访问器不变 } // 其他代码不变 }
这种方式的好处是如果后续需要修改验证规则,只需修改自定义特性的实现,所有使用该特性的属性都会自动更新。
方案3:实现IValidatableObject接口,手动复用验证逻辑
如果需要更灵活的验证逻辑(比如跨属性验证),可以让两个类实现IValidatableObject接口,然后把共享的验证逻辑抽到静态类中,两个类的Validate方法直接调用该逻辑。
代码示例:
// 共享验证逻辑类 public static class ShapeValidator { public static IEnumerable<ValidationResult> ValidateShape(IShape shape, ValidationContext validationContext) { var results = new List<ValidationResult>(); // 验证N的范围 if (shape.N < 3) { results.Add(new ValidationResult("至少3条边", new[] { nameof(shape.N) })); } // 验证side的范围 if (shape.side <= double.Epsilon) { results.Add(new ValidationResult("边长必须为正数", new[] { nameof(shape.side) })); } return results; } } // 修改Shape类实现IValidatableObject public class Shape : IShape, IValidatableObject { // 属性定义不变 public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { return ShapeValidator.ValidateShape(this, validationContext); } } // 修改ShapeUI类实现IValidatableObject public class ShapeUI : ViewModel, IShape, INotifyPropertyChanged, IValidatableObject { // 属性和INotifyPropertyChanged实现不变 public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) { return ShapeValidator.ValidateShape(this, validationContext); } }
这种方式完全脱离DataAnnotations的特性绑定,适合复杂验证场景,同时保证验证逻辑只写一次。
内容的提问来源于stack exchange,提问作者Dario
相关产品推荐
相关产品推荐

