C#と.NETフレームワークで電話番号を検証する方法

September 06, 2018
執筆者
AJ Saulsberry
寄稿者
Twilio の寄稿者によって表明された意見は彼ら自身のものです
レビュー担当者

C#と.NETフレームワークで電話番号を検証する方法

この記事はAJ Saulsberryこちらで公開した記事(英語)を日本語化したものです。

ユーザー入力の検証は、ソフトウェアアプリケーションのセキュリティの保全と運用にとって重要な役割を果たします。特に、電話番号などのデータでは、ユーザー入力の検証の重要性が高まります。電話番号は、メッセージ送信などのアプリケーション機能と、2要素認証などのセキュリティ機能の両方を提供するために使用されるプライベートなデータであるためです。

.NET Frameworkや.NET Coreなどのアプリケーション開発フレームワークにはデータ検証機能があるため、標準のデータ型は確実かつ簡単に処理できます。.NETフレームワークで電話番号を検証することも可能ですが、国際化(i18n)など、使用できる機能が制限されています。

幸いなことに、すべてのタイプの電話番号を検証して操作するためのリソースを提供するオープンソースライブラリのlibphonenumber-csharpがあります。libphonenumber-csharpは、Googleが作成したオープンソースライブラリから派生しています。本稿では、.NETプロジェクトにlibphonenumber-csharpを実装し、その強力な機能を簡単に活用する方法をご紹介します。

.NETデータ検証

C#と.NETフレームワークで構築されたソフトウェアの一般的な設計パターンでは、クラスオブジェクトを使用してデータを格納します。これは、Model View Controller(MVC)ライブラリを使用するWebアプリケーションでは標準的なアプローチです。.NET フレームワークには、データ検証を検証属性に抽象化する機能があり、エンティティクラスのプロパティの「装飾」に使用されます。

次の例の[Required][StringLength(100)]は、MovieクラスのTitleプロパティの装飾に使用する検証属性です。

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名前空間は、電話番号をデータ型として検証する列挙型と派生クラスの2つの方法を提供します。電話番号フィールドが単純型の場合は列挙型メソッドを使用し、フィールドがオブジェクト型の場合は派生クラスを使用します。

列挙型の形式

検証の列挙型メソッドを使用する単純型の文字列としての電話番号プロパティの例を次に示します。

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; }

一般的な使用法とベストプラクティス

[Phone][DataType(DataType.PhoneNumber)]がC#のコードで同じ意味で使用されるのは珍しいことではありませんが、[Phone]を使用する方がコードが短くて済むため、多くの開発者はこちらを習慣的に使用しています。どちらのアプローチも、ほとんどの場合は同じように機能しますが、派生クラス形式はコードがバックグラウンドで複雑になり、バグが発生する可能性があるため、要件のために派生クラスが必要な場合を除いて、列挙形式を使用することをお勧めします。

制限事項

これらの形式は両方とも、正規表現を使用して検証を実行するように文書化されていますが、ドキュメント自体は、使用する正規表現について言及していません。GitHubのソースコードを詳しく見ると、このクラスがどのように機能するか理解をすることができます。

コード全体を確認すると、電話番号を確実に検証できるのかどうか、潜在的な懸念事項が明らかになります。

  • 電話番号は、数字と文字-.()に加えて、ext.extxでマークされた内線番号で表すことができます。また、電話番号に英字が含まれる場合もあります。たとえば、米国では1 (800) LOAN-YES1 (800) MICROSOFTの両方を電話番号として発信できます。
  • 送信された電話番号の長さのチェックはありません。
  • 電話番号が特定の国で有効かどうかを判断するためのチェックはありません。

これを見ると、PhoneAttribute.csは、送信された番号が「電話番号らしい」かどうかを判断すると言った方が正確かもしれませんが、次にご紹介する電話番号の広範な検証を実行するコードがあるため、問題ありません。

libphonenumber-csharpライブラリ

