The system I'm developing uses FluentValidation (v5.0.0.1).
What I want to do is create several validators that partially validate an object, which I can then combine in other validators depending on what is required at the time.
For example, say my class has name and address. (This can't be split into a separate class like in the examples).
For scenario 1, I want to validate the name only, so I write a name validator class.
For scenario 2, I only want to validate the address, so I write an address validator class.
For scenario 3, I want to validate both the name and the address, so I want to write a new validator class that calls the name validator and then the address validator.
I don't want to repeat the code in different places, which is why I want them separate. I also don't want to use the When operator as there is no way to determine the when from the contents of the object.
I know I can call SetValidator, but how do I write the call?
RuleFor(j=>j).SetValidator(new NameValidator());
RuleFor(j=>j).SetValidator(new AddressValidator());
doesn't work.
I will explain the solution with this example. I'm going to validate this Contact entity:
public class Contact
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
}
The requirement is validate FirstName and LastName then Address1, Address2, City, PostalCode and have the posibility to reuse our validators in other entities.
Create interfaces to define what an specific entity is.
public interface IAmPerson
{
string FirstName { get; set; }
string LastName { get; set; }
}
public interface IHaveAddress
{
string Address1 { get; set; }
string Address2 { get; set; }
string City { get; set; }
string PostalCode { get; set; }
}
Now Contact entity has to implement both interfaces:
public class Contact : IAmPerson, IHaveAddress
{
public string FirstName { get; set; }
public string LastName { get; set; }
public string Address1 { get; set; }
public string Address2 { get; set; }
public string City { get; set; }
public string PostalCode { get; set; }
}
Then, create the first validator for an IAmPerson entity
public class PersonValidator : AbstractValidator<IAmPerson>
{
public PersonValidator()
{
RuleFor(data => data.FirstName).Length(3, 50).WithMessage("Invalid firstName");
RuleFor(data => data.LastName).Length(3, 50).WithMessage("Invalid LastName");
}
}
The second one for IHaveAddress entity
public class AddressValidator : AbstractValidator<IHaveAddress>
{
public AddressValidator()
{
RuleFor(data => data.Address1).NotNull().NotEmpty().WithMessage("Invalid address1");
RuleFor(data => data.Address2).NotNull().NotEmpty().WithMessage("Invalid address2");
RuleFor(data => data.City).NotNull().NotEmpty().WithMessage("Invalid City");
RuleFor(data => data.PostalCode).NotNull().NotEmpty().WithMessage("Invalid PostalCode");
}
}
Way to use your custom validators
public class ContactValidator: AbstractValidator<Contact>
{
public ContactValidator()
{
RuleFor(contact => contact).SetValidator(new PersonValidator());
RuleFor(contact => contact).SetValidator(new AddressValidator());
}
}
Now you can use your validators to validate person data or address data in any other entity. The unique thing you have to do is implement specific interfaces in the entities you are going to validate.
[UPDATE]
You can increase readability of your code by adding extension methods
public static class ValidatorExtensions
{
public static IRuleBuilderOptions<T, IHaveAddress> MustHaveAValidAddress<T>(this IRuleBuilder<T, IHaveAddress> ruleBuilder)
{
return ruleBuilder.SetValidator(new AddressValidator());
}
public static IRuleBuilderOptions<T, IAmPerson> MustBeAValidPerson<T>(this IRuleBuilder<T, IAmPerson> ruleBuilder)
{
return ruleBuilder.SetValidator(new PersonValidator());
}
}
This is the final result using the extension methods I have just added:
RuleFor(contact => contact).MustBeAValidPerson();
RuleFor(contact => contact).MustHaveAValidAddress();