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

如何在父类实现IDataErrorInfo验证子对象并完成验证绑定?附代码实现困惑求助

How to Validate Child Objects by Implementing IDataErrorInfo on Parent Class

Hi there! No worries at all—validation with nested objects can feel confusing when you're starting out. Let's break down exactly how to use that DataErrorInfo abstract class with your AddEditItemVM and nested models step by step.


Step 1: Make Your ViewModel Inherit from DataErrorInfo

First, update your AddEditItemVM to inherit from the DataErrorInfo abstract class. Also, ensure you have the necessary using directives for the validation library (the one that provides Validator, ValidationSpecificationSource, etc.—this is typically the Enterprise Library Validation Application Block or a similar framework):

using System.ComponentModel;
using System.Reflection;
// Add your validation library's using directive here, e.g.:
// using Microsoft.Practices.EnterpriseLibrary.Validation;

public class AddEditItemVM : DataErrorInfo {
    // Your existing properties
    public Category SelectedCategory { 
        get => _SelectedCategory; 
        set { 
            _SelectedCategory = value; 
            OnPropertyChanged(nameof(SelectedCategory)); 
        } 
    }
    private Category _SelectedCategory = new Category();

    public Items Source { 
        get => _Source; 
        set { 
            _Source = value; 
            OnPropertyChanged(nameof(Source));
            // Raise change notifications for nested properties to trigger validation
            OnPropertyChanged(nameof(Source.Name));
            OnPropertyChanged(nameof(Source.Price));
            OnPropertyChanged(nameof(Source.TaxPerc));
        } 
    }
    private Items _Source = new Items();

    // INotifyPropertyChanged implementation (critical for validation triggers)
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Step 2: Add Validation Rules to Your Model Properties

The DataErrorInfo class relies on validation attributes (or configuration) to define rules. Add these attributes to your Category and Items classes to enforce your required checks:

Updated Category Class

using System.ComponentModel.DataAnnotations; // Adjust if using a different validation library

public class Category : INotifyPropertyChanged {
    // Validate that a category is selected (Id can't be 0)
    [Range(1, long.MaxValue, ErrorMessage = "Please select a valid category")]
    public long Id { 
        get => _Id; 
        set { 
            _Id = value; 
            OnPropertyChanged(nameof(Id)); 
        } 
    }
    long _Id;

    public string Name { 
        get => _Name; 
        set { 
            _Name = value; 
            OnPropertyChanged(nameof(Name)); 
        } 
    }
    private string _Name;

    // INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Updated Items Class

using System.ComponentModel.DataAnnotations;

public class Items : INotifyPropertyChanged {
    public int CategoryID { 
        get => _CategoryID; 
        set { 
            _CategoryID = value; 
            OnPropertyChanged(nameof(CategoryID)); 
        } 
    }
    int _CategoryID;

    // Validate Name: Required + max 20 characters
    [Required(ErrorMessage = "Item name cannot be empty")]
    [StringLength(20, ErrorMessage = "Item name cannot exceed 20 characters")]
    public string Name { 
        get => _Name; 
        set { 
            _Name = value; 
            OnPropertyChanged(nameof(Name)); 
        } 
    }
    string _Name = String.Empty;

    // Validate TaxPerc: Required + between 1-100
    [Required(ErrorMessage = "Tax percentage is required")]
    [Range(1, 100, ErrorMessage = "Tax percentage must be between 1 and 100")]
    public decimal? TaxPerc { 
        get => _TaxPerc; 
        set { 
            _TaxPerc = value; // Reverted to use the passed value (adjust if you still need the Settings.Default logic)
            OnPropertyChanged(nameof(TaxPerc)); 
            OnPropertyChanged(nameof(PricePlsTax)); 
            OnPropertyChanged(nameof(TaxAmount)); 
        } 
    }
    decimal? _TaxPerc = null;

    // Validate Price: Required + cannot be zero
    [Required(ErrorMessage = "Price cannot be empty")]
    [Range(0.01, decimal.MaxValue, ErrorMessage = "Price cannot be zero")]
    public decimal? Price { 
        get => _Price; 
        set { 
            _Price = value; 
            OnPropertyChanged(nameof(Price)); 
            OnPropertyChanged(nameof(PricePlsTax)); 
            OnPropertyChanged(nameof(TaxAmount)); 
        } 
    }
    decimal? _Price = null;

