There are many places in the user interface of an .NET application where the displayed information has to conform to a string output format (currencies, scientific notations). In some cases, these formats need to change depending on the input value. Consider the following example.
The situation
As part of an online annual sales report, you would like to output the sales figure differently depending on its value:
- ${X.XX} for amounts < $10,000 (i.e. $101.35)
- ${X.XX}k for amounts >= $10,000 and < $1,000,000 (i.e. $12.61k, $372.89k)
- ${X.XX)m for amounts >= $1,000,000 (i.e. $9.37m, $87.52m)
Doing it the usual way
To output the correctly formatted string, you may write a function like this:
private static string FormatCurrency(double amount) { if (amount < 10000) return string.Format("{0:C}", amount); else if (amount >= 10000 && amount < 1000000) return string.Format("{0:C}k", (amount / 1000)); else return string.Format("{0:C}m", (amount/1000000)); }
Then to display the value on a webpage, you would write something like this in the MVC controller action:
1 | ViewData["annualSales"] = FormatCurrency(companyAccount.Sales); |
But this is a hassle! Not only do you have to ensure that every number you want formatted this way must pass through the FormatCurrency function, it keeps you guessing which variables need to be treated this way in the code. When you make mistakes, the data looks inconsistent.
The GString Way
Global String (GString) Formatter gives developers the option of defining variables like amount declaratively. Once initialized with a conditional set of string formats, any GString of that type will be outputted the same way, without any need to pass the value through any functions. Here is the GString way of formatting amount.
Define your GString type and any formatters. (Note that the Currency class is just a way to do book-keeping. It helps keep your GString class distinct from GString
public class Currency {} public class USDGString : GString<Currency, double> { public USDGString() { Formatters.Add(v => v < 1000, v => string.Format("{0:C}", v)); Formatters.Add(v => v >= 1000 && v < 1000000, v => string.Format("{0:C}k", v / 1000)); Formatters.Add(v => v >= 1000000, v => string.Format("{0:C}m", v / 1000000)); } }
The CompanyAccount class:
public class CompanyAccount { public USDGString Sales { get; private set; } public CompanyAccount(double sales) { Sales = new USDGString() { Value = sales }; } }
To create a CompanyAccount
var companyAccount = new CompanyAccount(8300291);
Set the value in the controller action
ViewData["annualSales"] = companyAccount.Sales;
Outputting the GString directly on a webpage will get you the correctly formatted value ($8.30m)
<%= ViewData["annualSales] %>
Other uses
Global String Formatter isn’t only for numbers. Since it is a generic class, you can use it for practically anything. Dates, strings, even Exceptions! Doing something like this is perfectly legal:
var gString = new GString<Currency, Exception>()
You could also use it to output some fun messages based on the weather:
var funTemp = new GString<Temperature, double>(); funTemp.Formatters.Add(t => t < -20, t => "Armageddeon!"); funTemp.Formatters.Add(t => t >= -20 && t < 32, t => "Burrr!"); funTemp.Formatters.Add(t => t >= -32 && t < 50, t => "Chilly"); funTemp.Formatters.Add(t => t >= 50 && t < 70, t => "OK"); funTemp.Formatters.Add(t => t >= 70 && t < 90, t => "Very nice"); funTemp.Formatters.Default = t => "Too hot!";
Dependency Injection
One common use for GStringFormatter comes through dependency injection. Imagine you are developing a bank account management program and would like to market it to both American and European companies. Different countries have different symbols and notations for currencies and you want to customize the codebase with minimal effort. (The following Ninject-based example is offered as a Sample application in the GString VS2010 solution.)
You write 2 classes, both inherited from GString
public class EuroGString : GString<Currency, double> { // Unless your machine is set to the European culture page, it won't be possible // to see the Euro symbol in the console. Thus, I'm using E to substitute. public EuroGString() { Formatters.Add(v => v < 1000, v => string.Format("{0:C} E", v)); Formatters.Add(v => v >= 1000 && v < 1000000, v => string.Format("{0:C}k E", v / 1000)); Formatters.Add(v => v >= 1000000, v => string.Format("{0:C}m E", v / 1000000)); } } public class USDGString : GString<Currency, double> { public USDGString() { Formatters.Add(v => v < 1000, v => string.Format("{0:C}", v)); Formatters.Add(v => v >= 1000 && v < 1000000, v => string.Format("{0:C}k", v / 1000)); Formatters.Add(v => v >= 1000000, v => string.Format("{0:C}m", v / 1000000)); } }
Using Ninject Modules, you could bind any field or property of type IGString to the respective GString types.
class EuroAccountModule : NinjectModule { public override void Load() { Bind<IGString<Currency, double>>().To<EuroGString>(); Bind<Account>().ToSelf(); } } class USDAccountModule : NinjectModule { public override void Load() { Bind<IGString<Currency, double>>().To<USDGString>(); Bind<Account>().ToSelf(); } }
Now suppose you had an unrealistically simple Account class to represent the bank accounts. Notice the [Inject] Ninject attribute that says to substitute the correct implementation for IGString.
public class Currency { } class Account { [Inject] public IGString<Currency, double> Amount { get; set; } public void Set(double value) { Amount.Value = value; } // other functions for adding, subtracting, clearing Amount }
Lastly, in your executing program (in this case a console), you would wire up all the objects and Global String Formatter would automatically handle all the string outputs for you, based on the desired implementation of GString.
class Program { static void Main(string[] args) { var kernel = new StandardKernel(new USDAccountModule()); var account = kernel.Get<Account>(); account.Set(300.67); Console.WriteLine(account.Amount); // prints $300.67 account.Add(45993); Console.WriteLine(account.Amount); // prints $46.29k } }
You could just as easily pass EuroAccountModule as the parameter to the
StandardKernel constructor and all your amounts will be outputted in Euros!
(I do understand that the proper way of printing culture-specific currency notation is by setting CultureInfo. Using “E” for Euro is just for demonstration.)
Download from CodePlex
Current version: 1.0.0.0
Leave a comment