Question

I have a parent class implementing INotifyPropertyChanged, and the parent class has multiple children. The children have different properties that all call PropertyChanged. I want to add validation, but I really don't want to have to write validations for every child class. The validation rules are supplied from the database, so I would have to eventually pull the validation rules for each child, then check the value against the rules. If I did that, I think that it would have too much redundant code, and I would like to have it placed at the parent level since PropertyChanged triggers on the string value of the value itself.

Is it possible to have the validation method on the parent class so I wouldn't have to write a validation method for every child class? Mind you, the properties in every child class are different.

Below is what I currently have, with validation in the child class.

public Parent : INotifyChanged {
    /// <summary>
    /// Occurs when a property is changed
    /// </summary>
    public event PropertyChangedEventHandler PropertyChanged;

    /// <summary>
    /// Raises the <see cref="PropertyChanged"/> for a given 
    /// property.
    /// </summary>
    /// <param name="propertyName"></param>
    protected void OnPropertyChanged(String propertyName) {
        // Get the hanlder
        PropertyChangedEventHandler handler = this.PropertyChanged;

        // Check that the event handler is not null
        if(null != handler) {
            // Fire the event
            handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

Child1 Class:

public Child1 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();

    private void Validate() {
        this.RemoveError("Child1Description");
        if(!Regex.IsMatch(Child1Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
            this.AddError("Child1Description", "Only non-numerics allowed.");
        }
    }

    private void AddError(string columnName, string msg) {
        if(!m_validationErrors.ContainsKey(columnName)) {
            m_validationErrors.Add(columnName, msg);
        }
    }

    private void RemoveError(string columnName) {
        if(m_validationErrors.ContainsKey(columnName)) {
            m_validationErrors.Remove(columnName);
        }
    }

    public string Error {
        get {
            if(m_validationErrors.Count > 0) {
                return "Field data is invalid.";
            }
            else return null;
        }
    }

    public string this[string columnName] {
        get {
            if(m_validationErrors.ContainsKey(columnName)) {
                return m_validationErrors[columnName];
            }
            else {
                return null;
            }
        }
    }
    /// <summary>
    /// Description of the air entity
    /// </summary>
    public string Child1Description {
        get {
            return Child1description;
        }
        set {
            description = value;
            Validate();
            OnPropertyChanged("Child1Description");
        }
    }
}

Child2 Class:

public Child2 : Parent, IDataErrorInfo {
private Dictionary<string, string> m_validationErrors = new Dictionary<string, string>();

    private void Validate() {
        this.RemoveError("Child2Description");
        if(!Regex.IsMatch(Child2Description, "^([a-zA-Z '-]+)$") && !String.IsNullOrWhiteSpace(Description)) {
            this.AddError("Child2Description", "Only non-numerics allowed.");
        }
    }

    private void AddError(string columnName, string msg) {
        if(!m_validationErrors.ContainsKey(columnName)) {
            m_validationErrors.Add(columnName, msg);
        }
    }

    private void RemoveError(string columnName) {
        if(m_validationErrors.ContainsKey(columnName)) {
            m_validationErrors.Remove(columnName);
        }
    }

    public string Error {
        get {
            if(m_validationErrors.Count > 0) {
                return "Field data is invalid.";
            }
            else return null;
        }
    }

    public string this[string columnName] {
        get {
            if(m_validationErrors.ContainsKey(columnName)) {
                return m_validationErrors[columnName];
            }
            else {
                return null;
            }
        }
    }
    /// <summary>
    /// Description of the air entity
    /// </summary>
    public string Child2Description {
        get {
            return Child2description;
        }
        set {
            description = value;
            Validate();
            OnPropertyChanged("Child2Description");
        }
    }
}
Was it helpful?

Solution 2

I ended up passing the property name, the property value and the list of rules I wanted to use to validate. Storing the Validate method in the parent, so it would run regardless if which child used it, it would use its own rules, but keep the same validation error message dictionary.

 protected void Validate(string propertyName, string propertyValue, List<ValidRule> validRules) {
      string temp = propertyValue.ToString();
      this.RemoveError(propertyName);
      if(propertyName.Equals("Description")) {
           foreach(ValidRule validRule in validRules) {
                if(!Regex.IsMatch(propertyValue, validRule.Rule) && !String.IsNullOrWhiteSpace(propertyValue)) {
                     this.AddError(propertyName, validRule.ErrorMessage);
                     break;
                }
           }
      }
 }

OTHER TIPS

I don't believe you're going to be able to do what you want.

In the trivial case, you may be able to make it work. Once you move into more complex types, I don't know that you're saving yourself much effort by having the validation in the parent instead of the child.

The trivial case is going to be where multiple children have similar types of properties. You can enforce calling the properties the same way and then you can write a validation rule within the parent that triggers on the name of the property. However, you could argue that those properties should then be part of the parent and inherited by the child.

The more complex case is individual properties at each child with little to no similarity to the properties of other children. Whether you put the validation code at the child or the parent makes no difference. You have to write the validation code for each individual property you wish to validate.

Given that your validation rules will be stored in a DB, you could write a method in the parent that would allow the children to retrieve the validation rule(s) for their properties. The child would still validate its own properties, but you would have common code for accessing the rules.

Actually it is do able just not the way you think you want it to occur. The below would be the steps I would follow to do something similar.

  1. get the Microsoft enterprise library for starters as you will be using the Microsoft.Practices.EnterpriseLibrary.Validation reference.
  2. Create a validation class that inherits from Validator<T> (this is part of the Enterprise library).
  3. Override the method DoValidate(T objectToValidate, object currentTarget, string key, ValidationResults validationResults) and currentTarget is the object being validated. You can pull the validation rules from the current target.
  4. You then create attribute for that validate making it inherit from ValueValidatorAttribute.
  5. You override the method DoCreateValidator(Type targetType, Type ownerType, MemberValueAccessBuilder memberValueAccessBuilder, ValidatorFactory validatorFactory) in the attribute class.

Once these first 5 steps are done it means you can attribute properties you want to validate and let the validator pick up the validation rule to use from the class (list of rules or dictionary or rules to perform to the property totally your choice).

The next step is to move the interface IDataErrorinfo to the parent class, create a Validate() method that takes the results from the call Microsoft.Practices.EnterpriseLibrary.Validation.Validation.Validate(this); which returns validation errors should they have occurred.

Its up to you on how where you want to place the method call. The simplest way when testing would be to place it in the OnPropertyChanged method you have.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top