Is there an extension in FluentValidation or any other way to defer the selection of the child validator depending on the type / value of the property being checked?
My situation is that I have a Notification class that I want to check. This class has a Payload property, which can be one of several types of payloads, for example. SmsPayload, EmailPayload, etc. Each of these Payload subclasses has its own associated validator, for example. SmsPayloadValidator and EmailPayloadValidator respectively. In addition to the above, there are no links from the main library (s) to individual notification providers. Essentially, this means that I can add providers as needed and connect everything using IoC.
Consider the following classes:
public class Notification { public Payload Payload { get; set; } public IEnumerable<string> Details { get; set; } } public abstract class Payload { public string Message { get; set; } public abstract string Type { get; } } public class SmsPayload : Payload { public List<string> Numbers { get; set; } public string Region { get; set; } public string Provider { get; set; } }
There is a notification validator and SmsPayloadValidator as follows:
public class NotificationValidator : AbstractValidator<Notification> { public NotificationValidator(IValidator<Payload> payloadValidator) { RuleFor(notification => notification.Payload).NotNull().WithMessage("Payload cannot be null."); RuleFor(notification => notification.Payload).SetValidator(payloadValidator); } } public class SmsPayloadValidator : AbstractValidator<SmsPayload> { public SmsPayloadValidator() { RuleFor(payload => payload.Provider) .Must(s => !string.IsNullOrEmpty(s)) .WithMessage("Provider is required."); RuleFor(payload => payload.Numbers) .Must(list => list != null && list.Any()) .WithMessage("Sms has no phone numbers specified."); RuleFor(payload => payload.Region) .Must(s => !string.IsNullOrEmpty(s)) .WithMessage("Region is required."); } }
As I mentioned the assembly, where NotificationValidator does not reference the assemblies in which the individual payload validator classes live. All wiring was taken care of by Ioc (Simple-Injector for this project).
Basically, I want to do something like the following - by first registering the factory callback in Simple Injector:
container.Register<Func<Payload, IValidator<Payload>>>(() => (payload => { if (payload.GetType() == typeof(SmsPayload)) { return container.GetInstance<ISmsPayloadValidator>(); } else if (payload.GetType() == typeof(EmailPayload)) { return container.GetInstance<IEmailPayloadValidator>(); } else {
Thus, I can select the appropriate validator as follows:
public class NotificationValidator : AbstractValidator<Notification> { public NotificationValidator(Func<Payload, IValidator<Payload>> factory) { RuleFor(notification => notification.Payload).NotNull().WithMessage("Payload cannot be null."); RuleFor(notification => notification.Payload).SetValidator(payload => factory.Invoke(payload)); } }
Any suggestions? or is there a better way to do what i suggest? If not, I will open the FluentValidation repository and submit the PR.