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

如何在无法继承的双类中复用DataAnnotations属性验证逻辑?

解决DataAnnotations验证逻辑复用的可行方案

针对你遇到的两个孪生类重复验证注解的问题,以下是几种实用的解决方案:

方案1:使用元数据类(MetadataTypeAttribute)

C#的MetadataTypeAttribute允许你将验证注解集中定义在一个元数据类中,然后让多个业务类关联到这个元数据类,实现注解复用。

步骤:

  1. 定义包含所有验证注解的元数据类
  2. 在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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:54:51