16 May 2007

Developing a Custom Property Comparison Validator using Entlib's VAP

 Here's a sample scenario where, in an application using Entlib's Validation Application Block (VAP), the validation of an entity's member depends on the value of  another property. In this case the entity I want to validate has two properties. Using the attributes approach to support validation, the entity's code looks like this:

public class MyEntity

{

    private string myVar1;

 

    [NotNullValidator()]

    [StringLengthValidator(0, 20, MessageTemplate = "MyProperty1 must be less than 20 characters length")]

    public string MyProperty1

    {

        get { return myVar1; }

        set { myVar1 = value; }

    }

 

    private string myVar2;

 

    [MyProp2Validator()]

    public string MyProperty2

    {

        get { return myVar2; }

        set { myVar2 = value; }

    }

}

The validators for Property1 are built in validators, no big deal with that. The thing here is that I want the second property to be validated against the value of the first one. Let’s say, for example, that Property2 is valid if it begins with the value of Property1.

Although the VAP ships with a PropertyComparisonValidator, it is limited to comparisons of type =, !=, >, >=, <>. So it seems that a custom validator has to be made. But how to obtain the actual value of Property1 from the Property2's custom validator?

One approach is to use reflection. Using reflection the DoValidate method of MyProperty2Validator would look something like this:

protected override void DoValidate(string objectToValidate, object currentTarget, string key, ValidationResults validationResults){

    //obtain the other property value through reflection

    PropertyInfo prop = currentTarget.GetType().GetProperty("MyProperty1");

    string myProp1Value = prop.GetValue(currentTarget, null) as string;

    //some custom logic here

    if (! objectToValidate.StartsWith( myProp1Value ){

        LogValidationResult(validationResults,"msg",currentTarget,key);

    }

}

But the dropdown of using reflection is that it wouldn't work with UI integration.

What do work with integration is what the built-in PropertyComparisonValidator does, and that is using the ValueAccess class. The ValueAccess class allow us to find other members in an integration friendly way throuhg its GetValue method. See how this is done in our Property2 valdiator:

[ConfigurationElementType(typeof(MyProp2ValidatorData))]

class MyProp2Validator : Validator<string> {

    private ValueAccess valueAccess;

    internal const string OtherPropName = "MyProperty1";

 

    public MyProp2Validator(ValueAccess valueAccess):base (Resources.MyProp2ValidatorMessageTemplate,null){

        this.valueAccess = valueAccess;

    }

 

    protected override void DoValidate(string objectToValidate, object currentTarget, string key, ValidationResults validationResults){

        object myProp1;

        string valueAccessFailureMessage;

        //try to obtain the value of property 1.

        if (!this.valueAccess.GetValue(currentTarget, out myProp1, out valueAccessFailureMessage)) {

            base.LogValidationResult(validationResults, string.Format(CultureInfo.CurrentUICulture, Resources.MyProp2ValidatorFailureToRetrieveProp1, new object[] { this.valueAccess.Key, valueAccessFailureMessage }), currentTarget, key);

        }

        else {

            //custom validation logic between prop1 and prop2

            if (!objectToValidate.StartsWith((string)myProp1)) {

                base.LogValidationResult(validationResults, string.Format(CultureInfo.CurrentUICulture, this.MessageTemplate, new object[] { objectToValidate, myProp1 }), currentTarget, key);

            }

        }

    }

 

    protected override string DefaultMessageTemplate {

        get { return Resources.MyProp2ValidatorDefaultMessageTemplate; }

    }

}

What’s left is to provide the ValueAccess to the validator’s constructor. This is done in the MyProp2ValidatorAttribute class, if using attributes to place validators, or in the MyProp2ValidatorData if using configuration files. MyProp2ValidatorAttribute would look like this:

public class MyProp2ValidatorAttribute : ValueValidatorAttribute

{

    protected override Validator DoCreateValidator(Type targetType)

    {

        throw new InvalidOperationException("A member value access builder is needed.");

    }

 

    protected override Validator DoCreateValidator(Type targetType, Type ownerType, MemberValueAccessBuilder memberValueAccessBuilder)

    {

        PropertyInfo propertyInfo = ownerType.GetProperty(MyProp2Validator.OtherPropName);

        if (propertyInfo == null)

        {

            throw new InvalidOperationException(String.Format(Resources.MyProp2ValidatorAttributeCouldNotFindProperty, new string[] { ownerType.Name, MyProp2Validator.OtherPropName }));

        }

        return new MyProp2Validator(memberValueAccessBuilder.GetPropertyValueAccess(propertyInfo));

    }

}

Finally, you can test the validation logic using the following code:

private void buttonValidate_Click(object sender, EventArgs e)

{

    MyEntity ent = new MyEntity();

    ent.MyProperty1 = textBoxProp1.Text;

    ent.MyProperty2 = textBoxProp2.Text;

    ValidationResults r = Validation.Validate<MyEntity>(ent);       

    if (!r.IsValid)

    {

        DisplayValidationResults(r);

    }

    else {

        MessageBox.Show("ok");

    }

}

It's only left to provide the UI integration for the custom validator to assure averything's working right. I hope to be posting this in a second part soon.