Googleでは、自社のクラウドアプリケーション、事業運営、Android、Chrome、Fuchsiaオペレーティングシステムで電話番号を多用しています。電話番号を効果的に処理することは、これらすべてのソフトウェアシステムのセキュリティと使いやすさにとって重要です。

Googleでは、「これは有効な電話番号ですか?」という質問にAndroidや他のアプリケーション開発者が容易に答えられるように、国際電話番号の解析、フォーマット、検証のためのオープンソースのJava、C++、JavaScriptライブラリであるlibphonenumberを開発し、管理をサポートしています。

このライブラリは素晴らしいですが、サーバー側のデータ検証を実行したいと考えている、ASP.NETまたはASP.NET Coreで作業しているC#開発者にとっては便利ではありません。

幸いなことに、Tom Clegg氏がC#向けのlibphonenumberのポート、libphonenumber-csharpを作成しており、.NET開発者はアプリケーションにlibphonenumberを簡単に実装できます。NuGetパッケージとして使用可能なため、任意のプロジェクトに簡単に追加できます。

必読のドキュメント

libphonenumber-csharpを.NETプロジェクトに接続して試行する前に、ドキュメントを確認することをお勧めします。電話番号に関連する一種の特異性と不確実性を理解するには、次のlibphonenumberのドキュメントをお読みください。

Falsehoods Programmers Believe About Phone Numbers(電話番号についてプログラマーが信じてしまう嘘)

これまで気づかなかった電話番号の複雑さにおそらく驚かれることでしょう。これで、libphonenumber-csharpをプロジェクトに追加する理由が多くあることがわかったと思います。

また、FAQとlibphonenumberのReadmeも確認してください。libphonenumber-csharpリポジトリのドキュメントよりもライブラリの機能について詳しく学ぶことができます。Readmeでは、簡単なコードサンプルで使用法の概要を確認することもできます。

libphonenumber-csharpをASP.NET Core MVCプロジェクトに追加する

libphonenumber-csharpは簡単に試すことができます。サーバー側の検証用に、ASP.NET Core MVCプロジェクトに追加するプロセスを見ていきましょう。JavaScriptバージョンのlibphonenumberを使用したクライアント側の検証の実行も一般的ですが、MVCの高度なモデル状態検証では、サーバー側の検証の結果として適切なフィードバックを提供できることも忘れないでください。

途中で迷った場合や、コードを参照実装と比較したい場合に、GitHubで実行可能なバージョン、BlipPhoneを用意しています。BlipPhoneプロジェクトは、libphonenumber-csharpを含むシンプルなASP.NET Core 2.1 Webアプリケーションです。Visual Studio 2017で実行できます。

デフォルトのプロジェクトを作成する

まず、Webアプリケーション(Model-View-Controller)テンプレートを選択し、ASP.NET Core WebアプリケーションプロジェクトのPhoneCheckを作成します。デモ用プロジェクトでは、プロジェクトに認証を追加する必要はありませんが、変更を追跡して元に戻すことができるように、ソースコードリポジトリを作成することをお勧めします。アプリケーション内のデータストレージを指定できますが、このチュートリアルでは、データベースを使用してデータを保存することはしません。

libphonenumber-csharpをインストールする

プロジェクトにlibphonenumber-csharpを含めるのは簡単です。まず、NuGet.orgで最新バージョンの情報を確認します。

次に、パッケージマネージャーコンソールウィンドウで次のコマンドを入力し、必要に応じて現在のバージョン情報に置き換えます。

PM> Install-Package libphonenumber-csharp -Version 8.12.38

または、PowerShellウィンドウで次の.NETコマンドラインを入力します。現在のディレクトリがプロジェクトディレクトリになっていることを確認します。

dotnet add package libphonenumber-csharp --version 8.12.38

ViewModelを作成する

ビューモデルを使用し、HTMLページでユーザーに提示したデータとユーザーから収集したデータを保持します。ビューモデルには、電話番号の発行国と検証する電話番号のフィールドが含まれています。

プロジェクトのModelsフォルダに、PhoneNumberCheckViewModel.csクラスファイルを作成します。

