For fun we can do this:
return string.Format("{0:(###) ### - ####}",ulong.Parse(phone.Replace("-", "")));
But what I'd really do is remove the input mask. Yes, use html/javascript to help the user put in good data, but do so in a way that's much more permissive. If I put in 5551234567
, 555.123.4567
, (555)123-4567
, or even much worse, you should be able to handle any of those.
Input masks are usually bad UI/UX.
On the C# end, I'd really separate this into two parts: normalization, where I clean up potentially messy input for storage and validation, and formatting, where I take the normalized data and format it for display. The reason for two steps is because it's often much more efficient in terms of storage and indexing to store a basic (unformatted) value. Even better, sometimes users want to see the same data represented in different ways. Now it's easy for me to have different format options for the same value. Some people will also include validation as it's own phase, but I like to do this as part of normalizing the data.
Thus, for a really basic phone number, I'd handle the code like this:
public static string NormalizePhone(string phone)
{
// **We should give the user the benefit of the doubt.**
// I don't care what crazy format they used, if there are 10 digits, we can handle it.
//remove anything not a digit
var digits = Regex.Replace(phone, @"[^\d]", "");
//ensure exactly 10 characters remain
if (digits.Length != 10) throw new InvalidArgumentException($"{phone} is not a valid phone number in this system.");
return digits;
}
// Phone argument should be pre-normalized,
// because we want to be able to use this method with strings retrieved
// from storage without having to re-normalize them every time.
// Remember, you'll show a repeat value more often than you receive new values.
public static string FormatPhone(string phone)
{
//Even better if you have an Assert() here that can show phone is always pre-normalized in testing, but works to a no-op in production.
return Regex.Replace(phone, @"(\d{3})(\d{3})(\d{4})", "($1) $2 - $3");
}
Now your existing code can call them together:
try
{
FormatPhone(NormalizePhone(phone));
}
catch(InvalidArgumentException ex)
{
// This won't happen often.
// The html/js layer should stop it in most cases,
// such that we meet the rule of reserving exception handling for actual exceptional events.
// But you'll still want to add a meaningful handler here.
}
But really, I would call NormalizePhone()
by itself, to have that raw value ready to save to the user's record, and then FormatPhone()
afterwards, to show the user on the screen.
Finally, this is a simplistic port. Real phone number validation can be quite complicated. The link is pretty much the standard work in the area, and it's a whopping 12Mb of raw code.