Validating user input is important to the security and proper operation of any software application. This is particularly true of data like phone numbers, which are typically private data used to deliver both application functionality, such as messaging, and security features like 2-factor authentication.
Application development frameworks, including the .NET Framework and .NET Core provide data validation capabilities to make it easier to handle standard data types more robustly. While the .NET frameworks provide for validating phone numbers, the capabilities are limited, particularly with respect to internationalization ("i18n").
Fortunately, there is a open source library, libphonenumber-csharp
, that provides extensive resources for validating and manipulating phone numbers of all types. It’s derived from an open source library created by Google. This post shows how you can implement libphonenumber-csharp
in your .NET projects and easily leverage this powerful functionality.
.NET Data Validation
A common design pattern in software built with C# and the .NET frameworks uses class objects to store data. This is the standard approach in web applications using the Model View Controller (MVC) libraries. The .NET frameworks provide functionality to abstract data validation into validation attributes which are used to "decorate" the properties of entity classes.
In the example below, [Required]
and [StringLength(100)]
are validation attributes used to decorate the Title
property of the Movie
class.
public class Movie
{
public int Id { get; set; }
[Required]
[StringLength(100)]
public string Title { get; set; }
}
Validation attributes, along with many other data annotations, are found in the
System.ComponentModel.DataAnnotations namespace, which is available in both the .NET Framework and .NET Core. There are standard validation attributes for a number of common data types, such as dates and email addresses.
Phone number validation in System.Component.DataAnnotations
The DataAnnotations namespace provides two ways of validating a telephone numbers as a data type, enumeration and derived class. Use the enumeration method when your phone number field is a simple type and use the derived class when the field is an object type.
Enumeration Format
Here’s an example of a phone number property as a simple string using the enumeration method of validation:
[DataType(DataType.PhoneNumber)]
public string MobilePhone { get; set; }
The DataType.PhoneNumber
enumeration provides the DataTypeAttribute
class with the specific type of data to validate from the list of standard data types the class supports. Because it’s an enumeration, you get the standard functionality of the DataTypeAttribute
class.
Derived Class Format
This approach applies a class as a validation attribute rather than an enumeration. The Phone
class derives from the DataTypeAttribute
class and can be overridden and extended to provide additional validation behavior.
[Phone]
public string MobilePhone { get; set; }
You could also modify the functionality of the Phone
class and use it to validate a object property:
[Phone]
public Landline WorkPhone { get; set; }
Common Usage and Best Practice
It is not unusual to see [Phone]
and [DataType(DataType.PhoneNumber)]
used interchangeably in C# code, and since the first usage is shorter many developers turn to it as a matter of habit. Although both approaches will work identically in the vast majority of cases, the derived class format does make your code more complex behind the scenes and provide a path for introducing bugs, so it’s better to use enumeration format unless your requirements call for the derived class.
Limitations
Both these formats are documented as performing validation with a regular expression, but the documentation itself is silent on the regular expression(s) used. A better understanding of how this class works can be had by examining the source code on GitHub.
A read-through of the code reveals a number of potential concerns for robust phone number validation:
- Phone numbers are permitted to be expressed with digits and the characters "-.()" plus an extension marked with "ext.", "ext", or "x". Phone numbers sometimes contain alpha characters. For example, "1 (800) LOAN-YES" and "1 (800) MICROSOFT" can both be dialed as phone numbers in the United States.
- There is no check on the length of the phone number submitted.
- There is no check to determine whether the potential phone number is valid in a specific country.
In general, it might be more accurate to say PhoneAttribute.cs determines if a submitted number is "phone number-ish" That's ok, because there is readily available code that performs much more extensive phone number validation.
The libphonenumber-csharp library
Google makes extensive use of phone numbers in its cloud applications, business operations, and in the Android, Chrome, and (presumably) Fuchsia operating systems. Handling phone numbers effectively is important for security and ease of use in all these software systems.
To help Android and other application developers answer the question "Is this a valid telephone number?" more easily, Google developed and helps maintain an open source Java, C++, and JavaScript library for "parsing, formatting, and validating international phone numbers", libphonenumber.
This is a great thing, but not convenient for C# developers working in ASP.NET or ASP.NET Core who want to do server-side data validation (which is definitely a good thing to do).
Fortunately, Tom Clegg has created a port of libphonenumber to C#, libphonenumber-csharp, so .NET developers can implement libphonenumber conveniently in their applications. It's available as a NuGet package, which makes it easy to add to any project.
Essential reading
Before plugging libphonenumber-csharp into a .NET project and hammering away, it's a good idea to review some of the documentation. To get an idea of the kinds of idiosyncrasies and uncertainties associated with phone numbers, read some of the libphonenumber documentation, starting with:
Falsehoods Programmers Believe About Phone Numbers
You'll probably be shocked at the amount of phone number complexity you've been blissfully unaware of before. Now that you know, you have many reasons for including libphonenumber-csharp in your projects.
Also be sure to check out the FAQ and the libphonenumber Readme. You'll learn more about the functionality here than you will in the docs in the libphonenumber-csharp repo. Also check out the Quick Examples in the Readme to get an overview of usage.
Adding libphonenumber-csharp to an ASP.NET Core MVC project
It’s easy to try out libphonenumber-csharp. Let’s walk through the process of adding it to an ASP.NET Core MVC project for server-side validation. (A separate article will cover using the JavaScript version of libphonenumber to do client-side validation, but remember MVC’s advanced model state validation gives you the capability to provide good feedback to the user as a product of server-side validation.)
To help you if you get lost along the way, or want to compare your code to a reference implementation, there’s a runnable version of this example on GitHub. The BlipPhone project is a simple ASP.NET Core 2.1 web application that includes this library. You can run it in Visual Studio 2017.
Create a default project
Begin by creating an ASP.NET Core Web Application project, PhoneCheck, choosing the Web Application (Model-View-Controller) template. For our demonstration project it isn’t necessary to add Authentication to the project, but creating a source code repository is a good idea so you can track and revert your changes. You can specify in-application data storage, but in this tutorial we won’t be using a database to save data.
Install libphonenumber-csharp
Including libphonenumber-csharp in your project is easy. First, check NuGet.org for the latest version information.
Then enter the following command in a Package Manager Console window, substituting the current version information as appropriate:
PM> Install-Package libphonenumber-csharp -Version 8.9.10
Or enter the following .NET command line in a PowerShell window. Be sure the current directory is the project directory:
dotnet add package libphonenumber-csharp --version 8.9.10
Create a ViewModel
We’ll use a view model to hold the data presented to and collected from the user on the HTML page. The view model contains fields for the phone number's issuing country and the phone number to check.
In the Models folder of the project, create a class file, PhoneNumberCheckViewModel.cs.
The BlipPhone sample project contains code to populate a dropdown field with a list of countries and return a two-character ISO country code in the viewmodel. You can use the code provided for the sample, or use the CountryCodeSelected
field as a plain text field and enter the country codes manually.
The PhoneNumberCheckViewModel.cs file is as follows:
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; }
[Display(Name = "Valid Number")]
public bool Valid { get; set; }
[Display(Name = "Has Extension")]
public bool HasExtension { get; set; }
}
}
Note that data attributes are used to make these input fields required and to set the values for their label elements.
Create a view
In the Views folder, create a Phone subfolder, then add a new view, Check
, using the Create template and the PhoneNumberCheckViewModel.cs viewmodel. When the MVC tooling finishes scaffolding the new view the Razor markup generated for the PhoneNumberRaw
field in the Check.cshtml file should look like the following.
In the file extracts that follow, ellipsis ("...") is used to indicate sections redacted for brevity.
...
<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>
...
Edit the view as follows: in the <form>
element, change the first <div>
element so the asp-validation-attribute
appears as shown below:
<div asp-validation-summary="All" class="text-danger"></div>
This enables error messages we’ll look at later.
At runtime the view model and the view are used together by ASP.NET Core MVC to serve HTML code. The HTML in the browser for the PhoneNumberRaw
field in the https://localhost:44383/Phone/Check web page looks like this:
...
<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>
...
Note that the <input>
field is automatically marked as required and the data validation attributes are wired up.
Create a Controller
Phone number validation will take place in the controller and will use the ModelState
object along with the fields in the view model to provide validation and error information to the user.
Begin by creating a new controller in the project’s Controllers folder using the default tooling: PhoneController
.
Add the PhoneNumber
and Models
namespaces to the PhoneController.cs file:
using PhoneNumbers;
using PhoneCheck.Models;
Create a private member variable for the phone number utility class and create an instance of the utility class in the controller constructor:
namespace PhoneCheck.Controllers
{
public class PhoneController : Controller
{
private static PhoneNumberUtil _phoneUtil;
public PhoneController()
{
_phoneUtil = PhoneNumberUtil.GetInstance();
}
...
Note that the new instance of PhoneNumberUtil
is created with the GetInstance()
method, rather than the typical class instance = new class();
syntax in C# to create an instance from a class constructor. Use of the GetInstance()
method results from libphonenumber-csharp
being a port from the original Java library.
Change the name of the default action
By default, the tooling creates an Index()
method for an empty controller. Change the name of the method to Check
.
public IActionResult Check()
{
return View();
}
Create the HttpPost action method
The controller action method accepts the view model returned from the HTML form by the MVC middleware and validates the antiforgery token and the model state of the view model. If those checks pass we can use the PhoneNumberUtil
instance created above in the constructor to do phone number validation and manipulation.
Below the default action (above), enter the following code for the HttpPost action :
[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Check(PhoneNumberCheckViewModel model)
{
if (model == null)
{
throw new ArgumentNullException(nameof(model));
}
if (ModelState.IsValid)
{
try
{
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);
}
Let’s unpack the code by looking at specific sections in the order they appear.
Note that the try...catch
block is important to handling phone number errors, so don’t skip it.
When phone numbers are submitted for server-side validation in the controller, the first step is to parse them into a PhoneNumber
object. Note that there are two required arguments, at minimum, for creating a phone number:
model.PhoneNumberRaw
the raw number to be parsed, as the user enters it and
model.CountryCodeSelected
the country in which the number is assigned.
Try
{
PhoneNumber phoneNumber = _phoneUtil.Parse(model.PhoneNumberRaw,
model.CountryCodeSelected);
Once you have a phone number object you can use your instance of PhoneNumberUtil
to determine a variety of information about it:
- Is it a valid number in the selected country?
- What type of phone number is it? (This may produce a result of more than one type.)
- What is the format for dialing it from a mobile phone in another country?
Checking for a valid number is done by passing the phoneNumber
object we just created and model.CountryCodeSelected
to the IsValidNumberForRegion
method of the _phoneUtil
object.
Since we are returning the results on the same HTML page, setting the properties of the model directly doesn’t work in ASP.NET Core, so we’ll use the ModelState
object to return the results of the IsValidNumberForRegion
method:
ModelState.FirstOrDefault(x => x.Key == nameof(model.Valid)).Value.RawValue =
_phoneUtil.IsValidNumberForRegion(phoneNumber, model.CountryCodeSelected);
Information can also be had from the PhoneNumber
object itself, such as:
- Does it include an extension?
- What is the country code associated with the number?
In our example, we’ll check to see if the phone number has been provided with an extension:
ModelState.FirstOrDefault(x => x.Key == nameof(model.HasExtension)).Value.RawValue =
phoneNumber.HasExtension;
If the try
block completes successfully we return the updated view model to the view:
return View(model);
When PhoneNumberUtil
encounters a error, such as a raw phone number that's too long, it will raise a NumberParseException
. In ASP.NET MVC you can trap these and add them to the ModelState
, which can be used to display error messages to the user.
catch (NumberParseException npex)
{
ModelState.AddModelError(npex.ErrorType.ToString(), npex.Message);
}
Note that to display errors declared at the ModelState
level, rather than the individual field level, the validation summary in the Check.cshtml view has to be set to display all errors, rather than just field-level errors in the model. This is why we made the change when we created the view.
If the test for ModelState.IsValid
is false we need to reset the values on the form before returning it, so add the following lines after the if block to complete the HttpPost
action method:
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);
You can use this pattern to perform a wide variety of checks and transformations on phone numbers, including formatting a number for international dialing from with mobile phones and landlines from various countries. If you want to try additional checks using the PhoneNumber
object or the PhoneNumberUtil
class in the Controller you can write them to output or add more fields to the view model and the view. The BlipPhone sample project shows some commonly used additional fields.
Add a link to the view
For convenience, you can add a link to the /Phone/Check page on the home page or the top navigation bar with Razor code like the following:
<a asp-controller="Phone" asp-action="Check">Check phone numbers</a>
Put it wherever you will find it most convenient.
Explore
The project should now be runnable. If you get stuck, take a look a the BlipPhone sample project for guidance. Use the sample number below with the project you built or the BlipPhone project.
Sample Phone Numbers
Here are some numbers that will demonstrate different aspects of the library.
Issuing Country
| ISO Code
| Number
| Notes
|
United States
| US
| 1-800-LOAN-YES
| Alphanumeric data
|
Switzerland
| CH
| 446681800
| International from US
|
United States
| US
| 617-229-1234 x1234
| Extension
|
United States
| US
| 212-439-12345678901
| Too long (>16 digits)
|
Summary
Calling people, or sending them texts, can be fraught with peril if you aren't sure you have a valid phone number or one that accepts the kind of data you want to send (like SMS). C# developers can take heart that there is a robust NuGet package available that can provide them with Google's expertise in validating phone numbers. This takes a lot of the sweat out of validating the data used in telephony-enabled .NET applications. Twilio does the rest.
Download the companion example project and see for yourself. If you have questions on the code, feel free to open a question in the issues list for the project.
Happy coding!