WPF Custom Validation Using the Enterprise Library

This shows how to create a custom validator using the Microsoft Enterprise Library.

Introduction

I’ve been using the Enterprise Library’s Validation Application Block for input validation in a WPF application and the different out-of-the-box validators are sufficient for quite some time. However, there came a time that the validation logic became complex that I need to create a custom validator.

Getting Started

If you don’t have the Enterprise Library yet, you can get it at http://www.codeplex.com/entlib. In this article, I’ll be using the 4.1 – October 2008 release. After installation, we can now use the various libraries in our project. Let’s create a class library that will contain our custom validator. In our example, I’ll be adding a custom validator called CollectionNotEmtpyValidator. This class will inherit from the ValueValidator class from the Validation Application Block. If we want to create a generic custom validator then we’ll have to use the ValueValidator(Of T) class. We’ll need to add a reference to the Microsoft.Practices.EnterpriseLibrary.Validation.dll (Enterprise Library Validation Application Block) to our project to use ValueValidator. The following code shows the custom validator.

public class CollectionNotEmptyValidator : ValueValidator

{

/// <summary>

/// Gets the Default Message Template when the validator is negated.

/// </summary>

protected override string DefaultNegatedMessageTemplate

{

get { throw new NotImplementedException(); }

}

/// <summary>

/// Gets the Default Message Template when the validator is non negated.

/// </summary>

protected override string DefaultNonNegatedMessageTemplate

{

get { throw new NotImplementedException(); }

}

/// <summary>

/// Implements the validation logic for the receiver.

/// </summary>

/// <param name="objectToValidate">The object to validate.</param>

/// <param name="currentTarget">The object on the behalf of which the validation is performed.</param>

/// <param name="key">The key that identifies the source of objectToValidate.</param>

/// <param name="validationResults">The validation results to which the outcome of the validation should be stored.</param>

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

{

throw new NotImplementedException();

}

}

Listing 1. The CollectionNotEmptyValidator Class

The ValueValidator class is abstract and we need to implement two properties and one method. The DefaultNegatedMessageTemplate returns the default message template when the Negated property of the validator is set to true. The Negated property, if set to true, indicates that the validation will fail if the condition is met. For example, if you specified that the range of an integer should be between 3 and 10 and the Negated property is set to true, the validation will fail when the value falls inside this range. The DefaultNonNegatedMessageTemplate returns the default message template when the Negated property is false. This is the default message template since the Negated property is set to false by default. Meanwhile, the DoValidate method implements the validation logic. The objectToValidate parameter contains the value to be validated. The currentTarget is the object that contains the element whose value is being validated. The key corresponds to the name of that element. For example, I want to validate a property SomeProperty of the object SomeObject. The objectToValidate will be the value of SomeProperty. The currentTarget is the SomeObject instance. The key will be the word SomeProperty. The validationResults is a collection of validation results.

Trying to build the code above will result in an error because the base class, ValueValidator, does not provide a constructor with no arguments. The only constructor it has accepts the following parameters: a string that will be used as the message template, a string that serves as a tag that may help in categorizing validation failures and a Boolean value used in setting the Negated property.

Implementing the Validator

We’ll just implement a simple validator, one that checks if the collection is not empty. This could be useful in an application where you need to enforce an items control, that is bound to a collection, to have at least one value. The following code snippet shows the updated validator.

public class CollectionNotEmptyValidator : ValueValidator

{

private const string defaultNegatedMessagetTemplate = "The collection must be empty.";

private const string defaultNonNegatedMessageTemplate = "The collection must not be empty.";

public CollectionNotEmptyValidator()

: base(null, null, false)

{

}

public CollectionNotEmptyValidator(string messageTemplate)

: base(messageTemplate, null, false)

{

}

public CollectionNotEmptyValidator(bool negated)

: base(null, null, negated)

{

}

/// <summary>

/// Gets the Default Message Template when the validator is negated.

/// </summary>

protected override string DefaultNegatedMessageTemplate

{

get { return defaultNegatedMessagetTemplate; }

}

/// <summary>

/// Gets the Default Message Template when the validator is non negated.

/// </summary>

protected override string DefaultNonNegatedMessageTemplate

{

get { return defaultNonNegatedMessageTemplate; }

}

/// <summary>

/// Implements the validation logic for the receiver.

/// </summary>

/// <param name="objectToValidate">The object to validate.</param>

/// <param name="currentTarget">The object on the behalf of which the validation is performed.</param>

/// <param name="key">The key that identifies the source of objectToValidate.</param>

/// <param name="validationResults">The validation results to which the outcome of the validation should be stored.</param>

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

{

if (objectToValidate is ICollection)

{

if ((((ICollection)objectToValidate).Count != 0) == Negated)

{

LogValidationResult(validationResults, MessageTemplate, currentTarget, key);

}

}

else

{

throw new ApplicationException("Object type not supported by validator.");

}

}

}

