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

WPF:如何验证ViewModel中自定义对象属性(无需迁移或修改领域模型)

方案1:在ViewModel内手动验证Product属性并关联错误路径

利用ObservableValidator的内置方法,在Product属性变更时手动验证其内部属性,并通过指定完整属性路径(如Product.Name)将错误绑定到对应UI字段。

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MyViewModel : ObservableValidator
{
    private Product _product;
    
    public Product Product 
    { 
        get => _product; 
        set 
        {
            if (SetProperty(ref _product, value, false))
            {
                // 每次Product变更时重新验证所有属性
                ValidateProductProperties();
            }
        }
    }

    private void ValidateProductProperties()
    {
        // 清空之前Product相关的错误
        var productErrors = ValidationErrors.Where(e => e.PropertyName.StartsWith("Product.")).ToList();
        foreach (var error in productErrors)
        {
            RemoveError(error.PropertyName, error.ErrorMessage);
        }

        if (Product == null)
        {
            AddError("Product", "产品不能为空");
            return;
        }

        // 验证Name属性
        if (string.IsNullOrEmpty(Product.Name))
        {
            AddError("Product.Name", "产品名称不能为空");
        }

        // 验证Id属性
        if (Product.Id <= 0)
        {
            AddError("Product.Id", "产品ID必须大于0");
        }
    }
}

说明:

  • 通过AddError指定带前缀的属性路径,确保UI绑定Product.Name时能正确显示错误
  • 先清理旧错误避免残留,再执行新验证逻辑
方案2:创建内部包装类继承ObservableValidator

在ViewModel内部定义一个包装类,继承ObservableValidator并添加验证特性,同时同步原Product对象的属性值,既不修改领域模型,又能利用特性化验证。

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MyViewModel : ObservableValidator
{
    private Product _product;
    private ProductWrapper _productWrapper;

    public ProductWrapper ProductWrapper
    {
        get => _productWrapper;
        set => SetProperty(ref _productWrapper, value);
    }

    public Product Product
    {
        get => _product;
        set
        {
            if (SetProperty(ref _product, value))
            {
                ProductWrapper = new ProductWrapper(value);
                // 订阅包装类的错误变更事件,实时同步到ViewModel
                ProductWrapper.ValidationErrorsChanged += OnProductWrapperErrorsChanged;
                // 初始化同步现有错误
                SyncWrapperErrors();
            }
        }
    }

    private void OnProductWrapperErrorsChanged(object? sender, DataErrorsChangedEventArgs e)
    {
        if (sender is ProductWrapper wrapper && !string.IsNullOrEmpty(e.PropertyName))
        {
            var fullPath = $"Product.{e.PropertyName}";
            ClearErrors(fullPath);
            foreach (var error in wrapper.GetErrors(e.PropertyName).Cast<string>())
            {
                AddError(fullPath, error);
            }
        }
    }

    private void SyncWrapperErrors()
    {
        foreach (var error in ProductWrapper.ValidationErrors)
        {
            AddError($"Product.{error.PropertyName}", error.ErrorMessage);
        }
    }

    // 内部包装类,用于添加验证特性
    public class ProductWrapper : ObservableValidator
    {
        private readonly Product _product;

        public ProductWrapper(Product product)
        {
            _product = product;
        }

        [Required(ErrorMessage = "产品名称不能为空")]
        [MinLength(2, ErrorMessage = "产品名称长度不能少于2个字符")]
        public string Name
        {
            get => _product.Name;
            set
            {
                if (_product.Name != value)
                {
                    _product.Name = value;
                    OnPropertyChanged();
                    ValidateProperty(value, nameof(Name));
                }
            }
        }

        [Range(1, int.MaxValue, ErrorMessage = "产品ID必须大于0")]
        public int Id
        {
            get => _product.Id;
            set
            {
                if (_product.Id != value)
                {
                    _product.Id = value;
                    OnPropertyChanged();
                    ValidateProperty(value, nameof(Id));
                }
            }
        }
    }
}

说明:

  • 包装类负责承载验证特性,原Product类保持纯净
  • 通过事件同步包装类的错误到ViewModel,确保错误路径与UI绑定匹配
方案3:实现INotifyDataErrorInfo自定义验证(不依赖Mvvm Toolkit)

如果不想依赖第三方库,直接实现INotifyDataErrorInfo接口,完全自定义验证逻辑和错误管理。

public class Product
{
    public int Id { get; set; }
    public string Name { get; set; }
}

public class MyViewModel : ObservableObject, INotifyDataErrorInfo
{
    private Product _product;
    private readonly Dictionary<string, List<string>> _validationErrors = new();

    public Product Product
    {
        get => _product;
        set
        {
            if (SetProperty(ref _product, value))
            {
                ValidateAllProperties();
            }
        }
    }

    public bool HasErrors => _validationErrors.Any();

    public event EventHandler<DataErrorsChangedEventArgs>? ErrorsChanged;

    public IEnumerable GetErrors(string? propertyName)
    {
        if (string.IsNullOrEmpty(propertyName) || !_validationErrors.ContainsKey(propertyName))
            return Enumerable.Empty<string>();
        return _validationErrors[propertyName];
    }

    private void ValidateAllProperties()
    {
        _validationErrors.Clear();

        if (Product == null)
        {
            AddError("Product", "产品不能为空");
            return;
        }

        if (string.IsNullOrEmpty(Product.Name))
        {
            AddError("Product.Name", "产品名称不能为空");
        }

        if (Product.Id <= 0)
        {
            AddError("Product.Id", "产品ID必须大于0");
        }

        OnErrorsChanged(string.Empty);
    }

    private void AddError(string propertyName, string errorMessage)
    {
        if (!_validationErrors.ContainsKey(propertyName))
            _validationErrors[propertyName] = new List<string>();

        if (!_validationErrors[propertyName].Contains(errorMessage))
        {
            _validationErrors[propertyName].Add(errorMessage);
            OnErrorsChanged(propertyName);
        }
    }

    private void OnErrorsChanged(string propertyName)
    {
        ErrorsChanged?.Invoke(this, new DataErrorsChangedEventArgs(propertyName));
    }
}

说明:

  • 完全自主控制错误集合和验证逻辑,灵活性最高
  • 通过ErrorsChanged事件通知UI更新错误状态,适配WPF等支持该接口的UI框架

内容的提问来源于stack exchange,提问作者salttoinjury

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.28 12:45:07