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
相关产品推荐
相关产品推荐

