'How to validate list of email in winform C# with specific rules

Mine concern is the following. I have these 2 classes :

public class Campaign
    {
        public string name { get; set; }
        public string description { get; set; }
        public string campaignFolder { get; set; }
        public string campaignAttachment { get; set; }
        public List<Contact> from { get; set; }
        public string subject { get; set; }
        public string mailBody { get; set; }
    }

public class Contact
{
    public string name { get; set; }
    public string mail { get; set; }
}

I have a windows form with 3 names and emails associated. The first name, email couple must be filled and email valid, the second and third couples can be empty but if not they must be valid (name filled and email valid).

How can I do this with fluentvalidation ?

I try this but the validtor check for each email In the form :

private void btnOK_Click(object sender, EventArgs e)
    {
        Campaign campagne = new Campaign();
        Contact contact = new Contact();
        List<Contact> contactFrom = new List<Contact>();
        List<Contact> contactCC = new List<Contact>();

        campagne.name = tbNameCampaign.Text;
        campagne.description = tbDescriptionCampaign.Text;
        campagne.campaignFolder = tbWorkFolder.Text;

        contactFrom.Add(new Contact { name = tbNameFrom1.Text, mail = tbMailFrom1.Text });
        contactFrom.Add(new Contact { name = tbNameFrom2.Text, mail = tbMailFrom2.Text });
        contactFrom.Add(new Contact { name = tbNameFrom3.Text, mail = tbMailFrom3.Text });
        campagne.from = contactFrom;

        // ----- Validation des données saisies
        CampaignValidator campaignValidator = new CampaignValidator();
        ContactValidator contactValidator = new ContactValidator();

        ValidationResult campaignErrors = campaignValidator.Validate(campagne);
        ValidationResult contactErrors = contactValidator.Validate(contact);

        if (campaignErrors.Errors.Count > 0)
        {
            foreach (ValidationFailure failure in campaignErrors.Errors)
            {
                MessageBox.Show(failure.ErrorMessage, "Erreur de saisie");
            }
        }
        else
            MessageBox.Show("Saisie validée");
    }

For the validator class :

public class CampaignValidator : AbstractValidator<Campaign>
{
    public CampaignValidator()
    {
        RuleFor(c => c.name)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("Name is required");

        RuleFor(c => c.campaignFolder)
            .Cascade(CascadeMode.Stop)
            .NotEmpty().WithMessage("Directory is required.")
            .Must(BeAValidFolder).WithMessage("Directory doesn't exist");

        RuleForEach(x => x.from).SetValidator(new ContactValidator());
    }

    protected bool BeAValidName(string name)
    {
        name = name.Replace(" ", "");
        name = name.Replace("-", "");
        return name.All(Char.IsLetter);
    }

    protected bool BeAValidFolder(string folder)
    {
        if (!Directory.Exists(folder))
            return false;
        else
            return true;
    }
}

public class ContactValidator : AbstractValidator<Contact>
{
    public ContactValidator()
    {
        RuleFor(x => x.mail)
            .EmailAddress().WithMessage("Erreur dans la saisie de l'Email").When(x => x.mail == null);
    }

}  

Thanks for your help (and sorry for my poor english).



Solution 1:[1]

You are assigning the same validator for all the contacts:

RuleForEach(x => x.from).SetValidator(new ContactValidator());

While you want different validation logic executed for some contacts. You can split contact validators into two, the first one being more strict and requiring both email and name to be not empty and valid:

public class FirstContactValidator : AbstractValidator<Contact>
{
    public FirstContactValidator()
    {
        RuleFor(x => x.mail)
            .NotEmpty();
        RuleFor(x => x.mail)
            .EmailAddress()
            .WithMessage("Erreur dans la saisie de l'Email")
            .When(x => x.mail != null);
        RuleFor(x => x.name)
            .NotEmpty();
    }
}

and second validator more forgiving for all the subsequent contacts, allowing empty values but only if both of the fields are empty or null:

public class SubsequentContactValidator : AbstractValidator<Contact>
{
    public SubsequentContactValidator()
    {
        When(x => !string.IsNullOrEmpty(x.mail) || !string.IsNullOrEmpty(x.name), () =>
        {
            RuleFor(x => x.mail)
                .NotEmpty();
            RuleFor(x => x.mail)
                .EmailAddress()
                .WithMessage("Erreur dans la saisie de l'Email")
                .When(x => x.mail != null);
            RuleFor(x => x.name)
                .NotEmpty();
        });
    }
}

Finally, the campaign validator that assigns contact validators in order you need. Note that I left for you to add some validation check against the amount of contacts. Consider if you want to return an error if there are not enough contacts present in campaign.

public class CampaignValidator : AbstractValidator<Campaign>
{
    public CampaignValidator()
    {
        // Other validation rules...

        // TODO: What if 'from' does not have 3 contacts?

        When(x => x.from?.Count >= 3, () =>
        {
            RuleFor(y => y.from[0])
                .SetValidator(new FirstContactValidator());
            RuleFor(y => y.from[1])
                .SetValidator(new SubsequentContactValidator());
            RuleFor(y => y.from[2])
                .SetValidator(new SubsequentContactValidator());
        });
    }
}

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Prolog