Using FluentValidation to validate types with multiple interfaces

I was recently asked on the FluentValidation forum whether it’s possible to re-use validation rules for interface properties across multiple types that implement that interface.

For example, imagine you a Customer class that implements two interfaces – IPerson and ICustomer.

public interface IPerson {
	string Name { get; set; }
}
 
public interface ICustomer {
	string CompanyName { get; set; }
}
 
public class Customer : IPerson, ICustomer {
	public decimal Discount { get; set; }
	public string Name { get; set; }
	public string CompanyName { get; set; }
}

The aim here is to be able to define rules for properties on IPerson and then re-use them for both the Manager and Customer validators.

Option 1: Inheritance

The first option is to define a base validator, generic validator constrained to the interface type:

public abstract class PersonValidatorBase<T> : AbstractValidtor<T> where T : IPerson {
   public PersonValidatorBase() {
      RuleFor(x => x.Name).NotNull();
   }
}

…then, it’s just a case of inheriting from this validator and closing the generic type:

public class CustomerValidator : PersonValidatorBase<Customer> {
   public CustomerValidator() {
     RuleFor(x => x.Discount).GreaterThan(0);
   }
}

Rather than inheriting from AbstractValidator, the CustomerValidator instead inherits from PersonValidatorBase and supplies the generic type. This way, the CustomerValidator will inherit all the rules defined in PersonValidatorBase.

Although this works for a single interface, it doesn’t solve our particular problem as we also want to be able to define and re-use the rules for the ICustomer interface too.

Option 2: A Composite Validator

A better option would be to define some sort of composite validator that can be used to compose multiple validators together at runtime. To do this, we can create a new class (CompositeValidator) that inherits from AbstractValidator and also provides a way to register additional validators:

public abstract class CompositeValidator<T> : AbstractValidator<T> {
	private List<IValidator> otherValidators = new List<IValidator>();
 
	protected void RegisterBaseValidator<TBase>(IValidator<TBase> validator) 
	{
		// Ensure that we've registered a compatible validator. 
		if(validator.CanValidateInstancesOfType(typeof(T))) {
			otherValidators.Add(validator);
		}
		else {
			throw new NotSupportedException(string.Format("Type {0} is not a base-class or interface implemented by {1}.", typeof(TBase).Name, typeof(T).Name));
		}
 
	}
 
 
	public override ValidationResult Validate(ValidationContext<T> context) {
		var mainErrors = base.Validate(context).Errors;
		var errorsFromOtherValidators = otherValidators.SelectMany(x => x.Validate(context).Errors);
		var combinedErrors = mainErrors.Concat(errorsFromOtherValidators);
 
		return new ValidationResult(combinedErrors);
	}
}

The CompositeValidator has a RegisterBaseValidator method that allows you to register an additional validator for a base class or interface. We also override the Validate method to merge the validation failures for the additional validators with the main validator’s failures.

We can now declare validators for IPerson and ICustomer in the normal way:

public class PersonInterfaceValidator:AbstractValidator<IPerson> {
	public PersonInterfaceValidator() {
		RuleFor(x => x.Name).NotNull();
	}
}
 
public class CustomerInterfaceValidator : AbstractValidator<ICustomer> {
	public CustomerInterfaceValidator() {
		RuleFor(x => x.CompanyName).NotNull();
	}
}

…and the CustomerValidator can be defined as inheriting from CompositeValidator:

public class CustomerValidator : CompositeValidator<Customer> {
	public CustomerValidator() {
		RegisterBaseValidator(new PersonInterfaceValidator());
		RegisterBaseValidator(new CustomerInterfaceValidator());
 
		RuleFor(x => x.Discount).GreaterThan(0);
	}
}

Note that the CustomerValidator includes the rules for the interface validators as well as defining its own rules. The CustomerValidator can now be used in the normal way:

var validator = new CustomerValidator();
var result = validator.Validate(new Customer());
Written on January 13, 2011