验证字符串是否是有效的电子邮件地址的最优雅的代码是什么?


当前回答

这是一个老问题,但我在SO上找到的所有答案,包括最近的答案,都与这个问题类似。然而,在。net 4.5 / MVC 4中,你可以通过从System.ComponentModel中添加[EmailAddress]注释来向表单添加电子邮件地址验证。DataAnnotations,所以我想知道为什么我不能只使用。net的内置功能。

这似乎是可行的,而且在我看来是相当优雅的:

using System.ComponentModel.DataAnnotations;

class ValidateSomeEmails
{
    static void Main(string[] args)
    {
        var email = new EmailAddressAttribute();
        email.IsValid("someone@somewhere.com");         //true
        email.IsValid("someone@somewhere.co.uk");       //true
        email.IsValid("someone+tag@somewhere.net");     //true
        email.IsValid("futureTLD@somewhere.fooo");      //true
        
        email.IsValid("fdsa");                          //false
        email.IsValid("fdsa@");                         //false
        email.IsValid("fdsa@fdsa");                     //false
        email.IsValid("fdsa@fdsa.");                    //false

        //one-liner
        if (new EmailAddressAttribute().IsValid("someone@somewhere.com")) 
            return true;
    }
}

其他回答

private static bool IsValidEmail(string emailAddress)
{
    const string validEmailPattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|"
                                     + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?<!\.)\.)*)(?<!\.)"
                                     + @"@[a-z0-9][\w\.-]*[a-z0-9]\.[a-z][a-z\.]*[a-z]$";

    return new Regex(validEmailPattern, RegexOptions.IgnoreCase).IsMatch(emailAddress);
}