Listing 2. Updated CollectionNotEmptyValidator Class

I’ve only set the values of the default message templates using constants but it is better to put these strings in the settings file of the project. That will allow us to change the message templates without modifying the source code. The DoValidate method contains the validation logic, wherein the type of the objectToValidate is checked first before casting to an ICollection object. If not, then an exception is thrown. Otherwise, if the collection is empty when the Negated property is false, then a validation result is logged. This is also the case when the collection has at least one item and the Negated property is true. If the user did not specify a message template explicitly, then the default message template will be logged. The validator won’t be complete if we don’t create the corresponding attribute class. This class is shown below.

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property,

AllowMultiple = true,

Inherited = false)]

public sealed class CollectionNotEmptyValidatorAttribute : ValueValidatorAttribute

{

/// <summary>

/// Creates the Validator described by the attribute object providing validator specific

/// information.

/// </summary>

/// <param name="targetType">The type of object that will be validated by the validator.</param>

/// <returns>The created Validator.</returns>

protected override Validator DoCreateValidator(Type targetType)

{

return new CollectionNotEmptyValidator(Negated);

}

}

Listing 3. The CollectionNotEmptyValidatorAttribute Class

The CollectionNotEmptyValidatorAttribute class inherits from ValueValidatorAttribute. We need to implement the DoCreateValidator method. The DoCreateValidator method returns the Validator object to use in validating the object marked with the attribute. The targetType parameter is not used in our example because we know what type of validator to create. The targetType may be useful if you want to use the ValidationFactory class’ CreateValidator method to create a validator for the specified type. We also need to specify where the attribute may be applied using the AttributeUsageAttribute class. We specified here that the attribute can only be used for fields and properties. The AllowMultiple property indicates whether the attribute can be used multiple times for a single element. The Inherited property indicates whether the attribute can be inherited in derived classes.

Testing the Validator

As you can see, creating a custom validator is relatively easy. Let’s create a simple console application to test our new validator. I have defined an Order class containing an Items property for this. There are four possible cases we can test:

- Collection is empty, Negated property is false

- Collection is non-empty, Negated property is false

- Collection is non-empty, Negated property is true

- Collection is empty, Negated property is true

class Program

{

static void Main(string[] args)

{

Order order = new Order();

order.Items = new Collection<OrderItem>();

// Invalid test - collection is empty

CollectionNotEmptyValidator validator = new CollectionNotEmptyValidator();

ValidationResults results = validator.Validate(order.Items);

Debug.Assert(!results.IsValid);

Console.WriteLine(results.First().Message);

// Output: The collection must not be empty.

// Valid test - collection has items

order.Items.Add(new OrderItem());

results = validator.Validate(order.Items);

Debug.Assert(results.IsValid);

Console.WriteLine("Valid test");

// Invalid test - collection has items (Negated)

validator = new CollectionNotEmptyValidator(true);

results = validator.Validate(order.Items);

Debug.Assert(!results.IsValid);

Console.WriteLine(results.First().Message);

// Output: The collection must be empty.

// Valid test - collection is empty (Negated)

order.Items.Clear();

results = validator.Validate(order.Items);

Debug.Assert(results.IsValid);

Console.WriteLine("Valid test");

Console.ReadKey();

}

}

Listing 4. Testing the Validator

In the preceding example, we used the validator’s Validate method. We can also use the attribute to validate the Items collection.

public class Order

{

[CollectionNotEmptyValidator]

public Collection<OrderItem> Items { get; set; }

}

class Program

{

static void Main(string[] args)

{

Order order = new Order();

order.Items = new Collection<OrderItem>();

// Use attribute

// Invalid test - collection is empty

results = Validation.ValidateFromAttributes(order);

Debug.Assert(!results.IsValid);

Console.WriteLine(results.First().Message);

// Output: The collection must not be empty.

// Valid test - collection has items

order.Items.Add(new OrderItem());

results = Validation.ValidateFromAttributes(order);

Debug.Assert(results.IsValid);

Console.WriteLine("Valid test");

Console.ReadKey();

}

}

Listing 5. Testing the Validator Using an Attribute

Notice in Listing 5 that we only tested the cases where the Negated property of the validator is false. If we want to test the other two cases, we need to set the Negated property to true in the attribute like this: [CollectionNotEmptyValidator(Negated = true)].

