Search code examples
c#asp.net.netasp.net-web-apifluentvalidation

FluentValidation - Validation of strings inside a list


I'm trying to create a validation to check the e-mail address in two lists. the email address cannot be duplicated in the CcList and in the BccList.

I have this Model:

public class EmailEnforceListingGroup : EnforceListingGroup
{
    public string TemplateId { get; set; }

    public string ToEmail { get; set; }

    public string Subject { get; set; }

    public string Body { get; set; }

    public string CcEmail { get; set; }

    public string BccEmail { get; set; }

    public IList<EnforceFileAttachment> Attachments { get; set; }

    public IList<string> CcEmailList => !CcEmail.IsNullOrEmpty() ? CcEmail.Split(',', ';').ToList() : new List<string>();

    public IList<string> BccEmailList => !BccEmail.IsNullOrEmpty() ? BccEmail.Split(',', ';').ToList() : new List<string>();

    public EmailEnforceListingGroup()
    {
        Attachments = new List<EnforceFileAttachment>();
    }
}

And I created this validator:

public class EmailEnforceListingGroupValidator : AbstractValidator<EmailEnforceListingGroup>
{
    public EmailEnforceListingGroupValidator()
    {
        RuleFor(x => x.TemplateId)
            .NotEmpty();

        RuleFor(x => x.ToEmail)
            .NotEmpty()
            .EmailAddress();

        RuleFor(x => x.Subject)
            .NotEmpty();

        RuleFor(x => x.Body)
            .NotEmpty();

        RuleForEach(x => x.CcEmailList)
            .EmailAddress();

        RuleForEach(x => x.BccEmailList)
            .EmailAddress();

        RuleFor(x => x)
            .Must(emailEnforce => !IsDuplicated(emailEnforce.ToEmail, emailEnforce.CcEmailList.ToList()))
            .WithMessage("The field [Cc] contains an Email Address that already exists in the [To] field.");

        RuleFor(x => x)
            .Must(emailEnforce => !IsDuplicated(emailEnforce.ToEmail, emailEnforce.BccEmailList.ToList()))
            .WithMessage("The field [Bcc] contains an Email Address that already exists in the [To] field.");

        RuleFor(emailEnforce =>
            RuleForEach(eachEmail => eachEmail.CcEmailList)
                .Must(email => !IsDuplicated(email, emailEnforce.BccEmailList.ToList()))
                .WithMessage("The field [Bcc] contains an Email Address that already exists in the [Cc] field."));
    }

    private bool IsDuplicated(string email, List<string> listToCheck)
    {
        return listToCheck.Any(x => x.Equals(email));
    }
}

Use a RuleForEach and SetValidator to a new custom validator is a possible way but I don't know how to send the BccList together each CcEmail to see if it exists in another list.

The two validations using RuleFor and Must are working fine but the validator using RuleFor, RuleForEach and Must is not catching any error.

EDITED:

I solved the problem using this approach

RuleForEach(emailEnforce => emailEnforce.CcEmailList)
            .Must((emailEnforce, email) => !IsDuplicated(email, emailEnforce.BccEmailList.ToList()))
            .WithMessage(
                "The field [Bcc] contains an Email Address ({PropertyValue}) that already exists in the [Cc] field.");

The explanation is in the link bellow

https://stackoverflow.com/a/20546097/10265429


Solution

  • I haven't had a chance to test this code, but it should be something like this. If you look at the FV Custom Validators page (https://fluentvalidation.net/custom-validators), you will see that you can access the root object in your custom function, thus the first param to my BeUniqueEmail() is the root object and then you can access any other property via the model. So you can get access to your BccList and it's easy to do the compare. The email string param is just each email in the CcEmailList coming from the RuleForEach. There should be no reason to nest the RuleForEach inside of a RuleFor.

    RuleForEach(x => x.CcEmailList )
        .BeUniqueEmail()
        .WithMessage("The field [Bcc] contains an Email Address that already exists in the [Cc] field.");
    
    
    private bool BeUniqueEmail(EmailEnforceListingGroup model, string email)
    {
        return model.BccEmailList.Any(x => x.Equals(email));
    }