As I mentioned earlier, phone numbers have a lot of
challenges. Phone number digits, in certain countries, shift when the area
code has more or less digits. Phone number area codes could have many
different amounts of digits. There are long-distance codes or out-of-country
codes that pose an additional problem. In addition, phone numbers may contain
dashes, but this could also be a space; however, the phone number validation
also allows no spacing as well. The solution right now is to ignore long-distance
and specialty codes and target the widest audience of home and business phone
numbers.
To counter additional illegal numbers with all of this
variation, two length properties are defined: a minimum and maximum length that
the phone number range must be between or a validation error is added.
Listing 4
protected virtual int MaximumLength
{
get
{
return int.Parse(ValidationResources.PhoneNumberMaximumLength);
}
}
protected virtual int MinimumLength
{
get
{
return int.Parse(ValidationResources.PhoneNumberMinimumLength);
}
}
protected virtual string PhoneNumberAreaCodeOnlyRegex
{
get
{
return ValidationResources.PhoneNumberAreaCodeOnlyRegex;
}
}
protected virtual string PhoneNumberRegex
{
get
{
return ValidationResources.PhoneNumberRegex;
}
}
The phone number validation regular expression looks like
this: ^((\([2-9][0-8][0-9]\) ?)|([2-9][0-8][0-9][- ]))[2-9][0-9]{2}[-
][0-9]{4}$. The ^ and $ denote the beginning and end. Also note that it
allows a format of (XXX) XXX-XXXX or XXX-XXX-XXXX. Lastly, note that with area
code rules state that the first digit is 2-9, the second is 0-8, and the third
is 0-9, which is similar to the exchange format. Also, there is an area code
regular expression for getting and parsing only the area code, which is used to
extract the digits from the area code and match them against the accepted area
code list (if provided), which will be discussed in the next section.
These properties can be overloaded if inherited from. All
of these properties use resource strings, as well as the default messages.
Listing 5
protected override string DefaultMessageTemplate
{
get
{
return ValidationResources.PhoneNumberValidator_DefaultMessage;
}
}
protected virtual string DefaultInvalidAreaCodeMessageTemplate
{
get
{
return
ValidationResources.PhoneNumberValidator_InvalidAreaCodeDefaultMessage;
}
}
protected virtual string DefaultInvalidLengthMessageTemplate
{
get
{
return ValidationResources.PhoneNumberValidator_InvalidLengthDefaultMessage;
}
}
To provide added functionality, this validator contains an
array list of area codes, which can enforce which area codes a phone number can
be in. So, for instance, you can limit the input to a specific range of area
code numbers, instead of allowing any area code.
Listing 6
public List < string > AcceptedAreaCodes
{
get
{
if (_acceptedAreaCodes == null)
_acceptedAreaCodes = new List < string > ();
return _acceptedAreaCodes;
}
}
Similarly, if no area codes are in the list, the zip code
can be enforced or not enforced through another property called EnforceZipCode.
Listing 7
public bool EnforceAreaCode
{
get
{
return _enforceAreaCode;
}
set
{
_enforceAreaCode = value;
}
}
To construct the object, there are many overloads available to provide area
codes, specify whether area codes are enforced, and the other default properties
(like message and tag); because that is straightforward, I will not show those
here.
The validation is not overly complex; however, it does
require a lot of thought as to how it will work. First, we have to determine
how detailed error messages are rendered specific to the problem with the
data. This is why there are three default error messages, to expose all of the
problems that there could be. The method that actually renders a message is an
overloaded version of GetMessage, which takes a message key (one of the several
constant values) and renders the appropriate default or overridden message.
Listing 8
private const string INVALID_AREA_CODE_MESSAGE_KEY = "Area Code";
private const string INVALID_LENGTH_MESSAGE_KEY = "Length";
protected virtual string GetMessage(string objectToValidate, string key, string
messageKey)
{
if (!string.IsNullOrEmpty(this.DefaultInvalidAreaCodeMessageTemplate) &&
messageKey == INVALID_LENGTH_MESSAGE_KEY)
return string.Format(CultureInfo.CurrentCulture,
this.DefaultInvalidAreaCodeMessageTemplate, objectToValidate, key);
else if (!string.IsNullOrEmpty(this.DefaultInvalidLengthMessageTemplate) &&
messageKey == INVALID_AREA_CODE_MESSAGE_KEY)
return string.Format(CultureInfo.CurrentCulture,
this.DefaultInvalidLengthMessageTemplate, new object[]
{
objectToValidate, key, this.MinimumLength, this.MaximumLength
}
);
else
return this.GetMessage(objectToValidate, key);
}
The call to it is as simple as below, which we will also see
more about later.
Listing 9
this.GetMessage(objectToValidate, key, INVALID_AREA_CODE_MESSAGE_KEY)
Another method is also helpful to use when validating area
codes. For instance, is the character returned from the regular expression an
area code character or something like the "(" in the area code of
"(724)." To determine this, the validation method loops through each
character and validates the char to determine if it is valid and calls this
method.
Listing 10
protected virtual bool IsValidAreaCodeValue(char value)
{
return Char.IsDigit(value);
}
The validation method, DoValidate, processes the text in
several ways. First, if enforce area code is set to false, it does not
validate the area code specifically. Let us walk through the current
validation process. I validate the length to ensure it is valid. If not, a
length-specific message is rendered to the screen.
Listing 11
if (objectToValidate.Length < this.MinimumLength || objectToValidate.Length >
this.MaximumLength)
base.LogValidationResult(validationResults, this.GetMessage(objectToValidate,
key, INVALID_LENGTH_MESSAGE_KEY), currentTarget, key);
Note the call to the overloaded GetMessage method. Next, if
enforcing the area code and we have a valid regular expression (some countries
will not have one because they don’t area codes), we process it specially to
ensure it is valid or exists within the range of area codes provided in the
list.
Listing 12
if (this.EnforceAreaCode && !string.IsNullOrEmpty
(this.PhoneNumberAreaCodeOnlyRegex))
{
if (!Regex.IsMatch(objectToValidate, this.PhoneNumberAreaCodeOnlyRegex))
base.LogValidationResult(validationResults, this.GetMessage
(objectToValidate, key, INVALID_AREA_CODE_MESSAGE_KEY), currentTarget,
key);
else if (this.AcceptedAreaCodes.Count > 0)
{
string areaCodeText = Regex.Match(objectToValidate,
this.PhoneNumberAreaCodeOnlyRegex).Value;
string areaCode = string.Empty;
foreach (char pos in areaCodeText)
{
if (this.IsValidAreaCodeValue(pos))
areaCode += pos.ToString();
}
if (!this.AcceptedAreaCodes.Contains(areaCode))
base.LogValidationResult(validationResults, this.GetMessage
(objectToValidate, key, INVALID_AREA_CODE_MESSAGE_KEY), currentTarget,
key);
}
}
Lastly, the whole number if validated against a regular
expression to ensure that it is correct and meets a specified regular
expression match.
Listing 13
if (!Regex.IsMatch(objectToValidate, this.PhoneNumberRegex))
base.LogValidationResult(validationResults, this.GetMessage(objectToValidate,
key), currentTarget, key);