65.9K
CodeProject 正在变化。 阅读更多。
Home

使用 C# 和 .NET 框架有效验证电话号码

emptyStarIconemptyStarIconemptyStarIconemptyStarIconemptyStarIcon

0/5 (0投票)

2018 年 9 月 24 日

CPOL
viewsIcon

24401

本文介绍如何将 libphonenumber-csharp 集成到您的 .NET 项目中,并轻松利用其强大的功能。

验证用户输入对于任何软件应用程序的安全和正常运行至关重要。对于电话号码这类通常是私有数据的数据尤其如此,它们用于实现应用程序功能(如消息传递)和安全功能(如双因素身份验证)。

应用程序开发框架,包括 .NET Framework 和 .NET Core,提供了数据验证功能,以便更轻松地更健壮地处理标准数据类型。虽然 .NET 框架提供了电话号码验证功能,但其功能有限,尤其是在国际化(“i18n”)方面。

幸运的是,有一个开源库 libphonenumber-csharp,它提供了丰富的资源来验证和操作各种类型的电话号码。它源自 Google 创建的一个开源库。本文介绍如何将 libphonenumber-csharp 集成到您的 .NET 项目中,并轻松利用其强大的功能。

.NET 数据验证

使用 C# 和 .NET 框架构建的软件中的一个常见设计模式是使用类对象来存储数据。这是使用模型视图控制器 (MVC) 库的 Web 应用程序中的标准方法。 .NET 框架提供了将数据验证抽象到验证属性的功能,这些属性用于“装饰”实体类的属性。

在下面的示例中,[Required][StringLength(100)] 是用于“装饰”MovieTitle 属性的验证属性。

public class Movie
{
   public int Id { get; set; }
	[Required]
   [StringLength(100)]
   public string Title { get; set; }
// Additional properties and methods of the Movie class
}

验证属性以及许多其他数据注解都位于

System.ComponentModel.DataAnnotations 命名空间中,该命名空间在 .NET Framework 和 .NET Core 中都可用。有一些标准的验证属性可用于许多常见数据类型,例如日期和电子邮件地址。

System.Component.DataAnnotations 中的电话号码验证

DataAnnotations 命名空间提供了两种验证电话号码作为数据类型的方法:枚举和派生类。当您的电话号码字段是简单类型时,请使用枚举方法;当字段是对象类型时,请使用派生类。

枚举格式

以下是电话号码属性作为简单字符串的示例,使用了枚举验证方法

[DataType(DataType.PhoneNumber)]
public string MobilePhone { get; set; }

DataType.PhoneNumber 枚举为 DataTypeAttribute 类提供了要验证的数据的具体类型,该类型来自类支持的标准数据类型列表。因为它是一个枚举,所以您获得了 DataTypeAttribute 类的标准功能。

派生类格式

这种方法将一个类作为验证属性而不是枚举应用。Phone 类派生自 DataTypeAttribute 类,并且可以被重写和扩展以提供额外的验证行为。

[Phone]
public string MobilePhone { get; set; }

您还可以修改 Phone 类的功能,并使用它来验证对象属性

[Phone]
public Landline WorkPhone { get; set; }

常见用法和最佳实践

在 C# 代码中,[Phone][DataType(DataType.PhoneNumber)] 经常被互换使用,由于第一种用法更简短,许多开发人员习惯性地选择它。虽然在绝大多数情况下这两种方法都会以相同的方式工作,但派生类格式会使您的代码在后台更复杂,并可能引入 bug,因此最好使用枚举格式,除非您的需求需要派生类。

限制

这两种格式都被文档记录为使用正则表达式执行验证,但文档本身对使用的正则表达式(们)保持沉默。通过检查 GitHub 上的源代码,可以更好地理解这个类是如何工作的。

通读代码会发现许多可能对稳健电话号码验证带来担忧的因素

  • 电话号码允许使用数字和字符“- .()”以及标记为“ext.”、“ext”或“x”的扩展名来表示。电话号码有时包含字母字符。例如,“1 (800) LOAN-YES”和“1 (800) MICROSOFT”在美国都可以作为电话号码拨打。
  • 没有检查提交的电话号码的长度。
  • 没有检查以确定潜在电话号码在特定国家/地区是否有效。

总的来说,更准确的说法可能是 PhoneAttribute.cs 确定提交的号码是否“像电话号码”。这没关系,因为有现成的代码可以执行更广泛的电话号码验证。

libphonenumber-csharp

Google 在其云应用程序、业务运营以及 Android、Chrome 和(可能)Fuchsia 操作系统中广泛使用电话号码。在所有这些软件系统中,有效处理电话号码对于安全性和易用性至关重要。

为了帮助 Android 和其他应用程序开发人员更轻松地回答“这个电话号码是否有效?”这个问题,Google 开发并维护了一个用于“解析、格式化和验证国际电话号码”的开源 Java、C++ 和 JavaScript 库,名为 libphonenumber

