1. Validating Objects and Collections of Objects
The most common scenario when using the Validation block is to
validate an instance of a class in your application. The Validation
block uses the combination of rules defined in a rule set and
validators added as attributes to test the values of members of the
class, and the result of executing any self-validation methods within
the class.
The Validation block makes it easy to validate entire objects
(all or a subset of its members) using a specific type validator or by using the Object validator. You can also validate all of the
objects in a collection using the Object Collection validator. We will look at the Object
validator and the Object Collection validator later. For the moment,
we'll concentrate on creating and using a specific type
validator.
Creating a Type Validator using the ValidatorFactory
You can resolve a ValidatorFactory instance through the
Enterprise Library container and use it to create a validator for a
specific target type. This validator will validate objects using a
rule set, and/or any attributes and self-validation methods the
target object contains. To obtain an instance of the ValidatorFactory class, you can use the
following code.
ValidatorFactory valFactory
= EnterpriseLibraryContainer.Current.GetInstance<ValidatorFactory>();
You can then create a validator for any type you want to
validate. For example, this code creates a validator for the Product class and then validates an instance
of that class named myProduct.
Validator<Product> pValidator = valFactory.CreateValidator<Product>();
ValidationResults valResults = pValidator.Validate(myProduct);
By default, the validator will use the default rule set
defined for the target type (you can define multiple rule sets for a
type, and specify one of these as the default for this type). If you
want the validator to use a specific rule set, you specify this as
the single parameter to the CreateValidator method, as shown here.
Validator<Product> productValidator
= valFactory.CreateValidator<Product>("RuleSetName");
ValidationResults valResults = productValidator.Validate(myProduct);
The example named Using a Validation Rule
Set to Validate an Object creates an instance of the
Product class that contains invalid
values for all of the properties, and then uses the code shown above to create a
type validator for this type and validate it. It then
displays details of the validation errors contained in the returned
ValidationResults
instance. However, rather than using the simple technique of iterating over the ValidationResults instance displaying the
top-level errors, it uses code to dive deeper into the results to
show more information about each validation error, as you will see
in the next section.
Delving Deeper into ValidationResults
You can check if validation succeeded, or if any validation
error were detected, by examining the IsValid property of a ValidationResults instance and displaying
details of any validation errors that occurred. However, when you
simply iterate over a Validation
Results instance , we displayed just the top-level errors. In many
cases, this is all you will require. If the validation error occurs
due to a validation failure in a composite (And or Or)
validator, the error this approach will display is the message and
details of the composite validator.
However, sometimes you may wish to delve deeper into the
contents of a Validation Results
instance to learn more about the errors that occurred. This is
especially the case when you use nested validators inside a
composite validator. The code we use in the example provides richer
information about the errors. When you run the example, it displays
the following results (we've removed some repeated content for
clarity).
The following 6 validation errors were detected:
+ Target object: Product, Member: DateDue
- Detected by: OrCompositeValidator
- Tag value: Date Due
- Validated value was: '23/11/2010 13:45:41'
- Message: 'Date Due must be between today and six months time.'
+ Nested validators:
- Detected by: NotNullValidator
- Validated value was: '23/11/2010 13:45:41'
- Message: 'Value can be NULL or a date.'
- Detected by: RelativeDateTimeValidator
- Validated value was: '23/11/2010 13:45:41'
- Message: 'Value can be NULL or a date.'
+ Target object: Product, Member: Description
- Detected by: OrCompositeValidator
- Validated value was: '-'
- Message: 'Description can be NULL or a string value.'
+ Nested validators:
- Detected by: StringLengthValidator
- Validated value was: '-'
- Message: 'Description must be between 5 and 100 characters.'
- Detected by: NotNullValidator
- Validated value was: '-'
- Message: 'Value can be NULL.'
...
...
+ Target object: Product, Member: ProductType
- Detected by: EnumConversionValidator
- Tag value: Product Type
- Validated value was: 'FurryThings'
- Message: 'Product Type must be a value from the 'ProductType' enumeration.'
You can see that this shows the target object type and the
name of the member of the target object that was being validated. It
also shows the type of the validator that performed the operation,
the Tag property values, and the
validation error message. Notice also that the output
includes the validation results from the validators nested within
the two OrCompositeValidator
validators. To achieve this, you must iterate recursively through
the ValidationResults
instance because it contains nested entries for the
composite validators.
The code we used also contains a somewhat contrived feature:
to be able to show the value being validated, some examples that use this routine include the validated
value at the start of the message using the {0} token in the form:
[{0}] validation error
message. The example code parses the Message property to extract the value and the
message when it detects that this message string contains such a
value. It also encodes this value for display in case it contains
malicious content.
While this may not represent a requirement in real-world
application scenarios, it is useful here as it allows the example to
display the invalid values that caused the validation errors and
help you understand how each of the validators works. We haven't
listed the code here, but you can examine it in the example
application to see how it works, and adapt it to meet your own
requirements. You'll find it in the ShowValidationResults, ShowValidatorDetails,
and GetTypeNameOnly routines located
in the region named Auxiliary routines at the end of the main
program file.
Using the Object Validator
An alternative approach to validating objects is to programmatically create an Object Validator by calling its constructor. You
specify the type that it will validate and, optionally, a rule set
to use when performing validation. If you do not specify a rule set
name, the validator will use the default rule set. When you call the
Validate method of the Object
validator, it creates a type-specific validator for the target type
you specify, and you can use this to validate the object, as shown
here.
Validator pValidator = new ObjectValidator(typeof(Product), "RuleSetName");
ValidationResults valResults = pValidator.Validate(myProduct);
Alternatively, you can call the default constructor of the
Object validator. In this case, it will create a type-specific
validator for the type of the target instance you pass to the
Validate method. If you do not
specify a rule set name in the constructor, the validation will use
the default rule set defined for the type it is validating.
Validator pValidator = new ObjectValidator("RuleSetName");
ValidationResults valResults = pValidator.Validate(myProduct);
The validation will take into account any applicable rule
sets, and any attributes and self-validation methods found within
the target object.
Differences Between the Object Validator and the
Factory-Created Type Validators
While the two approaches you've just seen to creating or
obtaining a validator for an object achieve the same result, there
are some differences in their behavior:
-
If you do not specify a target type when you create an
Object Validator programmatically, you can use it
to validate any type. When you call the Validate method, you specify the target
instance, and the Object validator creates a type-specific
validator for the type of the target instance. In contrast, the
validator you obtain from a factory can only be used to validate
instances of the type you specify when you obtain the validator.
However, it can also be used to validate subclasses of the
specified type, but it will use the rules defined for the
specified target type.
-
The Object Validator will always use rules in
configuration for the type of the target object, and attributes
and self-validation methods within the target instance. In
contrast, you can use a specific factory class type to obtain
validators that only validate the target instance using one type
of rule source (in other words, just configuration rule sets, or
just one type of attributes).
-
The Object Validator will acquire a type-specific
validator of the appropriate type each time you call the Validate method, even if you use the same
instance of the Object validator every time. In contrast, a
validator obtained from one of the factory classes does not need
to do this, and will offer improved performance.
As you can see from the flexibility and performance advantages
listed above, you should generally consider using the ValidatorFactory approach for creating
validators to validate objects rather than creating individual Object
Validator instances.
Validating Collections of Objects
Before we leave the topic of validation of objects, it is
worth looking at how you can validate collections of objects. The
Object Collection validator can be used to check that
every object in a collection is of the specified type, and to
perform validation on every member of the collection. You can apply
the Object Collection validator to a property of a class that is a
collection of objects using a Validation block attribute if you
wish, as shown in this example that ensures that the ProductList property is a collection of
Product instances, and that every
instance in the collection contains valid values.
[ObjectCollectionValidator(typeof(Product))]
public Product[] ProductList { get; }
You can also create an Object Collection validator programmatically, and use
it to validate a collection held in a variable. The example named
Validating a Collection of Objects demonstrates
this approach. It creates a List
named productList that contains two
instances of the Product class, one
of which contains all valid values, and one that contains invalid
values for some of its properties. Next, the code creates an Object
Collection validator for the Product
type and then calls the Validate
method.
// Create an Object Collection Validator for the collection type.
Validator collValidator
= new ObjectCollectionValidator(typeof(Product));
// Validate all of the objects in the collection.
ValidationResults results = collValidator.Validate(productList);
Finally, the code displays the validation errors using the
same routine as in earlier examples. As the invalid Product instance contains the
same values as the previous example, the result is the same. You can
run the example and view the code to verify that this is the
case.