BlipPhoneサンプルプロジェクトには、ドロップダウンフィールドに国のリストを入力し、ビューモデルで2文字の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サブフォルダを作成し、CreateテンプレートとPhoneNumberCheckViewModel.csビューモデルを使用して、新しいビューのCheckを追加します。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>
...

次のようにビューを編集します。

Razorマークアップの中の、<form>要素の内側に存在する最初の<div>要素のasp-validation-attributeを以下のように変更します。

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

これにより、エラーメッセージが有効になります。

実行時に、ビューモデルとビューがASP.NET Core MVCで使用され、HTMLコードが提供されます。https://localhost: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オブジェクトとビューモデルのフィールドを使用して、検証とエラー情報をユーザーに提供します。

最初に、デフォルトのツールのPhoneControllerを使用し、プロジェクトのControllersフォルダに新しいコントローラーを作成します。

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の新しいインスタンスは、クラスコンストラクタからインスタンスを作成するためのC#の一般的なclass instance = new class();構文ではなく、GetInstance()メソッドを使用して作成されることに注意してください。libphonenumber-csharpは元のJavaライブラリのポートであるため、GetInstance()メソッドを使用します。

デフォルトのアクションの名前を変更する

デフォルトで、空のコントローラーの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

表示順に、コードを詳しく見てみましょう。

try…catchブロックは電話番号エラーを処理するために重要であるため、スキップしないでください。

サーバー側の検証用にコントローラーで電話番号が送信されたら、まずは電話番号をPhoneNumberオブジェクトに解析します。電話番号を作成するには、少なくとも2つの必須の引数があることに注意してください。

  • 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では機能しません。このため、IsValidNumberForRegionメソッドの結果を返すには、ModelStateオブジェクトを使用します。

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に追加できます。これを使用して、ユーザーにエラーメッセージを表示できます。

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);

このパターンを使用して、携帯電話やさまざまな国の固定電話から発信する国際電話用の番号のフォーマットなど、電話番号のさまざまなチェックと変換を実行できます。コントローラーのPhoneNumberオブジェクトまたはPhoneNumberUtilクラスを使用して追加のチェックを試したい場合は、それらを記述し、ビューモデルとビューにフィールドを出力または追加できます。BlipPhoneサンプルプロジェクトでは、一般に使用される追加フィールドを示しています。

リンクをビューに追加する

便宜上、以下のようなRazor構文で、トップページやトップナビゲーションバーに/Phone/Checkページへのリンクを追加することができます。

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

分かりやすい場所に配置してください。

プロジェクトを試す

これで、プロジェクトを実行できます。行き詰まった場合は、BlipPhoneサンプルプロジェクトを参照してください。作成したプロジェクトまたはBlipPhoneプロジェクトで、次のサンプル番号を使用します。

サンプル電話番号

ライブラリのさまざまな機能を試すことのできる番号を以下に例示します。

発行国ISOコード電話番号注記
米国US1-800-LOAN-YES英数字データ
スイスCH446681800米国からの国際電話
米国US617-229-1234 x1234内線番号
米国US212-439-12345678901長すぎる(16桁を超えている)

まとめ

有効な電話番号や、送信したい種類のデータ(SMSなど)を受け入れられる電話番号があるかどうか分からない場合、電話をかけたり、テキストを送信したりすると、危険な可能性があります。電話番号の検証に関するGoogleの専門知識を提供できるNuGetパッケージを利用できるため、C#開発者は安心できます。これにより、テレフォニー対応の.NETアプリケーションで使用されるデータを検証する手間が省けます。その他の対応はTwilioにお任せください。

コンパニオンサンプルプロジェクトをダウンロードし、ご自身の目でお確かめください。コードについて質問がある場合は、プロジェクトのイシューリストで新しい質問を開いてください。

コーディングを楽しみましょう!

.NET CoreアプリケーションでのTwilio APIの完全な統合については、5部構成のビデオシリーズをご覧ください。一度に多くのAPIの概要を確認できます。