这很好,但对于在 ASP.NET 或 ASP.NET Core 中进行服务器端数据验证(这绝对是一件好事)的 C# 开发人员来说并不方便。

幸运的是,Tom Clegg 已将 libphonenumber 移植到 C#,即 libphonenumber-csharp,因此 .NET 开发人员可以在其应用程序中方便地集成 libphonenumber。它可以通过 NuGet 包获得,这使得添加到任何项目都变得容易。

必读

在将 libphonenumber-csharp 集成到 .NET 项目并开始使用之前,最好先查阅一些文档。为了了解电话号码的怪异之处和不确定性,请阅读一些 libphonenumber 文档,从

程序员对电话号码的错误认识

您可能会对您之前一直安然不知的电话号码的复杂程度感到震惊。现在您知道了,您有很多理由将 libphonenumber-csharp 添加到您的项目中。

另外,请务必查看 FAQlibphonenumberReadme。您将在这里学到的功能比在 libphonenumber-csharp 仓库中的文档中学到的要多。还可以查看 Readme 中的快速示例以了解用法概览。

libphonenumber-csharp 添加到 ASP.NET Core MVC 项目

尝试使用 libphonenumber-csharp 非常容易。让我们一步一步地介绍将其添加到 ASP.NET Core MVC 项目进行服务器端验证的过程。(另一篇文章将介绍使用 JavaScript 版的 libphonenumber 进行客户端验证,但请记住 MVC 的高级模型状态验证使您能够为用户提供良好的反馈,这是服务器端验证的结果。)

为了在您迷路时提供帮助,或者想将您的代码与参考实现进行比较,GitHub 上有一个可运行的示例版本。 BlipPhone 项目是一个简单的 ASP.NET Core 2.1 Web 应用程序,其中包含此库。您可以在 Visual Studio 2017 中运行它。

创建默认项目

首先,创建一个 ASP.NET Core Web 应用程序项目,命名为 PhoneCheck,选择 Web 应用程序(模型-视图-控制器)模板。对于我们的演示项目,添加身份验证不是必需的,但创建一个源代码存储库是一个好主意,这样您就可以跟踪和回滚您的更改。您可以指定应用程序内数据存储,但在本教程中我们不会使用数据库来保存数据。

安装 libphonenumber-csharp

将 libphonenumber-csharp 添加到您的项目非常简单。首先,请查看 NuGet.org 以获取最新的版本信息。

然后,在“程序包管理器控制台”窗口中输入以下命令,并根据需要替换当前版本信息

PM> Install-Package libphonenumber-csharp -Version 8.9.10

或者,在 PowerShell 窗口中输入以下 .NET 命令行。请确保当前目录是项目目录

dotnet add package libphonenumber-csharp --version 8.9.10

创建 ViewModel

我们将使用一个视图模型来存储在 HTML 页面上显示给用户和从用户收集的数据。视图模型包含用于电话号码的签发国家/地区和要检查的电话号码的字段。

在项目的 Models 文件夹中,创建一个类文件 PhoneNumberCheckViewModel.cs

BlipPhone 示例项目包含用于使用国家/地区列表填充下拉列表字段并在视图模型中返回两位 ISO 国家/地区代码的代码。您可以使用示例中提供的代码,或者将 CountryCodeSelected 字段用作纯文本字段并手动输入国家/地区代码。

PhoneNumberCheckViewModel.cs 文件如下

using System.ComponentModel.DataAnnotations;
namespace PhoneCheck.Models
{
   public class PhoneNumberCheckViewModel
   {
       private string _countryCodeSelected;
[Required]
       [Display(Name = "Issuing Country")]
       public string CountryCodeSelected
       {
           get => _countryCodeSelected;
           set => _countryCodeSelected = value.ToUpperInvariant();
       }
[Required]
       [Display(Name = "Number to Check")]
       public string PhoneNumberRaw { get; set; }
// Holds the validation response. Not for data entry.
[Display(Name = "Valid Number")]
       public bool Valid { get; set; }
// Holds the validation response. Not for data entry.
[Display(Name = "Has Extension")]
       public bool HasExtension { get; set; }
// Optionally, add more fields here for returning data to the user.
   }
}

请注意,数据属性用于使这些输入字段成为必需字段,并设置其标签元素的值。

创建视图

Views 文件夹中,创建一个 Phone 子文件夹,然后添加一个名为 Check 的新视图,使用“创建”模板和 PhoneNumberCheckViewModel.cs 视图模型。当 MVC 工具完成新视图的脚手架后,Check.cshtml 文件中为 PhoneNumberRaw 字段生成的 Razor 标记应如下所示。

在接下来的文件摘录中,省略号(“...”)用于表示为简洁而省略的部分。

