I'm trying to validate this property in MVC model, which can contain zero or more email addresses delimited by comma:
public class DashboardVM
{
public string CurrentAbuseEmails { get; set; }
...
}
The question is how do I do this using the built-in fluent validation rule for Email Address? For now I have a solution using Must and regular expression which works, but I don't find it .. elegant enough.
public DashboardVMValidator()
{
RuleFor(x => x.CurrentAbuseEmails).Must(BeValidDelimitedEmailList).WithMessage("One or more email addresses are not valid.");
}
private bool BeValidDelimitedEmailList(string delimitedEmails)
{
//... match very very long reg. expression
}
So far the closest solution including RuleFor(...).EmailAddress() was creating a custom Validator below and call Validate on each email from the string, but that didn't work for some reason (AbuseEmailValidator wasn't able to get my predicate x => x - when calling validator.Validate on each email).
public class AbuseEmailValidator : AbstractValidator<string>
{
public AbuseEmailValidator()
{
RuleFor(x => x).EmailAddress().WithMessage("Email address is not valid");
}
}
Is there way to do this in some simple manner? Something similar to this solution, but with one string instead of list of strings, as I can't use SetCollectionValidator (or can I?): How do you validate against each string in a list using Fluent Validation?
You can try something like this:
public class InvoiceValidator : AbstractValidator<ContractInvoicingEditModel>
{
public InvoiceValidator()
{
RuleFor(m => m.EmailAddressTo)
.Must(CommonValidators.CheckValidEmails).WithMessage("Some of the emails provided are not valid");
}
}
public static class CommonValidators
{
public static bool CheckValidEmails(string arg)
{
var list = arg.Split(';');
var isValid = true;
var emailValidator = new EmailValidator();
foreach (var t in list)
{
isValid = emailValidator.Validate(new EmailModel { Email = t.Trim() }).IsValid;
if (!isValid)
break;
}
return isValid;
}
}
public class EmailValidator : AbstractValidator<EmailModel>
{
public EmailValidator()
{
RuleFor(x => x.Email).EmailAddress();
}
}
public class EmailModel
{
public string Email { get; set; }
}
It seems to work fine if you use an intermediary poco. My emails are separated by ";" in this case. Hope it helps.
As of version 9, FluentValidation supports this without requiring custom validators using the Transform
and ForEach
methods.
In versions 9.0-9.4, you would write it like this:
RuleFor(x => x.List)
.Transform(list => list.Split(','))
.ForEach(itemRule => itemRule.EmailAddress());
In version 9.5 and up, RuleFor
isn't used with Transform
, so you write it like this:
Transform(x => x.List, list => list.Split(','))
.ForEach(itemRule => itemRule.EmailAddress());
To handle nulls, use null coalesce operator in the Transform
delegate:
list => (list ?? "").Split(',')
To handle whitespace, you may want to trim each item in the list. You can add a Select
clause:
list => (list ?? "").Split(',')
.Select(item => item.Trim())
If you want to ignore empty items, add a Where
clause:
list => (list ?? "").Split(',')
.Select(item => item.Trim())
.Where(item => !string.IsNullOrEmpty(item))
To require that there is at least one item in the list, add the NotEmpty
rule. So the final version 9.5+ code will look like:
Transform(x => x.List,
list => (list ?? "").Split(',')
.Select(item => item.Trim())
.Where(item => !string.IsNullOrEmpty(item)))
.NotEmpty()
.ForEach(itemRule => itemRule.EmailAddress());
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With