Class validation. | ![]() |
This section demonstrates how to make use of the class validation functionality.
Altough an object can be validated using inline object validation, class validation is the recommended way to validate (data)objects.
Using class validation gives you the opportunity to encapsulate and reuse your validation rules for your data objects.
With class validation you introduce new classes that are tailor-fit to validate a specific class.
Property validation rules are registerd within those classes and are enforced when validating an object.
These rules can be defined in the constuctor using the Fluent API.
There is no need to create a new instance of your validation class each time you validate an object. You can use a single instance in your application.
The examples require you to install the Xploration.Validation nuget package into your project or reference the Xploration.Validation.dll.
After the package is installed or the dll is referenced add a using directive to your source file.
using Xploration.Validation;
Throughout the examples an Address and Person class is used as the data objects we want to validate.
public class Address { public string AddressLine { get; set; } public string PostalCode { get; set; } public string City { get; set; } public string Country { get; set; } }
public class Person { public string Name { get; set; } public string EMail { get; set; } public int Age { get; set; } private List<string> _phoneNumbers = new List<string>(); public List<string> PhoneNumbers { get { return _phoneNumbers; } } public DateTime LastModified { get; set; } public Address HomeAddress { get; set; } }
To create a class validator you can derive from the ClassValidator class. This class requires a single type parameter which must be set to the class you want to validate.
In the constructor of the validator you can register the rules for validating the class its properties.
// Derive from ClassValidator with the generic parameter set to the class we want to validate. public class PersonValidator : ClassValidator<Person> { // You can add validation rules for the Person class in the constructor. public PersonValidator() { } }
When the validator is defined you can start to register rules to validate the properties of the class. The constructor is the recommended place to register these validation rules. This ensures that when a new instance of the validator is created all the rules are registerd. You have the following options to register a validation rule in the constructor:
Using the Fluent API is the recommended way to register property rules. It simplifies the registration and increases the readability.
For clarity both techniques are shown below so we can compare them. The first example demonstrates how to register a rule using the RegisterValidator method.
The second example demonstrates how to register the same rule using the Fluent API.
// Derive from ClassValidator with the generic parameter set to the class we want to validate. public class PersonValidator : ClassValidator<Person> { // You can add validation rules for the Person class in the constructor. public PersonValidator() { // Get the property info object for the Name property of the Person class. PropertyInfo nameProperty = typeof(Person).GetProperty(nameof(Person.Name), BindingFlags.Instance | BindingFlags.Public); // Register a rule that checks that the Name property is not null or whitespace. PropertyValidator<Person> propertyValidator = RegisterValidator(nameProperty, new NotNullOrWhiteSpaceValidator()); // Register an error entity provider that returns a message if the rule fails. propertyValidator.ErrorProvider = new ErrorMessage("A name is required", propertyValidator.Validator); } }
// Derive from ClassValidator with the generic parameter set to the class we want to validate. public class PersonValidator : ClassValidator<Person> { // You can add validation rules for the Person class in the constructor. public PersonValidator() { // Register a rule that checks that the Name property is not null or whitespace and return a custom string error message when it fails. Check((person) => person.Name).OnNotNullOrWhiteSpace().ReturnError("A name is required"); } }
Comparing both examples shows that the first example is more complex and that its harder to discover what we try to accomplish. Of course you are free to use the technique you prefer.
The Validation framework allows you to report back any error entity that is meaningful to your application. It isn't restricted to an error message, although you can certainly do that.
// Check that the Name property is not null or whitespace. Return a custom string error message. Check((person) => person.Name).OnNotNullOrWhiteSpace().ReturnError("A name is required"); // Check that the LastModified property is not in the future. Return a custom integer error code. Check((person) => person.LastModified).On((lm) => lm <= DateTime.Now).ReturnError(99);
In the above example we provide a string error entity for the OnNotNullOrWhiteSpace rule and an int error entity for the Expression rule.
The validation process will assign these error entities to the ValidationResult of the related rule if the rule failed.
So if, for example, the OnNotNullOrWhiteSpace rule fails the string "A name is required" will be assigned to the ValidationResult of that rule.
![]() |
---|
Error entities are only assigned to a ValidationResult if the rule failed. |
In the example below more rules for the properties of the Person class were added to the constructor:
// Derive from ClassValidator with the generic parameter set to Person public class PersonValidator : ClassValidator<Person> { // You can add validation rules in the constructor. public PersonValidator() { // Check that the Name property is not null or whitespace. Return a custom string error message. Check((person) => person.Name).OnNotNullOrWhiteSpace().ReturnError("A name is required"); // Conditionally(if) check the EMail property with a regular expression. Check((person) => person.EMail).OnMatches(@"^\S+@\S+$").If((c) => !string.IsNullOrEmpty(c.NewValue.EMail)); // Check that the Age property is in the range of 0..110. Check((person) => person.Age).OnMinValue(0).And().OnMaxValue(110); // Check that the LastModified property is not in the future. Return a custom integer error code. Check((person) => person.LastModified).On((lm) => lm <= DateTime.Now).ReturnError(99); } }
![]() |
---|
Providing error entities with ReturnError is not limited to string or integers types. You can provide any type you wish. |
When the rules are in place you can start using the validator by creating an instance.
// Create a PersonValidator instance. PersonValidator validator = new PersonValidator(); // Create a Person instance. Person person = new Person(); // Validate the peron. bool valid = await validator.ValidateAsync(person); // According to the rules only Name should fail. valid will become false. // Fix the Name property, no failures should occur now. valid will become true. person.Name = "Danny"; valid = await validator.ValidateAsync(person); // Set the LastModified property into the future. valid will become false. person.LastModified = DateTime.Now.AddMinutes(1); valid = await validator.ValidateAsync(person);
If you want more information on why a property isn't valid you can request the framework to report back the validation results of each rule.
You can use this information, for example, in an error recovery situation or to produce an extensive error message.
// Create a PersonValidator instance. PersonValidator validator = new PersonValidator(); // Create a Person instance. Person person = new Person(); // The Name and LastModified property will be invalid. person.Name = null; person.LastModified = DateTime.Now.AddHours(1); // Validate the person and request all the validation results. IEnumerable<ValidationResult> results = await validator.ExecuteAsync(person); // results will contain succeeded and failed validation results. foreach (ValidationResult vr in results) { // Inspect failed and succeeded results... } // CastErrors<E> returns all error entities of type E. In this case string entities. IEnumerable<string> stringErrors = results.CastErrors<string>(); // Loop through string error entities. foreach (string error in stringErrors) { // "A name is required" will be the only string error present. (Name property) } // Loop through integer error entities. foreach (int error in results.CastErrors<int>()) { // 99 will be the only integer error present. (LastModified property) }
In the above example the person object is validated with an invalid Name and LastModified property.
Instead of calling ValidateAsync(), which returns a boolean, we now call ExecuteAsync().
This call results in a collection of ValidationResult objects which we can further process.
After this the validation results are then filtered on string and integer errors using the CastErrors<> method.
![]() |
---|
The validation process returns all validation results, failed and succeeded. |
When you want to validate a property, which value is a collection of entities, you can validate the collection itself or validate each element in that collection.
If you validate the collection itself you can check on its properties, for example the amount of elements it can contain.
To validate the properties of a collection you can use the CheckCollection() statement to register a rule.
// Check that the PhoneNumbers property does not contain more then 3 phonenumbers(elements). CheckCollection((person) => person.PhoneNumbers).OnMaxElements(3);
In the above example a rule for the PhoneNumbers property is registerd using the CheckCollection() statement.
This rule will result in that the collection of the PhoneNumbers property can not contain more then 3 elements.
When you want to validate elements that are contained in a collection of a property you can use the CheckElements() statement.
Rules that are added after this statement will be applied to each element in the collection of the property.
// Check that a phonenumber contained by the PhoneNumbers property matches the regular expression. CheckElements((person) => person.PhoneNumbers).OnMatches("^[0-9()-]+$");
In the above example a rule for the PhoneNumbers property is registerd using the CheckElements() statement.
This rule will result in that each element contained by the collection of the PhoneNumbers property must match the regular expression.
When you want to validate a property, which value type is a class, you can use a ClassValidator to validate that property.
For example, the Person class contains a complex property named HomeAddress. To validate this property we can introduce a new ClassValidator for the Address class.
public class AddressValidator : ClassValidator<Address> { public AddressValidator() { // Check that the AddressLine property is not null or whitespace with a max length of 100 chars. Check((address) => address.AddressLine).OnNotNullOrWhiteSpace().And().OnMaxLength(100); // Check that the City property is not null or whitespace with a max length of 30 chars. Check((address) => address.City).OnNotNullOrWhiteSpace().And().OnMaxLength(30); // Check that the Country property is not null or whitespace with a max length of 25 chars. Check((address) => address.Country).OnNotNullOrWhiteSpace().And().OnMaxLength(25); // Check that the PostalCode matches the regular expression if the Country property is "US". Check((address) => address.PostalCode).OnMatches("^(?!0{3})[0-9]{3,5}$").If((context) => context.NewValue.Country == "US"); } }
When the address validator is in place you can add a rule to the constructor of the PersonValidator to validate the HomeAddress property.
// Check that the HomeAddress property is not null and is valid according to the AddressValidator Check((person) => person.HomeAddress).OnNotNull().And().On(new AddressValidator());
In the above example two rules are defined for the HomeAddress property:
It is also possible to validate a complex property without using a ClassValidator. To accomplish this you can use an expression.
// Check the HomeAddress with an expression if an Address is assigned to the HomeAddress property. Check((person) => person.HomeAddress).On((address) => { return !string.IsNullOrWhiteSpace(address.AddressLine); }).If((context) => context.NewValue.HomeAddress != null);
In the above example a conditional expression is used to validate the AddressLine property of the Address class.
It is also possible to validate an object without a predefined class validator by using inline validation.
The downside of this technique is that the validation rules you define are not encapsulated by a class and therefore harder to reuse.
On the other hand this technique gives you the opportunity to dynamically set up your validation rules!
// Create a data object, in this case an Address. Address home = new Address(); // Create a ClassValidator instance for the Address class. We can use this instance to add the needed rules. ClassValidator<Address> addressValidator = new ClassValidator<Address>(); // Add a rule that checks that the AddressLine property of an Address is not null or whitespace with a max length of 100 chars. addressValidator.Check((address) => address.AddressLine).OnNotNullOrWhiteSpace().And().OnMaxLength(100); // Add a rule that checks that the City property of an Address is not null or whitespace with a max length of 30 chars. addressValidator.Check((address) => address.City).OnNotNullOrWhiteSpace().And().OnMaxLength(30); // etc.. // Validate the data object. bool valid = await addressValidator.ValidateAsync(home);
As you may have noticed in the above example, with inline object validation the Fluent API is also at your disposal.