...
    <span asp-validation-for="CountryCodeSelected" class="text-danger"></span>
</div>
<div class="form-group">
    <label asp-for="PhoneNumberRaw" class="control-label"></label>
    <input asp-for="PhoneNumberRaw" class="form-control" />
    <span asp-validation-for="PhoneNumberRaw" class="text-danger"></span>
</div>
<div class="form-group">
    <div class="checkbox">
    <label>
...

如下编辑视图:在 <form> 元素中,更改第一个 <div> 元素,使 asp-validation-attribute 如下所示

<div asp-validation-summary="All" class="text-danger"></div>

这将启用我们稍后将看到的错误消息。

在运行时,ASP.NET Core MVC 会将视图模型和视图结合使用来提供 HTML 代码。在浏览器中,https://:44383/Phone/Check 网页中 PhoneNumberRaw 字段的 HTML 如下所示

...
    <span class="text-danger field-validation-valid" data-valmsg-for="CountryCodeSelected" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
    <label class="control-label" for="PhoneNumberRaw">Number to Check</label>
    <input class="form-control" type="text" data-val="true" data-val-required="The Number to Check field is required." id="PhoneNumberRaw" name="PhoneNumberRaw" value="" />
    <span class="text-danger field-validation-valid" data-valmsg-for="PhoneNumberRaw" data-valmsg-replace="true"></span>
</div>
<div class="form-group">
    <div class="checkbox">
    <label>
...

请注意,<input> 字段会自动标记为必需,并且数据验证属性已连接。

创建控制器

电话号码验证将在控制器中进行,并将使用 ModelState 对象以及视图模型中的字段来向用户提供验证和错误信息。

首先,使用默认工具在项目的 Controllers 文件夹中创建一个新控制器:PhoneController

PhoneNumberModels 命名空间添加到 PhoneController.cs 文件

using PhoneNumbers;
using PhoneCheck.Models;

为电话号码实用工具类创建一个私有成员变量,并在控制器构造函数中创建一个实用工具类实例

namespace PhoneCheck.Controllers
{
   public class PhoneController : Controller
   {
       private static PhoneNumberUtil _phoneUtil;
       
