C# NetCore Data Annotations to allow for [EmailAddress] or empty string?

Viewed 657

Currently, it looks like:

[EmailAddress]
public string Email { get; set; }

Currently, we are using [EmailAddress] validator data annotation for a field, but also need to allow "" (empty string) as well. If we do not allow for "" (empty string), then we will break old versions of our front-end apps.

How can I use data annotations to allow for an email address, or an empty string, for a given data field?

I understand that the front-end apps should be sending null instead of "" (empty string), and then I could just keep the field as optional, but it is too late, as old versions of the apps cannot be eradicated from the wild, and need to continue to work.

Thank you!

2 Answers

You could refer to the EmailAddress source code and try to custom EmailAddress DataTypeAttribute like below:

public class CustomEmailAddress: DataTypeAttribute
{
    const string pattern = @"^((([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+(\.([a-z]|\d|[!#\$%&'\*\+\-\/=\?\^_`{\|}~]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])+)*)|((\x22)((((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(([\x01-\x08\x0b\x0c\x0e-\x1f\x7f]|\x21|[\x23-\x5b]|[\x5d-\x7e]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(\\([\x01-\x09\x0b\x0c\x0d-\x7f]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF]))))*(((\x20|\x09)*(\x0d\x0a))?(\x20|\x09)+)?(\x22)))@((([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|\d|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.)+(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])|(([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])([a-z]|\d|-|\.|_|~|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])*([a-z]|[\u00A0-\uD7FF\uF900-\uFDCF\uFDF0-\uFFEF])))\.?$";
    const RegexOptions options = RegexOptions.Compiled | RegexOptions.IgnoreCase | RegexOptions.ExplicitCapture;
    private static Regex _regex = new Regex(pattern, options);

    public CustomEmailAddress()
        : base(DataType.EmailAddress)
    {
    }

    public override bool IsValid(object value)
    {
        if (String.IsNullOrEmpty(value.ToString()))
        {
            return true;
        }

        string valueAsString = value as string;
        // Use RegEx implementation if it has been created, otherwise use a non RegEx version.
        if (_regex != null)
        {
            return valueAsString != null && _regex.Match(valueAsString).Length > 0;
        }
        else
        {
            int atCount = 0;

            foreach (char c in valueAsString)
            {
                if (c == '@')
                {
                    atCount++;
                }
            }
            return (valueAsString != null
            && atCount == 1
            && valueAsString[0] != '@'
            && valueAsString[valueAsString.Length - 1] != '@');
        }
    }        
}

Result:

enter image description here

What I ended up using, which is surprisingly compact and easy:

public class FooBar : IValidatableObject
{
    public string Email { get; set; }

    public IEnumerable<ValidationResult> Validate(ValidationContext validationContext)
    {
        var checker = new EmailAddressAttribute();
        if (!checker.IsValid(Email) && Email != "")
        {
            yield return new ValidationResult("Some error message");
        }
    }
}

It is also very easy to test:

[Fact]
public void ShouldAcceptEmailOrEmptyString()
{
    var request = new FooBar()
    {
        Email = "",
    }

    Assert.Empty(request.Validate(new ValidationContext(request)));
}
Related