Custom Validation using a Configuration File

Another way to validate an object is to use a configuration file. This way, we can change the validation rules for a specific object without modifying the source code. To add a CollectionNotEmptyValidator to the Items property of the Order class, we can use the Enterprise Library Configuration Editor in Visual Studio. Just right click the application configuration file and select “Edit Enterprise Library Configuration”. Add the Validation Application Block and the Order type under it. You’ll have to find the assembly where this type can be found. After this, add a rule set and the members we want to validate. In our case, we want to validate the Items property.



Figure 1. Adding the CollectionNotEmptyValidator Using Configuration

We can add many rule sets for a certain type. Since we only have one rule set in the example, let’s just set the default rule set in the properties window of the Order type so we don’t have to specify the rule set later on. The last thing to do is to add the validator. We still can’t add our validator using the existing code because we have to add the ConfigurationElementType attribute to the validator. This allows us to use the validator through the configuration file. The ConfigurationElementTypeAttribute class’ constructor accepts a ValidatorData object, which is shown below.

public class CollectionNotEmptyValidatorData : ValueValidatorData

{

/// <summary>

/// Constructor.

/// </summary>

public CollectionNotEmptyValidatorData()

{

}

/// <summary>

/// Constructor.

/// </summary>

/// <param name="name">The name for the instance.</param>

public CollectionNotEmptyValidatorData(string name)

: base(name, typeof(CollectionNotEmptyValidator))

{

}

/// <summary>

/// Creates the Validator described by the configuration object.

/// </summary>

/// <param name="targetType">The type of object that will be validated by the validator.</param>

/// <returns>The created Validator.</returns>

protected override Validator DoCreateValidator(Type targetType)

{

return new CollectionNotEmptyValidator(Negated);

}

}

[ConfigurationElementType(typeof(CollectionNotEmptyValidatorData))]

public class CollectionNotEmptyValidator : ValueValidator

{

...

}

Listing 6. Adding a Configuration Object for the Validator

At the very least, we need to implement the DoCreateValidator method and return a validator instance. Now, we can add the validator to our configuration file. Unfortunately, we can’t use the built-in editor this time. We have to edit the configuration file manually. If you want your validator to be added to the choices when using the editor, you can use the Application Block Software Factory as detailed by Guy Burstein in his article. The following shows the configuration file.

<?xml version="1.0" encoding="utf-8"?>

<configuration>

<configSections>

<section name="validation" type="Microsoft.Practices.EnterpriseLibrary.Validation.Configuration.ValidationSettings, Microsoft.Practices.EnterpriseLibrary.Validation, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

<section name="dataConfiguration" type="Microsoft.Practices.EnterpriseLibrary.Data.Configuration.DatabaseSettings, Microsoft.Practices.EnterpriseLibrary.Data, Version=4.1.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />

</configSections>

<validation>

<type defaultRuleset="Rule Set" assemblyName="CustomValidatorsConfigDemo, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"

name="CustomValidatorsConfigDemo.Order">

<ruleset name="Rule Set">

<properties>

<property name="Items">

<validator negated="false" messageTemplate="" messageTemplateResourceName=""

messageTemplateResourceType="" tag="" type="CustomValidators.CollectionNotEmptyValidator, CustomValidators, Version=1.0.0.0, Culture=neutral"

name="Collection Not Empty Validator" />

</property>

</properties>

</ruleset>

</type>

</validation>

</configuration>

Listing 7. Configuration File

We need to specify the type of the validator along with its namespace, the assembly where it is found, the version, culture and the public key token if the assembly is signed. You can also set the other properties like the Negated and MessageTemplate properties. These properties can be edited in the properties window. Just a bit of warning. Opening the configuration file again in the Enterprise Library Configuration Editor may cause you to lose the custom validator configuration because we haven’t added it to the list of validators known by the enterprise library.

Relaying Validation Logic

Creating a custom validator is desirable if this will be used several times. Most of the time, I’ve been only validating properties or fields of a certain class and sometimes, the validation logic only applies to a single object. Creating a custom validator which is going to be used only once is not desirable. In this example, a method that is defined in the class containing the property or field to validate, will contain the validation logic. This is achieved by using the RelayValidator class.

public class RelayValidator : ValueValidator