       public PhoneController()
       {
           _phoneUtil = PhoneNumberUtil.GetInstance();
       }
...

请注意,新创建的 PhoneNumberUtil 实例是通过 GetInstance() 方法创建的,而不是 C# 中典型的 class instance = new class(); 语法来从类构造函数创建实例。使用 GetInstance() 方法是因为 libphonenumber-csharp 是从原始 Java 库移植过来的。

更改默认操作的名称

默认情况下,工具会为空控制器创建一个 Index() 方法。将该方法名称更改为 Check

public IActionResult Check()
{
    return View();
}

创建 HttpPost 操作方法

控制器操作方法接受 MVC 中间件从 HTML 表单返回的视图模型,并验证视图模型的防伪令牌和模型状态。如果这些检查通过,我们就可以使用构造函数中创建的 PhoneNumberUtil 实例来进行电话号码验证和操作。

在默认操作(上方)下方,为 HttpPost 操作输入以下代码

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Check(PhoneNumberCheckViewModel model)
{
    if (model == null)
    {
        throw new ArgumentNullException(nameof(model));
    }
if (ModelState.IsValid)
    {
        try
        {
            // Parse the number to check into a PhoneNumber object.
            PhoneNumber phoneNumber = _phoneUtil.Parse(model.PhoneNumberRaw,
model.CountryCodeSelected);            ModelState.FirstOrDefault(x => x.Key == nameof(model.Valid)).Value.RawValue =                _phoneUtil.IsValidNumberForRegion(phoneNumber, model.CountryCodeSelected);            ModelState.FirstOrDefault(x => x.Key == nameof(model.HasExtension)).Value.RawValue                = phoneNumber.HasExtension;            return View(model);        }        catch (NumberParseException npex)        {            ModelState.AddModelError(npex.ErrorType.ToString(), npex.Message);        }    }    ModelState.SetModelValue(nameof(model.CountryCodeSelected),        model.CountryCodeSelected, model.CountryCodeSelected);    ModelState.SetModelValue(nameof(model.PhoneNumberRaw),        model.PhoneNumberRaw, model.PhoneNumberRaw); ModelState.SetModelValue(nameof(model.Valid), false, null);
    model.Valid = false;
ModelState.SetModelValue(nameof(model.HasExtension), false, null);    model.HasExtension = false;
return View(model);
}

让我们按顺序查看代码的特定部分进行解析。

请注意,try...catch 块对于处理电话号码错误很重要,所以不要跳过它。

当电话号码被提交到控制器进行服务器端验证时,第一步是将它们解析为 PhoneNumber 对象。请注意,创建电话号码至少需要两个必需的参数

model.PhoneNumberRaw:要解析的原始号码,即用户输入的内容,以及

model.CountryCodeSelected:分配号码的国家/地区。

Try
{
    // Parse the number to check into a PhoneNumber object.
    PhoneNumber phoneNumber = _phoneUtil.Parse(model.PhoneNumberRaw,
        model.CountryCodeSelected);

一旦有了电话号码对象,您就可以使用 PhoneNumberUtil 实例来确定有关它的各种信息

  • 它在该国家/地区是有效号码吗?
  • 这是什么类型的电话号码?(这可能产生一个以上的结果。)
  • 从其他国家的手机拨打的格式是什么?

通过将我们刚刚创建的 phoneNumber 对象和 model.CountryCodeSelected 传递给 _phoneUtil 对象的 IsValidNumberForRegion 方法来检查号码是否有效。

由于我们将结果返回到同一个 HTML 页面,因此直接在 ASP.NET Core 中设置模型属性不起作用,因此我们将使用 ModelState 对象来返回 IsValidNumberForRegion 方法的结果。

ModelState.FirstOrDefault(x => x.Key == nameof(model.Valid)).Value.RawValue =
    _phoneUtil.IsValidNumberForRegion(phoneNumber, model.CountryCodeSelected);

还可以从 PhoneNumber 对象本身获取信息,例如

  • 它是否包含分机号?
  • 与该号码关联的国家/地区代码是什么?

在我们的示例中,我们将检查电话号码是否带有分机号。

ModelState.FirstOrDefault(x => x.Key == nameof(model.HasExtension)).Value.RawValue =
    phoneNumber.HasExtension;

如果 try 块成功完成,我们将更新后的视图模型返回到视图。

return View(model);

PhoneNumberUtil 遇到错误(例如,原始电话号码过长)时,它将引发 NumberParseException。在 ASP.NET MVC 中,您可以捕获这些错误并将它们添加到 ModelState 中,然后可以使用 ModelState 向用户显示错误消息。

catch (NumberParseException npex)
{
   ModelState.AddModelError(npex.ErrorType.ToString(), npex.Message);
}

请注意,要显示在 ModelState 级别声明的错误,而不是在模型中单个字段级别声明的错误,Check.cshtml 视图中的验证摘要必须设置为显示所有错误,而不仅仅是模型中的字段级别错误。这就是我们在创建视图时所做的更改。

如果 ModelState.IsValid 测试为 false,我们需要在返回之前重置表单上的值,因此在 if 块之后添加以下行以完成 HttpPost 操作方法。

ModelState.SetModelValue(nameof(model.CountryCodeSelected), 
    model.CountryCodeSelected, model.CountryCodeSelected);
ModelState.SetModelValue(nameof(model.PhoneNumberRaw), 
    model.PhoneNumberRaw, model.PhoneNumberRaw);
ModelState.SetModelValue(nameof(model.Valid), false, null);
    model.Valid = false;
ModelState.SetModelValue(nameof(model.HasExtension), false, null);
    model.HasExtension = false;
return View(model);

您可以使用此模式对电话号码执行各种检查和转换,包括将号码格式化为从世界各地的手机和固定电话进行国际拨打。如果您想使用 Controller 中的 PhoneNumber 对象或 PhoneNumberUtil 类进行其他检查,您可以将它们编写为输出或向视图模型和视图添加更多字段。 BlipPhone 示例项目显示了一些常用的附加字段。

向视图添加链接

为了方便起见,您可以使用以下 Razor 代码在主页或顶部导航栏上添加指向 /Phone/Check 页面的链接。

<a asp-controller="Phone" asp-action="Check">Check phone numbers</a>

将其放在您觉得最方便的位置。

探索

项目现在应该可以运行了。如果您遇到困难,可以参考 BlipPhone 示例项目寻求指导。在您构建的项目或 BlipPhone 项目中使用下面的示例号码。

示例电话号码

这里有一些数字可以演示该库的不同方面。

签发国家/地区

ISO 代码

数字

注释

United States

US

1-800-LOAN-YES

字母数字数据

Switzerland

CH

446681800

从美国拨打的国际长途

United States

US

617-229-1234 x1234

Extension

United States

US

212-439-12345678901

太长(>16 位数字)

摘要

如果您不确定您拥有的是否是有效电话号码,或者是否是接受您想发送的数据类型(如 SMS)的号码,那么呼叫他人或向他们发送文本可能会充满风险。C# 开发人员可以欣慰的是,有一个强大的 NuGet 包可用,可以提供 Google 在验证电话号码方面的专业知识。这大大减轻了为支持电话功能的 .NET 应用程序验证数据的辛劳。Twilio 将处理其余的工作。

下载配套的 示例项目,亲自体验。如果您对代码有疑问,请随时在该项目的 issues 列表中提出问题。

祝您编码愉快!

© . All rights reserved.