Search code examples
c#regexvalidationemailcsv

Regex to validate a semi-colon separated list of emails?


I have the following c# extension method for validating an email address. The regex came from Microsoft on their 'How to: Verify that Strings Are in Valid Email Format' page.

I need to improve this method to be able to handle a semi-colon separated list of emails. A valid example string could be as badly formatted as: ";; ; ; xxx.sss.xxx ; ;; xxx.sss.xxx;"

    /// <summary>
    /// Validates the string is an Email Address...
    /// </summary>
    /// <param name="emailAddress"></param>
    /// <returns>bool</returns>
    public static bool IsValidEmailAddress(this string emailAddress)
    {
        var valid = true;
        var isnotblank = false;

        var email = emailAddress.Trim();
        if (email.Length > 0)
        {
            isnotblank = true;
            valid = Regex.IsMatch(email, @"^(?("")("".+?(?<!\\)""@)|(([0-9a-z]((\.(?!\.))|[-!#\$%&'\*\+/=\?\^`\{\}\|~\w])*)(?<=[0-9a-z])@))" +
            @"(?(\[)(\[(\d{1,3}\.){3}\d{1,3}\])|(([0-9a-z][-\w]*[0-9a-z]*\.)+[a-z0-9][\-a-z0-9]{0,22}[a-z0-9]))$", RegexOptions.IgnoreCase);
        }

        return (valid && isnotblank);
    }

Solution

  • Microsoft's regex does a pretty good job. However, it doesn't catch a few strange scenarios and a number of special characters which are valid for email. I'll give you a different regex. Choose to use it or not is your prerogative.

    I would separate the concerns by having one extension method which validates an email address and another which validates the list. Do a .trim() on each email before passing it to the email validation method. So, something like this:

        /// <summary>
        /// Validates the string is an Email Address...
        /// </summary>
        /// <param name="emailAddress"></param>
        /// <returns>bool</returns>
        public static bool IsValidEmailAddress(this string emailAddress)
        {
            var valid = true;
            var isnotblank = false;
    
            var email = emailAddress.Trim();
            if (email.Length > 0)
            {
                // Email Address Cannot start with period.
                // Name portion must be at least one character
                // In the Name, valid characters are:  a-z 0-9 ! # _ % & ' " = ` { } ~ - + * ? ^ | / $
                // Cannot have period immediately before @ sign.
                // Cannot have two @ symbols
                // In the domain, valid characters are: a-z 0-9 - .
                // Domain cannot start with a period or dash
                // Domain name must be 2 characters.. not more than 256 characters
                // Domain cannot end with a period or dash.
                // Domain must contain a period
                isnotblank = true;
                valid = Regex.IsMatch(email, @"\A([\w!#%&'""=`{}~\.\-\+\*\?\^\|\/\$])+@{1}\w+([-.]\w+)*\.\w+([-.]\w+)*\z", RegexOptions.IgnoreCase) &&
                    !email.StartsWith("-") &&
                    !email.StartsWith(".") &&
                    !email.EndsWith(".") && 
                    !email.Contains("..") &&
                    !email.Contains(".@") &&
                    !email.Contains("@.");
            }
    
            return (valid && isnotblank);
        }
    
        /// <summary>
        /// Validates the string is an Email Address or a delimited string of email addresses...
        /// </summary>
        /// <param name="emailAddress"></param>
        /// <returns>bool</returns>
        public static bool IsValidEmailAddressDelimitedList(this string emailAddress, char delimiter = ';')
        {
            var valid = true;
            var isnotblank = false;
    
            string[] emails = emailAddress.Split(delimiter);
    
            foreach (string e in emails)
            {
                var email = e.Trim();
                if (email.Length > 0 && valid) // if valid == false, no reason to continue checking
                {
                    isnotblank = true;
                    if (!email.IsValidEmailAddress())
                    {
                        valid = false;
                    }
                }
            }
            return (valid && isnotblank);
        }