A common object-oriented technique for validation is model your validation rules as first-class objects:
- Define a common interface for validating a particular type of data
- Implement a collection of classes or functions conforming to that interface
- Loop over each function/object in this collection and invoke the validation method. The return value is either true/false or perhaps an object describing the validation failure (null if the validation rule passed). Build a list of validation failures while iterating over the rule collection
- Present the validation failures to the user in an appropriate manner
You'll see this technique in use by many libraries out there.
Example:
// the entity you want to validate
public class Person
{
public int Age { get; set; }
public string Name { get; set; }
}
public class ValidationFailure
{
public ValidationFailure(string description) { Description = description; }
public string Description { get; set; }
// perhaps add other properties here if desired
}
// note that this is generic and can be reused for any object to validate
public interface IValidationRule<TEntity>
{
ValidationFailure Test(TEntity entity);
}
public class ValidatesMaxAge : IValidationRule<Person>
{
public ValidationFailure Test(Person entity)
{
if (entity.Age > 100) return new ValidationFailure("Age is too high.");
}
}
public class ValidatesName : IValidationRule<Person>
{
public ValidationFailure Test(Person entity)
{
if (string.IsNullOrWhiteSpace(entity.Name))
return new ValidationFailure("Name is required.");
}
}
// to perform your validation
var rules = new List<IValidationRule> { new ValidatesMaxAge(), new ValidatesName() };
// test each validation rule and collect a list of failures
var failures = rules.Select(rule => rule.Test(person))
.Where(failure => failure != null);
bool isValid = !failures.Any();
Advantages of this design:
- Conforming to an interface will promote consistency in your code patterns
- One class or function per validation rule keeps your rules atomic, readable, self documenting, reusable
- Follows the single responsibility principle for the validation rule classes, and helps simplify your code that needs to perform the validation
- Reduces cyclomatic complexity (fewer nested
if statements), because it's a more object-oriented or functional approach rather than procedural
- Allows introduction of dependency injection for individual validation rule classes, which is useful if you're accessing a database or service class for validation
Edit: @Mgetz mentions input validation vs business validation, which is also an important consideration. The class-per-rule approach described above is based on a system I work with every day. We use this more for business logic validation within a service class (it's a complex enterprise system with lots of business rules), and the design serves our purposes well. The specific rules I wrote above are very simple and do look more like input validation. For input validation, I'd recommend a lighter weight approach and to keep it separate from business logic validation when appropriate.
Another implementation of this design is to have a single validator class per entity, and use something more lightweight such as lambdas for individual validation rules. This is used by the popular Fluent Validation library, for example. This is great for user input validation, as it allows for less code to do simple validation, and encourages you to keep the input validation separate from the business logic validation:
// Example using the FluentValidation library
public class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
RuleFor(p => p.Age).LessThan(100);
RuleFor(p => p.Name).NotEmpty();
}
}