{

private const string defaultNegatedMessagetTemplate = "The value is not valid.";

private const string defaultNonNegatedMessageTemplate = "The value is not valid.";

private readonly string validateMethodName;

public RelayValidator(string validateMethodName)

: base(null, null, false)

{

this.validateMethodName = validateMethodName;

}

public RelayValidator(string validateMethodName, string messageTemplate)

: base(messageTemplate, null, false)

{

this.validateMethodName = validateMethodName;

}

public RelayValidator(string validateMethodName, bool negated)

: base(null, null, negated)

{

this.validateMethodName = validateMethodName;

}

/// <summary>

/// Gets the Default Message Template when the validator is negated.

/// </summary>

protected override string DefaultNegatedMessageTemplate

{

get { return defaultNegatedMessagetTemplate; }

}

/// <summary>

/// Gets the Default Message Template when the validator is non negated.

/// </summary>

protected override string DefaultNonNegatedMessageTemplate

{

get { return defaultNonNegatedMessageTemplate; }

}

/// <summary>

/// Implements the validation logic for the receiver.

/// </summary>

/// <param name="objectToValidate">The object to validate.</param>

/// <param name="currentTarget">The object on the behalf of which the validation is performed.</param>

/// <param name="key">The key that identifies the source of objectToValidate.</param>

/// <param name="validationResults">The validation results to which the outcome of the validation should be stored.</param>

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

{

MethodInfo methodInfo = currentTarget.GetType().GetMethod(validateMethodName);


bool
isValid = (bool)methodInfo.Invoke(currentTarget, null);


if
(isValid == Negated)

{

LogValidationResult(validationResults, MessageTemplate, currentTarget, key);

}

}

}

Listing 8. The RelayValidator Class

The constructor accepts a string corresponding to the name of the method that contains the validation logic. As you can see in the DoValidate method, the method name corresponds to a public instance method having no parameters and returns a Boolean value. Using reflection, we are able to get the MethodInfo object and invoke the method. The return value indicates whether the object is valid or not. It would have been better if we could just specify a callback method to be used for validation instead of the method name. However, we will be creating a validator attribute for our custom validator and that means we won’t be able to specify arguments that cannot be resolved at compile time. The following code shows the validator attribute.

[AttributeUsage(AttributeTargets.Field | AttributeTargets.Property,

AllowMultiple = true,

Inherited = false)]

public sealed class RelayValidatorAttribute : ValueValidatorAttribute

{

private readonly string validateMethodName;

/// <summary>

/// Constructor.

/// </summary>

/// <param name="validateMethodName">

/// The name of the method containing the validation logic.

/// </param>

public RelayValidatorAttribute(string validateMethodName)

{

this.validateMethodName = validateMethodName;

}

/// <summary>

/// Creates the Validator described by the attribute object providing validator specific

/// information.

/// </summary>

/// <param name="targetType">The type of object that will be validated by the validator.</param>

/// <returns>The created Validator.</returns>

protected override Validator DoCreateValidator(Type targetType)

{

return new RelayValidator(validateMethodName, Negated);

}

}

Listing 9. The RelayValidatorAttribute Class

To use the validator, we just need to mark the property or field with the attribute. The following code shows how to check if a collection is empty, using the RelayValidator this time.

public class Order

{

[RelayValidator("ValidateItems")]

public Collection<OrderItem> Items { get; set; }

public bool ValidateItems()

{

return Items.Count > 0;

}

}

Listing 10. Using the RelayValidator

The RelayValidator is not very flexible. We cannot use the Validate method of the validator directly because the value of the currentTarget parameter in the DoValidate method will be the same as the value of the objectToValidate parameter, not the object containing the field or property. Remember that the RelayValidator class uses reflection to get a method from the currentTarget parameter. That only means we can only use the attribute. Since we can’t define the method that contains the validation logic in another class, choices are limited to the class’ methods. If we can’t modify the source code, then we’re out of luck. Also, if you are familiar with self-validation, the RelayValidator only does the same job in a quite different way.

Extending the RelayValidator

To solve some of the problems described in the preceding paragraph, let’s create a new validator that requires the assembly qualified type name of the class that has the public static method containing the validation logic. With this, we can define the method in a separate class and possibly in a separate assembly. The method should accept the objectToValidate and currentTarget parameters.

public class ExtendedRelayValidator : ValueValidator