我根据维基百科的规则和样本地址创建了一个电子邮件地址验证程序。对于那些不介意多看一点代码的人,这里。说实话,我不知道电子邮件地址规范中有这么多疯狂的规则。我没有完全验证主机名或ipaddress,但它仍然通过了维基百科上的所有测试用例。

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace EmailValidateUnitTests
{
    [TestClass]
    public class EmailValidationUnitTests
    {
        [TestMethod]
        public void TestEmailValidate()
        {
            // Positive Assertions
            Assert.IsTrue("prettyandsimple@example.com".IsValidEmailAddress());
            Assert.IsTrue("very.common@example.com".IsValidEmailAddress());
            Assert.IsTrue("disposable.style.email.with+symbol@example.com".IsValidEmailAddress());
            Assert.IsTrue("other.email-with-dash@example.com".IsValidEmailAddress());
            Assert.IsTrue("\"much.more unusual\"@example.com".IsValidEmailAddress());
            Assert.IsTrue("\"very.unusual.@.unusual.com\"@example.com".IsValidEmailAddress()); //"very.unusual.@.unusual.com"@example.com
            Assert.IsTrue("\"very.(),:;<>[]\\\".VERY.\\\"very@\\\\ \\\"very\\\".unusual\"@strange.example.com".IsValidEmailAddress()); //"very.(),:;<>[]\".VERY.\"very@\\ \"very\".unusual"@strange.example.com
            Assert.IsTrue("admin@mailserver1".IsValidEmailAddress());
            Assert.IsTrue("#!$%&'*+-/=?^_`{}|~@example.org".IsValidEmailAddress());
            Assert.IsTrue("\"()<>[]:,;@\\\\\\\"!#$%&'*+-/=?^_`{}| ~.a\"@example.org".IsValidEmailAddress()); //"()<>[]:,;@\\\"!#$%&'*+-/=?^_`{}| ~.a"@example.org
            Assert.IsTrue("\" \"@example.org".IsValidEmailAddress()); //" "@example.org (space between the quotes)
            Assert.IsTrue("example@localhost".IsValidEmailAddress());
            Assert.IsTrue("example@s.solutions".IsValidEmailAddress());
            Assert.IsTrue("user@com".IsValidEmailAddress());
            Assert.IsTrue("user@localserver".IsValidEmailAddress());
            Assert.IsTrue("user@[IPv6:2001:db8::1]".IsValidEmailAddress());
            Assert.IsTrue("user@[192.168.2.1]".IsValidEmailAddress());
            Assert.IsTrue("(comment and stuff)joe@gmail.com".IsValidEmailAddress());
            Assert.IsTrue("joe(comment and stuff)@gmail.com".IsValidEmailAddress());
            Assert.IsTrue("joe@(comment and stuff)gmail.com".IsValidEmailAddress());
            Assert.IsTrue("joe@gmail.com(comment and stuff)".IsValidEmailAddress());

            // Failure Assertions
            Assert.IsFalse("joe(fail me)smith@gmail.com".IsValidEmailAddress());
            Assert.IsFalse("joesmith@gma(fail me)il.com".IsValidEmailAddress());
            Assert.IsFalse("joe@gmail.com(comment and stuff".IsValidEmailAddress());
            Assert.IsFalse("Abc.example.com".IsValidEmailAddress());
            Assert.IsFalse("A@b@c@example.com".IsValidEmailAddress());
            Assert.IsFalse("a\"b(c)d,e:f;g<h>i[j\\k]l@example.com".IsValidEmailAddress()); //a"b(c)d,e:f;g<h>i[j\k]l@example.com
            Assert.IsFalse("just\"not\"right@example.com".IsValidEmailAddress()); //just"not"right@example.com
            Assert.IsFalse("this is\"not\\allowed@example.com".IsValidEmailAddress()); //this is"not\allowed@example.com
            Assert.IsFalse("this\\ still\\\"not\\\\allowed@example.com".IsValidEmailAddress());//this\ still\"not\\allowed@example.com
            Assert.IsFalse("john..doe@example.com".IsValidEmailAddress());
            Assert.IsFalse("john.doe@example..com".IsValidEmailAddress());
            Assert.IsFalse(" joe@gmail.com".IsValidEmailAddress());
            Assert.IsFalse("joe@gmail.com ".IsValidEmailAddress());
        }
    }

    public static class ExtensionMethods
    {
        private const string ValidLocalPartChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!#$%&'*+-/=?^_`{|}~";
        private const string ValidQuotedLocalPartChars = "(),:;<>@[]. ";
        private const string ValidDomainPartChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-:";

        private enum EmailParseMode
        {
            BeginLocal, Local, QuotedLocalEscape, QuotedLocal, QuotedLocalEnd, LocalSplit, LocalComment,
            At,
            Domain, DomainSplit, DomainComment, BracketedDomain, BracketedDomainEnd
        };

        public static bool IsValidEmailAddress(this string s)
        {
            bool valid = true;

            bool hasLocal = false, hasDomain = false;
            int commentStart = -1, commentEnd = -1;
            var mode = EmailParseMode.BeginLocal;
            for (int i = 0; i < s.Length; i++)
            {
                char c = s[i];
                if (mode == EmailParseMode.BeginLocal || mode == EmailParseMode.LocalSplit)
                {
                    if (c == '(') { mode = EmailParseMode.LocalComment; commentStart = i; commentEnd = -1; }
                    else if (c == '"') { mode = EmailParseMode.QuotedLocal; }
                    else if (ValidLocalPartChars.IndexOf(c) >= 0) { mode = EmailParseMode.Local; hasLocal = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.LocalComment)
                {
                    if (c == ')')
                    {
                        mode = EmailParseMode.Local; commentEnd = i;
                        // comments can only be at beginning and end of parts...
                        if (commentStart != 0 && ((commentEnd + 1) < s.Length) && s[commentEnd + 1] != '@') { valid = false; break; }
                    }
                }
                else if (mode == EmailParseMode.Local)
                {
                    if (c == '.') mode = EmailParseMode.LocalSplit;
                    else if (c == '@') mode = EmailParseMode.At;
                    else if (c == '(') { mode = EmailParseMode.LocalComment; commentStart = i; commentEnd = -1; }
                    else if (ValidLocalPartChars.IndexOf(c) >= 0) { hasLocal = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.QuotedLocal)
                {
                    if (c == '"') { mode = EmailParseMode.QuotedLocalEnd; }
                    else if (c == '\\') { mode = EmailParseMode.QuotedLocalEscape; }
                    else if (ValidLocalPartChars.IndexOf(c) >= 0 || ValidQuotedLocalPartChars.IndexOf(c) >= 0) { hasLocal = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.QuotedLocalEscape)
                {
                    if (c == '"' || c == '\\') { mode = EmailParseMode.QuotedLocal; hasLocal = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.QuotedLocalEnd)
                {
                    if (c == '.') { mode = EmailParseMode.LocalSplit; }
                    else if (c == '@') mode = EmailParseMode.At;
                    else if (c == '(') { mode = EmailParseMode.LocalComment; commentStart = i; commentEnd = -1; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.At)
                {
                    if (c == '[') { mode = EmailParseMode.BracketedDomain; }
                    else if (c == '(') { mode = EmailParseMode.DomainComment; commentStart = i; commentEnd = -1; }
                    else if (ValidDomainPartChars.IndexOf(c) >= 0) { mode = EmailParseMode.Domain; hasDomain = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.DomainComment)
                {
                    if (c == ')')
                    {
                        mode = EmailParseMode.Domain;
                        commentEnd = i;
                        // comments can only be at beginning and end of parts...
                        if ((commentEnd + 1) != s.Length && (commentStart > 0) && s[commentStart - 1] != '@') { valid = false; break; }
                    }
                }
                else if (mode == EmailParseMode.DomainSplit)
                {
                    if (ValidDomainPartChars.IndexOf(c) >= 0) { mode = EmailParseMode.Domain; hasDomain = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.Domain)
                {
                    if (c == '(') { mode = EmailParseMode.DomainComment; commentStart = i; commentEnd = -1; }
                    else if (c == '.') { mode = EmailParseMode.DomainSplit; }
                    else if (ValidDomainPartChars.IndexOf(c) >= 0) { hasDomain = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.BracketedDomain)
                {
                    if (c == ']') { mode = EmailParseMode.BracketedDomainEnd; }
                    else if (c == '.' || ValidDomainPartChars.IndexOf(c) >= 0) { hasDomain = true; }
                    else { valid = false; break; }
                }
                else if (mode == EmailParseMode.BracketedDomain)
                {
                    if (c == '(') { mode = EmailParseMode.DomainComment; commentStart = i; commentEnd = -1; }
                    else { valid = false; break; }
                }
            }
            bool unfinishedComment = (commentEnd == -1 && commentStart >= 0);

            return hasLocal && hasDomain && valid && !unfinishedComment;
        }
    }
}

There are a lot of strong answers here. However, I recommend that we take a step back. @Cogwheel answers the question https://stackoverflow.com/a/1374644/388267. Nevertheless, it could be costly in a bulk validation scenario, if many of the email address being validated are invalid. I suggest that we employ a bit of logic before we enter into his try-catch block. I know that the following code could be written using RegEx but that could be costly for new developers to understand. This is my twopence worth:

    public static bool IsEmail(this string input)
    {
        if (string.IsNullOrWhiteSpace(input)) return false;

        // MUST CONTAIN ONE AND ONLY ONE @
        var atCount = input.Count(c => c == '@');
        if (atCount != 1) return false;

        // MUST CONTAIN PERIOD
        if (!input.Contains(".")) return false;

        // @ MUST OCCUR BEFORE LAST PERIOD
        var indexOfAt = input.IndexOf("@", StringComparison.Ordinal);
        var lastIndexOfPeriod = input.LastIndexOf(".", StringComparison.Ordinal);
        var atBeforeLastPeriod = lastIndexOfPeriod > indexOfAt;
        if (!atBeforeLastPeriod) return false;

        // CODE FROM COGWHEEL'S ANSWER: https://stackoverflow.com/a/1374644/388267 
        try
        {
            var addr = new System.Net.Mail.MailAddress(input);
            return addr.Address == input;
        }
        catch
        {
            return false;
        }
    }

一般来说,使用正则表达式来验证电子邮件地址并不是一件容易的事情;在撰写本文时,电子邮件地址的语法必须遵循相对较多的标准,在正则表达式中实现所有这些标准实际上是不可行的!

我强烈建议你试试我们的EmailVerify。NET是一个成熟的。NET库,它可以根据当前所有的IETF标准(RFC 1123, RFC 2821, RFC 2822, RFC 3696, RFC 4291, RFC 5321和RFC 5322)验证电子邮件地址,测试相关的DNS记录,检查目标邮箱是否可以接受消息,甚至可以判断给定的地址是否是一次性的。

免责声明:我是这个组件的主要开发人员。

Personally, I would say that you should just make sure there is an @ symbol in there, with possibly a . character. There's many regexes you could use of varying correctness, but I think most of these leave out valid email addresses, or let invalid ones through. If people want to put in a fake email address, they will put in a fake one. If you need to verify that the email address is legit, and that the person is in control of that email address, then you will need to send them an email with a special coded link so they can verify that it indeed is a real address.