Currency Input in Spectre.Console

I was trying this just the other day in Spectre.Console. I wanted an input prompt that would accept currency values. I started down the path of using a decimal representation (TextPrompt<decimal>). While that mostly worked on the input value, it does not support using negative sign, separators, or decimal places. I wanted to be able to input large numbers, so the separators would be very helpful.

After a bit of research and trying out different TextPrompt<string> configurations, I came to the following conclusion…

To configure a TextPrompt<string> in Spectre.Console that accepts input for currency values (including negative signs, thousands separators, and decimal points) and is culture-aware, you need to configure the NumberStyles and CultureInfo classes in .NET in a basic TextPrompt<string> (not decimal).

Here’s a step-by-step guide and example code:

Steps:

  1. Culture Awareness: Use CultureInfo.CurrentCulture or specify a culture explicitly (like CultureInfo("en-US") for U.S. formatting) to handle currency symbols, thousands separators, and decimal points correctly.
    The example below uses CultureInfo to test various locales, but in actual product usage, you can use CultureInfo.CurrentCulture or CultureInfo.CurrentUICulture.
  2. NumberStyles: Use NumberStyles.Currency or NumberStyles.AllowDecimalPoint | NumberStyles.AllowThousands | NumberStyles.AllowLeadingSign to handle the input format.
  3. Validation: Utilize the TextPrompt<T>.Validate() method to ensure the input conforms to the currency format.

Example Code:

using Spectre.Console;
using System.Globalization;

// Set a specific culture (for testing purposes),
// but use CultureInfo.CurrentCulture for the current culture in real code.
var culture = CultureInfo.GetCultureInfo("en-US");

//var culture = CultureInfo.CurrentCulture;

// Prompt user for a decimal value with proper validation and culture awareness
var result = AnsiConsole.Prompt(
    new TextPrompt<string>("Enter a currency value (e.g., -1,234.56):")
        .Culture(culture)
        .Validate(input =>
        {
            // Try parsing the input based on currency styles and the current culture
            if (decimal.TryParse(input, NumberStyles.Currency, culture, out decimal value))
            {
                return ValidationResult.Success();
            }
            else
            {
                return ValidationResult.Error("[red]Please enter a valid currency value.[/]");
            }
        }));

// Convert the result to a decimal
decimal parsedValue = decimal.Parse(result, NumberStyles.Currency, culture);

AnsiConsole.MarkupLine($"You entered: {parsedValue.ToString("C", culture)}");

Explanation:

  1. CultureInfo: The CultureInfo.GetCultureInfo("en-US") ensures that the input will be parsed according to U.S. currency format (e.g., comma for thousands separator, period for decimals, negative sign).
  2. TextPrompt with Validation: The .Validate() method ensures that the input follows the currency format by using decimal.TryParse with NumberStyles.Currency to handle symbols, separators, and decimals.
  3. Parsing and Output: Once validated, the input is parsed as a decimal, and then displayed back using the ToString("C", culture) method to ensure it is formatted correctly as currency.

Handling Different Cultures:

You can change CultureInfo.GetCultureInfo() to any culture you wish to support (e.g., en-GB, fr-FR, etc.), or simply use CultureInfo.CurrentCulture to automatically detect the current culture of the user’s machine

This approach ensures that your input is flexible, accepts various cultural formats for currency, and handles negative values, thousands separators, and decimal points properly.

Leave a comment