1 // Licensed to the .NET Foundation under one or more agreements. 2 // The .NET Foundation licenses this file to you under the MIT license. 3 // See the LICENSE file in the project root for more information. 4 5 namespace System.ComponentModel.DataAnnotations 6 { 7 [AttributeUsage(AttributeTargets.Property | AttributeTargets.Field | AttributeTargets.Parameter, 8 AllowMultiple = false)] 9 public sealed class PhoneAttribute : DataTypeAttribute 10 { 11 private const string AdditionalPhoneNumberCharacters = "-.()"; 12 private const string ExtensionAbbreviationExtDot = "ext."; 13 private const string ExtensionAbbreviationExt = "ext"; 14 private const string ExtensionAbbreviationX = "x"; 15 PhoneAttribute()16 public PhoneAttribute() 17 : base(DataType.PhoneNumber) 18 { 19 // Set DefaultErrorMessage not ErrorMessage, allowing user to set 20 // ErrorMessageResourceType and ErrorMessageResourceName to use localized messages. 21 DefaultErrorMessage = SR.PhoneAttribute_Invalid; 22 } 23 IsValid(object value)24 public override bool IsValid(object value) 25 { 26 if (value == null) 27 { 28 return true; 29 } 30 31 if (!(value is string valueAsString)) 32 { 33 return false; 34 } 35 36 valueAsString = valueAsString.Replace("+", string.Empty).TrimEnd(); 37 valueAsString = RemoveExtension(valueAsString); 38 39 bool digitFound = false; 40 foreach (char c in valueAsString) 41 { 42 if (Char.IsDigit(c)) 43 { 44 digitFound = true; 45 break; 46 } 47 } 48 49 if (!digitFound) 50 { 51 return false; 52 } 53 54 foreach (char c in valueAsString) 55 { 56 if (!(Char.IsDigit(c) 57 || Char.IsWhiteSpace(c) 58 || AdditionalPhoneNumberCharacters.IndexOf(c) != -1)) 59 { 60 return false; 61 } 62 } 63 64 return true; 65 } 66 RemoveExtension(string potentialPhoneNumber)67 private static string RemoveExtension(string potentialPhoneNumber) 68 { 69 var lastIndexOfExtension = potentialPhoneNumber 70 .LastIndexOf(ExtensionAbbreviationExtDot, StringComparison.OrdinalIgnoreCase); 71 if (lastIndexOfExtension >= 0) 72 { 73 var extension = potentialPhoneNumber.Substring( 74 lastIndexOfExtension + ExtensionAbbreviationExtDot.Length); 75 if (MatchesExtension(extension)) 76 { 77 return potentialPhoneNumber.Substring(0, lastIndexOfExtension); 78 } 79 } 80 81 lastIndexOfExtension = potentialPhoneNumber 82 .LastIndexOf(ExtensionAbbreviationExt, StringComparison.OrdinalIgnoreCase); 83 if (lastIndexOfExtension >= 0) 84 { 85 var extension = potentialPhoneNumber.Substring( 86 lastIndexOfExtension + ExtensionAbbreviationExt.Length); 87 if (MatchesExtension(extension)) 88 { 89 return potentialPhoneNumber.Substring(0, lastIndexOfExtension); 90 } 91 } 92 93 lastIndexOfExtension = potentialPhoneNumber 94 .LastIndexOf(ExtensionAbbreviationX, StringComparison.OrdinalIgnoreCase); 95 if (lastIndexOfExtension >= 0) 96 { 97 var extension = potentialPhoneNumber.Substring( 98 lastIndexOfExtension + ExtensionAbbreviationX.Length); 99 if (MatchesExtension(extension)) 100 { 101 return potentialPhoneNumber.Substring(0, lastIndexOfExtension); 102 } 103 } 104 105 return potentialPhoneNumber; 106 } 107 MatchesExtension(string potentialExtension)108 private static bool MatchesExtension(string potentialExtension) 109 { 110 potentialExtension = potentialExtension.TrimStart(); 111 if (potentialExtension.Length == 0) 112 { 113 return false; 114 } 115 116 foreach (char c in potentialExtension) 117 { 118 if (!Char.IsDigit(c)) 119 { 120 return false; 121 } 122 } 123 124 return true; 125 } 126 } 127 } 128