ASP.NET 3.5验证中用于电子邮件格式验证的最佳正则表达式

8
我曾使用以下两个正则表达式来测试ASP.NET验证控件中的有效电子邮件表达式。我想知道从性能角度来看哪个更好,或者是否有更好的选择。
- \w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)* - ^([0-9a-zA-Z]([-\.\w]*[0-9a-zA-Z])*@([0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+[a-zA-Z]{2,9})$
我试图避免在BCL Team Blog中描述的“指数级慢表达式”问题。
更新
根据反馈,我最终创建了一个函数来测试电子邮件是否有效:
Public Function IsValidEmail(ByVal emailString As String, Optional ByVal isRequired As Boolean = False) As Boolean
    Dim emailSplit As String()
    Dim isValid As Boolean = True
    Dim localPart As String = String.Empty
    Dim domainPart As String = String.Empty
    Dim domainSplit As String()
    Dim tld As String

    If emailString.Length >= 80 Then
        isValid = False
    ElseIf emailString.Length > 0 And emailString.Length < 6 Then
        'Email is too short
        isValid = False
    ElseIf emailString.Length > 0 Then
        'Email is optional, only test value if provided
        emailSplit = emailString.Split(CChar("@"))

        If emailSplit.Count <> 2 Then
            'Only 1 @ should exist
            isValid = False
        Else
            localPart = emailSplit(0)
            domainPart = emailSplit(1)
        End If

        If isValid = False OrElse domainPart.Contains(".") = False Then
            'Needs at least 1 period after @
            isValid = False
        Else
            'Test Local-Part Length and Characters
            If localPart.Length > 64 OrElse ValidateString(localPart, ValidateTests.EmailLocalPartSafeChars) = False OrElse _
               localPart.StartsWith(".") OrElse localPart.EndsWith(".") OrElse localPart.Contains("..") Then
                isValid = False
            End If

            'Validate Domain Name Portion of email address
            If isValid = False OrElse _
               ValidateString(domainPart, ValidateTests.HostNameChars) = False OrElse _
               domainPart.StartsWith("-") OrElse domainPart.StartsWith(".") OrElse domainPart.Contains("..") Then
                isValid = False
            Else
                domainSplit = domainPart.Split(CChar("."))
                tld = domainSplit(UBound(domainSplit))

                ' Top Level Domains must be at least two characters
                If tld.Length < 2 Then
                    isValid = False
                End If
            End If
        End If
    Else
        'If no value is passed review if required
        If isRequired = True Then
            isValid = False
        Else
            isValid = True
        End If
    End If

    Return isValid
End Function

注意:

  • IsValidEmail对允许的字符比RFC更加严格,但它并没有测试所有可能的无效字符使用情况

9个回答

12
如果你想知道为什么这个问题没有引起太多的关注,那是因为在开始考虑性能之前,有许多其他问题需要解决。其中最重要的是,在验证电子邮件地址之前,你是否应该使用正则表达式--共识是不应该。这比大多数人期望的要棘手得多,可能也毫无意义。
另一个问题是你的两个正则表达式在匹配字符串类型上有很大差异。例如,第二个正则表达式在两端都有锚定,但第一个没有;它将匹配 ">>>>foo@bar.com<<<<",因为嵌入了一个类似电子邮件地址的东西。也许框架会强制正则表达式匹配整个字符串,但如果是这样,那么第二个正则表达式为什么还要锚定?
另一个区别是第一个正则表达式在整个表达式中都使用了\w,而第二个正则表达式在许多地方使用[0-9a-zA-Z]。在大多数正则表达式中,\w除了字母和数字外,还匹配下划线,但在某些正则表达式中(包括.NET),它还匹配Unicode已知的每个书写系统中的字母和数字。
还有许多其他的区别,但这是理论上的;这两个正则表达式都不是很好。请参见这里获取该主题的良好讨论和更好的正则表达式。
回到最初的问题,我认为这两个正则表达式都没有性能问题。除了在BCL博客文章中引用的嵌套量词反模式之外,您还应该注意两个或更多相邻部分的正则表达式可以匹配相同的字符集的情况--例如,

([A-Za-z]+|\w+)@

你发布的正则表达式中都没有这样的内容。由量词控制的部分总是被未量化的其他部分分割开来。两个正则表达式都会经历一些可避免的回溯,但有许多更好的理由拒绝它们,而不仅仅是性能问题。
编辑:因此,第二个正则表达式确实会遭受灾难性的回溯;在嘴上胡说之前,我应该彻底测试它。仔细看看那个正则表达式,我不明白为什么你需要第一部分的外部星号。
[0-9a-zA-Z]([-.\w]*[0-9a-zA-Z])*

这个代码块的作用是确保第一个和最后一个字符是字母数字,同时允许在它们之间插入一些其他字符。这个版本的代码也实现了同样的功能,但是当无法匹配时,它会更快地失败:

[0-9a-zA-Z][-.\w]*[0-9a-zA-Z]

这应该足以消除回溯问题,但您还可以通过使用原子组使“@”后面的部分更有效率:

(?>(?:[0-9a-zA-Z][-\w]*[0-9a-zA-Z]\.)+)[a-zA-Z]{2,9}

换句话说,如果您已经匹配了所有看起来像域组件的带有尾随点的子字符串,并且下一部分看起来不像顶级域名,则不要浪费时间回溯。您需要放弃的第一个字符是最后一个点,而您知道 [a-zA-Z]{2,9} 不会匹配它。

我一直在考虑创建一个自定义验证器,使用一个非正则表达式电子邮件测试进行服务器端检查。经过测试,我发现第二个表达式可以在JavaScript或.NET进程中(在服务器上)创建“指数级慢表达式”(给定正确的输入),其中处理会导致看似冻结的进程。 - Josh

8
我们使用这个正则表达式,它已经在内部针对150万个地址进行了测试。它可以正确识别超过98%的地址,但我知道有一些格式会导致错误。
^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$

我们还确保数据中没有EOL字符,因为EOL可能会欺骗这个正则表达式。我们的函数:

Public Function IsValidEmail(ByVal strEmail As String) As Boolean
    ' Check An eMail Address To Ensure That It Is Valid
    Const cValidEmail = "^([\w-]+(?:\.[\w-]+)*)@((?:[\w-]+\.)*\w[\w-]{0,66})\.([a-z]{2,6}(?:\.[a-z]{2})?)$"   ' 98% Of All Valid eMail Addresses
    IsValidEmail = False
    ' Take Care Of Blanks, Nulls & EOLs
    strEmail = Replace(Replace(Trim$(strEmail & " "), vbCr, ""), vbLf, "")
    ' Blank eMail Is Invalid
    If strEmail = "" Then Exit Function
    ' RegEx Test The eMail Address
    Dim regEx As New System.Text.RegularExpressions.Regex(cValidEmail)
    IsValidEmail = regEx.IsMatch(strEmail)
End Function

现在需要更新以适应更长的顶级域名。现在有许多超过6个字符的顶级域名可用。我将我的设置为20。^([\w-]+(?:.[\w-]+)*)@((?:[\w-]+.)*\w[\w-]{0,66}).([a-z]{2,20}(?:.[a-z]{2})?)$http://newgtlds.icann.org/en/program-status/delegated-strings - 2GDave
我们的数据库是电子邮件地址定义得如此简短的主要原因。该数据库最初建于90年代,与MS-Dynamics相匹配,这增加了进一步的复杂性。 - Dave

2

我是一名新手,但尝试了下面的方法后,它似乎只将"@ "符号之后的“.xxx”限制在两次或更少的出现。

^([a-zA-Z0-9]+[a-zA-Z0-9._%-]*@(?:[a-zA-Z0-9-])+(\.+[a-zA-Z]{2,4}){1,2})$

注意:由于我在R中使用这个正则表达式,所以不得不用双“\\”代替单个“\”。

1

对于服务器端验证,我发现Phil Haack的解决方案是比较好的之一。他的尝试是坚持RFC标准:

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

Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
return regex.IsMatch(emailAddress);

Details: http://blog.degree.no/2013/01/email-validation-finally-a-net-regular-expression-that-works/


1

我让微软帮我完成这项工作:

Public Function IsValidEmail(ByVal emailString As String) As Boolean
    Dim retval As Boolean = True
    Try
        Dim address As New System.Net.Mail.MailAddress(emailString)
    Catch ex As Exception
        retval = False
    End Try
    Return retval
End Function

好主意,我没有意识到地址对象具有内置验证。我的唯一担忧是您必须使用异常管理来处理正常工作流程。 - Josh
这并不起作用,我曾尝试使用电子邮件地址,其中有些是无效的(例如something@.com)。没有异常抛出,对象成功创建。这是在C#中完成的,但假设VB也是一样的。 - Andreas
Andreas,当然在VB中也是一样的。.NET框架中有一个MailAddress类,你可以从任何.NET语言中使用它。 - Concrete Gannet
FormatException是指参数不符合已知格式的异常(MSDN)。不幸的是,没有提供关于什么是可接受的或不可接受的详细信息... - Captain Sensible

1

1
你有一个正则表达式或一系列正则表达式吗? - Dave
以下是一段开头……: /([!#-'*+.-9 =?A-Z ^ -〜]{1,64} |“ [^”]{1,62}”)@ [a-zA-Z] [a-zA-Z0-9.-]{1,255}/在第一部分中,句号不能在开头或结尾。 - kzh

0

文本框:

<asp:TextBox ID="txtemail" runat="server" CssClass="form-control pantxt" Placeholder="Enter Email Address"></asp:TextBox>

必填字段验证器:

<asp:RequiredFieldValidator ID="RequiredFieldValidator9" runat="server" ControlToValidate="txtemail" ErrorMessage="Required"></asp:RequiredFieldValidator>

电子邮件验证的正则表达式:

<asp:RegularExpressionValidator ID="validateemail" runat="server" ControlToValidate="txtemail" ValidationExpression="\w+([-+.']\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*" ErrorMessage="Invalid Email"></asp:RegularExpressionValidator>

使用此正则表达式在ASP.NET中进行电子邮件验证。

0

只是为了贡献,我正在使用这个正则表达式。

^([a-zA-Z0-9]+[a-zA-Z0-9._%-]*@(?:[a-zA-Z0-9-]+\.)+[a-zA-Z]{2,4})$

它验证了hshsh@hn.cm.cm.cm.cm.cm这个错误的地址...有没有可能限制用户在@后输入超过2个点呢? - Sangram Nandkhile

0

问题在于,每个引入的域名扩展都会更改规范。

你坐在这里修改正则表达式,测试、测试、再测试。最终你得到了你“认为”是准确的,然后规范又变了......你更新你的正则表达式来适应新的要求。

然后有人输入aa@aa.aa,你做了那么多工作,结果怎么样?它符合你精心制作的正则表达式...太惨了!

你可能只需检查单个“@”和“.”就可以继续。我向你保证,如果别人不想公开他们的电子邮件,你也不会得到它。你会收到垃圾邮件或者他们从不查看并且不太关心的hotmail帐户。

我见过很多情况出了大问题,客户打电话来说他们自己的电子邮件地址因为糟糕的正则表达式检查被拒绝了。而这本来就不应该尝试的。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接