Thursday, May 24, 2007

Custom string formatting in .Net

Recently, while working on a project, I came across the need to format the phone number supplied as string in the standard U.S. phone format(i.e. 1234567890 should be displayed as (123) 456-7890. The phone number was being fetched as a string from the database. I personally feel, that a better option to operate on strings is to save them in a formatted manner i.e. the way you wish to display them, but this is not always possible.

So how do you custom format the strings in .Net? You got it - you use IFormatProvider. IFormatProvider is an interface that provides you an option to format the value as per your requirements. The sole member contained in this interface is GetFormat(). To provide the custom formatting, you need to make a class that implements IFormatProvider. Apart from this, your class must also implement ICustomFormatter. This is the interface that actually handles the custom logic of formatting the supplied value. ICustomFormatter contains a single method named Format() which accepts the format in which value should be formatted, the value that is to be formatted and an instance of IFormatProvider(i.e your custom class that implements IFormatProvider).

Lets get down to the code and see what it need to do a custom formatting:-

Our requirement: We have a string that contains the phone number. The phone number is a standard 10 digit number. However, it may or may not have a country code attached. If the country code is present, the format will be '+' followed by country code, a space and then the 10 digit phone number. So, example of valid values are:

Phone number without country code: 1234567890
Phone number with country code: +91 1234567890

Lets look at the implementation of our class that handles the formatting:

public class PhoneFormatter : IFormatProvider, ICustomFormatter

{

#region IFormatProvider Members


public object GetFormat(Type formatType)

{

// Check if the class implements ICustomFormatter

if (formatType == typeof(ICustomFormatter))

{

return this;

}

else

{

return null;

}

}


#endregion


#region ICustomFormatter Members


public string Format(string format, object arg, IFormatProvider formatProvider)

{

// if the passed in argument is null, return empty string

if (arg == null)

{

return string.Empty;

}


// Get the value of argument in string

string phoneNumber = arg.ToString();


// Check if phone number has country code

if (phoneNumber.StartsWith("+") && phoneNumber.IndexOf(' ') > 1)

{

// If it contains country code, separate it from phone number

string countryCode = phoneNumber.Substring(0, phoneNumber.IndexOf(' ') + 1);

phoneNumber = phoneNumber.Remove(0, countryCode.Length);


// Get the formatted value of phone number and prefix it with the country code

phoneNumber = string.Format("{0}{1}", countryCode, this.GetFormattedPhoneNumber(phoneNumber, format));

}


// Check if the phone number is a valid 10 digit number

if (phoneNumber.Length == 10)

{

// Get the formatted value of phone number

phoneNumber = this.GetFormattedPhoneNumber(phoneNumber, format);

}


return phoneNumber;

}


#endregion


#region Helper method


private string GetFormattedPhoneNumber(string phoneNumber, string format)

{

long number = 0;


//Check if the phone number is a valid numeric value

if (long.TryParse(phoneNumber, out number))

{

// If phone number is numeric, format it as per the passed in value

phoneNumber = number.ToString(format);

}

return phoneNumber;

}


#endregion

}

As you can see, we have a class defined, that implements IFormatProvider and ICustomFormatter. I have commented code at each step so that it is easier to understand whats going on.

Now, lets have a look at its usage:

string phoneNumber = "+91 1234567890";

Console.WriteLine(string.Format(new PhoneFormatter(), "{0:(###) ###-####}", phoneNumber));



This outputs: +91 (123) 456-7890

Similarly, following code produces (123) 456-7890

string phoneNumber = "1234567890";

Console.WriteLine(string.Format(new PhoneFormatter(), "{0:(###) ###-####}", phoneNumber));



Another interesting implementation could be in case of displaying Bank account numbers where only last 4 or 5 digits are displayed and rest are shown as '*' (e.g. ****-****-1234). Its not that this can't be achieved by string operations like substring, but its just that this approach provides you a more structured, managable and reusable way of formatting values.

I hope that this article was easy to follow and enjoyable to you as much as it was to me writing it.

kick it on DotNetKicks.com

2 comments:

Anonymous said...

Your blog keeps getting better and better! Your older articles are not as good as newer ones you have a lot more creativity and originality now keep it up!

Anonymous said...

It is in reality a nice and useful piece of info. I am glad that you simply shared this helpful information with us. Please keep us up to date like this. Thank you for sharing.