    // Computed properties (assumed implementation)
    public decimal PricePlsTax => Price.GetValueOrDefault() * (1 + TaxPerc.GetValueOrDefault() / 100);
    public decimal TaxAmount => Price.GetValueOrDefault() * (TaxPerc.GetValueOrDefault() / 100);

    // INotifyPropertyChanged implementation
    public event PropertyChangedEventHandler PropertyChanged;
    protected void OnPropertyChanged(string propertyName) {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Step 3: Extend Validation to Handle Nested Properties

The default DataErrorInfo implementation only validates properties directly on the parent class. To validate nested properties like Source.Name or SelectedCategory.Id, override the indexer in your AddEditItemVM to traverse nested paths:

First, update the abstract DataErrorInfo class to make the validator method accessible:

public abstract class DataErrorInfo : IDataErrorInfo {
    string IDataErrorInfo.Error { get { return string.Empty; } }

    // Make the indexer virtual so we can override it
    virtual string IDataErrorInfo.this[string columnName] {
        get {
            var prop = this.GetType().GetProperty(columnName);
            return GetErrorInfo(prop);
        }
    }

    private string GetErrorInfo(PropertyInfo prop) {
        var validator = GetPropertyValidator(prop);
        if (validator != null) {
            var results = validator.Validate(this);
            if (!results.IsValid) {
                return string.Join(" ", results.Select(r => r.Message).ToArray());
            }
        }
        return string.Empty;
    }

    // Make this method protected so child classes can reuse it
    protected virtual Validator GetPropertyValidator(PropertyInfo prop) {
        string ruleset = string.Empty;
        var source = ValidationSpecificationSource.All;
        var builder = new ReflectionMemberValueAccessBuilder();
        return PropertyValidationFactory.GetPropertyValidator(
            prop.DeclaringType, prop, ruleset, source, builder);
    }
}

Then, override the indexer in AddEditItemVM to handle nested properties:

public class AddEditItemVM : DataErrorInfo {
    // ... Your existing properties and INotifyPropertyChanged ...

    string IDataErrorInfo.this[string columnName] {
        get {
            // Handle nested properties (e.g., "Source.Name")
            if (columnName.Contains('.')) {
                var propertyParts = columnName.Split('.');
                object currentObject = this;

                // Traverse each part of the nested path
                foreach (var part in propertyParts) {
                    var prop = currentObject.GetType().GetProperty(part);
                    if (prop == null) return string.Empty;
                    currentObject = prop.GetValue(currentObject);
                    if (currentObject == null) return "Required field cannot be empty";
                }

                // Validate the final nested property
                var nestedProp = currentObject.GetType().GetProperty(propertyParts.Last());
                if (nestedProp != null) {
                    var validator = GetPropertyValidator(nestedProp);
                    if (validator != null) {
                        var results = validator.Validate(currentObject);
                        if (!results.IsValid) {
                            return string.Join(" ", results.Select(r => r.Message).ToArray());
                        }
                    }
                }
                return string.Empty;
            }

            // Use base implementation for non-nested properties
            return base[columnName];
        }
    }
}

Step 4: Enable Validation in Your View

In your XAML, bind controls to the nested properties and enable validation feedback with ValidatesOnDataErrors=True:

<!-- Category Selection -->
<ComboBox ItemsSource="{Binding Categories}" 
          SelectedItem="{Binding SelectedCategory, ValidatesOnDataErrors=True}"
          DisplayMemberPath="Name" />

<!-- Item Name -->
<TextBox Text="{Binding Source.Name, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

<!-- Item Price -->
<TextBox Text="{Binding Source.Price, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

<!-- Tax Percentage -->
<TextBox Text="{Binding Source.TaxPerc, ValidatesOnDataErrors=True, UpdateSourceTrigger=PropertyChanged}" />

Key Reminders

  • All classes must implement INotifyPropertyChanged to trigger validation when properties update.
  • Adjust validation attributes if you're using a library other than System.ComponentModel.DataAnnotations (e.g., Enterprise Library's NotNullValidator or RangeValidator).
  • When updating the Source property, raise PropertyChanged for all nested properties to ensure validation refreshes immediately.

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.27 14:37:47