I have a domain model/entity that, depending on what how it's populated needs to be validated differently. Say I come up with 3 validators like the ones below:
public class Product1Validator : AbstractValidator<Ticket>
{
public Product1Validator()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
}
}
public class Product2Validator : AbstractValidator<Ticket>
{
public Product2Validator()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
}
}
public class Product3Validator : AbstractValidator<Ticket>
{
public Product3Validator()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
RuleFor(ticket => ticket.Policy.DistributionChannel)
.NotEmpty()
.WithMessage("Distribution Channel is missing.");
}
}
How can I refactor the repeated RuleFor(s) so that there are only one of them and are shared by different validators?
Thank you, Stephen
UPDATE
I ran with Ouarzy's idea but when I write the code to validate it won't compile.
[TestMethod]
public void CanChainRules()
{
var ticket = new Ticket();
ticket.Policy = new Policy();
ticket.Policy.ApplSignedInState = "CA";
ticket.Policy.PolicyNumber = "";
ticket.Policy.DistributionChannel = null;
var val = new Product1Validator();
var result = val.Validate(ticket); //There is no Method 'Validate'
Assert.IsTrue(!result.IsValid);
Console.WriteLine(result.Errors.GetValidationText());
}
UPDATE 2
The problem was that the new composite validators didn't inherit from AbstractValidator, once I corrected this it compiles, but they don't seem to work.
public class Product1Validator : AbstractValidator<Ticket>
{
public Product1Validator()
{
TicketValidator.Validate().Policy().ApplSignedState();
}
}
UPDATE 3
After scathing my head about the original answer and reaching out to Jeremy directly on GitHub I came up with the following:
class Program{
static void Main(string[] args){
var p = new Person();
var pv = new PersonValidator();
var vr = pv.Validate(p);
//Console.ReadKey();
}
}
class Person
{
public string FirstName { get; set; }
public string LastName { get; set; }
public DateTime BirthDate { get; set; }
}
class PersonValidator : AbstractValidator<Person>
{
public PersonValidator()
{
CascadeMode = CascadeMode.Continue;
this.FirstName();
this.LastName();
}
}
static class Extensions
{
public static void FirstName(this AbstractValidator<Person> a)
{
a.RuleFor(b => b.FirstName).NotEmpty();
}
public static void LastName(this AbstractValidator<Person> a)
{
a.RuleFor(b => b.LastName).NotEmpty();
}
}
In your case, I would probably try to build a fluent validation for the Ticket, with all the rules, and then call the required validation per product. Something like:
public class TicketValidator : AbstractValidator<Ticket>
{
public TicketValidator Policy()
{
RuleFor(ticket => ticket.Policy.PolicyNumber)
.NotEmpty()
.WithMessage("Policy Number is missing.");
return this;
}
public TicketValidator ApplSignedState()
{
RuleFor(ticket => ticket.Policy.ApplSignedInState)
.NotEmpty()
.WithMessage("Application Signed In State is missing or invalid.");
return this;
}
public TicketValidator DistributionChannel()
{
RuleFor(ticket => ticket.Policy.DistributionChannel)
.NotEmpty()
.WithMessage("Distribution Channel is missing.");
return this;
}
public static TicketValidator Validate()
{
return new TicketValidator();
}
}
And then one validator per product using the fluent syntax:
public class Product1Validator
{
public Product1Validator()
{
TicketValidator.Validate().Policy().ApplSignedState();
}
}
public class Product2Validator
{
public Product2Validator()
{
TicketValidator.Validate().Policy().ApplSignedState();
}
}
public class Product3Validator
{
public Product3Validator()
{
TicketValidator.Validate().Policy().ApplSignedState().DistributionChannel();
}
}
I didn't try to compile this code, but I hope you see the idea.
Hope it helps.