{

private const string defaultNegatedMessagetTemplate = "The value is not valid.";

private const string defaultNonNegatedMessageTemplate = "The value is not valid.";

private readonly string assemblyQualifiedTypeName;

private readonly string validateMethodName;

public ExtendedRelayValidator(string assemblyQualifiedTypeName, string validateMethodName)

: base(null, null, false)

{

this.assemblyQualifiedTypeName = assemblyQualifiedTypeName;

this.validateMethodName = validateMethodName;

}

public ExtendedRelayValidator(string assemblyQualifiedTypeName, string validateMethodName, string messageTemplate)

: base(messageTemplate, null, false)

{

this.assemblyQualifiedTypeName = assemblyQualifiedTypeName;

this.validateMethodName = validateMethodName;

}

public ExtendedRelayValidator(string assemblyQualifiedTypeName, string validateMethodName, bool negated)

: base(null, null, negated)

{

this.assemblyQualifiedTypeName = assemblyQualifiedTypeName;

this.validateMethodName = validateMethodName;

}

/// <summary>

/// Gets the Default Message Template when the validator is negated.

/// </summary>

protected override string DefaultNegatedMessageTemplate

{

get { return defaultNegatedMessagetTemplate; }

}

/// <summary>

/// Gets the Default Message Template when the validator is non negated.

/// </summary>

protected override string DefaultNonNegatedMessageTemplate

{

get { return defaultNonNegatedMessageTemplate; }

}

/// <summary>

/// Implements the validation logic for the receiver.

/// </summary>

/// <param name="objectToValidate">The object to validate.</param>

/// <param name="currentTarget">The object on the behalf of which the validation is performed.</param>

/// <param name="key">The key that identifies the source of objectToValidate.</param>

/// <param name="validationResults">The validation results to which the outcome of the validation should be stored.</param>

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

{

MethodInfo methodInfo = Type.GetType(assemblyQualifiedTypeName).GetMethod(validateMethodName, BindingFlags.Public | BindingFlags.Static);


bool
isValid = (bool)methodInfo.Invoke(null, new object[] { objectToValidate, currentTarget });

if (isValid == Negated)

{

LogValidationResult(validationResults, MessageTemplate, currentTarget, key);

}

}

}

Listing 11. The ExtendedRelayValidator Class

The following code shows how to use the new validator. Take note that we need to supply the assembly qualified type name to find the type having the validation logic.

public class Order

{

[ExtendedRelayValidator

("ExtendedRelayValidatorDemo.OrderValidator, ExtendedRelayValidatorDemo, Version=1.0.0.0, Culture=neutral",

"ValidateItems")]

public Collection<OrderItem> Items { get; set; }

}

public class OrderItem

{

}

public class OrderValidator

{

public static bool ValidateItems(object objectToValidate, object currentTarget)

{

// current target may be useful in scenarios where we need

// to access other members.

Order order = (Order)currentTarget;


// validate the items.

ICollection items = (ICollection)objectToValidate;

return items.Count > 0;

}

}

Listing 11. Using The ExtendedRelayValidator Class

We can also use the validator through a configuration file just like what we did previously for the CollectionNotEmptyValidator. There are just some additional details since we need to set the assemblyQualifiedTypeName and validateMethodName through the configuration file. Here is the ValidatorData for the ExtendedRelayValidator class.

public class ExtendedRelayValidatorData : ValueValidatorData

{

private const string AssemblyQualifiedTypeNamePropertyName = "assemblyQualifiedTypeName";

private const string ValidateMethodNamePropertyName = "validateMethodName";

/// <summary>

/// Constructor.

/// </summary>

public ExtendedRelayValidatorData()

{

}

/// <summary>

/// Constructor.

/// </summary>

/// <param name="name">The name for the instance.</param>

public ExtendedRelayValidatorData(string name)

: base(name, typeof(CollectionNotEmptyValidator))

{

}

[ConfigurationProperty(AssemblyQualifiedTypeNamePropertyName)]

public string AssemblyQualifiedTypeName

{

get { return (string)this[AssemblyQualifiedTypeNamePropertyName]; }

set { this[AssemblyQualifiedTypeNamePropertyName] = value; }

}

[ConfigurationProperty(ValidateMethodNamePropertyName)]

public string ValidateMethodName

{

get { return (string)this[ValidateMethodNamePropertyName]; }

set { this[ValidateMethodNamePropertyName] = value; }

}

/// <summary>

/// Creates the Validator described by the configuration object.

/// </summary>

/// <param name="targetType">The type of object that will be validated by the validator.</param>

/// <returns>The created Validator.</returns>

protected override Validator DoCreateValidator(Type targetType)

{

return new ExtendedRelayValidator(AssemblyQualifiedTypeName, ValidateMethodName, Negated);

}

}

Listing 12. ValidatorData for the ExtendedRelayValidator

You can notice that we have two properties corresponding to the assemblyQualifiedName and validateMethodName parameters that the ExtendedRelayValidator needs. This is marked with the ConfigurationProperty attribute so that we can set it in the configuration file. You can check the Visual Studio 2008 solution here.

By Michael Detras   Popularity  (11961 Views)
Biography - Michael Detras
.NET developer. Interested in WPF, Silverlight, and XNA.
My blog