### Work with Currency and CurrencyInfo in C# Source: https://context7.com/remyduijkeren/nodamoney/llms.txt Shows how to retrieve Currency and CurrencyInfo objects using ISO codes, access currency metadata like symbol and decimal digits, and manage custom currencies. Includes examples of getting current culture's currency and registering new ones. ```csharp using NodaMoney; using System.Globalization; // Get Currency from code Currency usd = Currency.FromCode("USD"); Console.WriteLine($"Code: {usd.Code}, Symbol: {usd.Symbol}"); // Output: Code: USD, Symbol: $ // Get full CurrencyInfo with metadata CurrencyInfo euroInfo = CurrencyInfo.FromCode("EUR"); Console.WriteLine($"Name: {euroInfo.EnglishName}"); Console.WriteLine($"Symbol: {euroInfo.Symbol}"); Console.WriteLine($"Decimal Digits: {euroInfo.DecimalDigits}"); Console.WriteLine($"ISO Number: {euroInfo.NumericCode}"); // Output: // Name: Euro // Symbol: € // Decimal Digits: 2 // ISO Number: 978 // Get currency from current culture/region CurrencyInfo current = CurrencyInfo.CurrentCurrency; // Get currency from CultureInfo var nlCulture = new CultureInfo("nl-NL"); CurrencyInfo nlCurrency = nlCulture.GetCurrencyInfo(); // List all available currencies IReadOnlyList allCurrencies = CurrencyInfo.GetAllCurrencies(); // Find currencies by symbol (may return multiple matches) IReadOnlyList dollarCurrencies = CurrencyInfo.GetAllCurrencies("$"); // Register a custom currency CurrencyInfo customCurrency = CurrencyInfo.Create("BTC") with { Symbol = "₿", EnglishName = "Bitcoin", MinorUnit = MinorUnit.Eight, // 8 decimal places IsIso4217 = false }; CurrencyInfo.Register(customCurrency); var bitcoin = new Money(0.00123456m, "BTC"); ``` -------------------------------- ### Quick Start with NodaMoney Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Basic usage demonstrating money initialization, arithmetic, formatting, parsing, and splitting. ```csharp using NodaMoney; var price = new Money(12.99m, "USD"); var tax = price * 0.21m; // currency‑safe arithmetic var total = price + tax; // auto‑rounded to minor unit string text = total.ToString("C", new System.Globalization.CultureInfo("en-US")); // e.g. "$15.72" // Parse var parsed = Money.Parse("$15.72", Currency.FromCode("USD")); // Split without losing cents var shares = total.Split(3); // e.g. [$5.24, $5.24, $5.24] ``` -------------------------------- ### Example appsettings.json Configuration Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney.DependencyInjection/README.md JSON structure for configuring MoneyContext via the configuration system. ```json { "MoneyContext": { "DefaultCurrency": "EUR", "Precision": 28, "MaxScale": 2 } } ``` -------------------------------- ### Dynamic MoneyContext Setup Middleware Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney/Context/How-to-use.md Implement middleware to dynamically set the MoneyContext based on request query parameters or headers. Ensure the thread-local context is reset after the request. ```csharp public class MoneyContextMiddleware { private readonly RequestDelegate _next; public MoneyContextMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // Example: Read rounding preferences from a query parameter or header var roundingPolicy = context.Request.Query["roundingPolicy"]; MoneyContext.ThreadContext = roundingPolicy switch { "Retail" => MoneyContext.CreateRetail(), "Accounting" => MoneyContext.CreateAccounting(), _ => MoneyContext.CreateDefault() }; try { await _next(context); } finally { // Reset the thread-local context after the request is handled MoneyContext.ThreadContext = null; } } } ``` -------------------------------- ### Install NodaMoney via NuGet Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Use the .NET CLI to add the NodaMoney package and optional dependency injection support to your project. ```powershell dotnet add package NodaMoney dotnet add package NodaMoney.DependencyInjection # optional DI integration ``` -------------------------------- ### Money Formatting and Culture Support Source: https://github.com/remyduijkeren/nodamoney/blob/master/README.md Examples of formatting money objects using different cultures and standard format specifiers. ```csharp Money yen = new Money(2765m, "JPY"); Money euro = new Money(2765.43m, "EUR"); Money dollar = new Money(2765.43m, "USD"); Money dinar = new Money(2765.432m, "BHD"); // Implicit when current culture is 'en-US' yen.ToString(); // "¥2,765" euro.ToString(); // "€2,765.43" dollar.ToString(); // "$2,765.43" dinar.ToString(); // "BD2,765.432" // Implicit when current culture is 'nl-BE' yen.ToString(); // "¥ 2.765" euro.ToString(); // "€ 2.765,43" dollar.ToString(); // "$ 2.765,43" dinar.ToString(); // "BD 2.765,432" // Implicit when current culture is 'fr-BE' yen.ToString(); // "2.765 ¥" euro.ToString(); // "2.765,43 €" dollar.ToString(); // "2.765,43 $" dinar.ToString(); // "2.765,432 BD" // Explicit format for culture 'nl-NL' var ci = new CultureInfo("nl-NL"); yen.ToString(ci); // "¥ 2.765" euro.ToString(ci); // "€ 2.765,43" dollar.ToString(ci); // "$ 2.765,43" dinar.ToString(ci); // "BD 2.765,432" // Standard Formats when currenct culture is 'nl-NL' dollar.ToString("C"); // "$ 2.765,43" Currency symbol format dollar.ToString("C0");// "$ 2.765" Currency symbol format with precision specifier dollar.ToString("c"); // "$ 2.8K" Compact Currency symbol format dollar.ToString("I"); // "US$ 2.765,43" International Currency symbol format dollar.ToString("i"); // "US$ 2.8K" Compact International Currency symbol format dollar.ToString("G"); // "USD 2.765,43" ISO currency code format (= C but with currency code) dollar.ToString("g"); // "USD 2.8K" Compact ISO currency code format (international) dollar.ToString("L"); // "2.765,43 dollar" English name format dollar.ToString("l"); // "2.8K dollar" Compact English name format dollar.ToString("R"); // "USD 2,765.43" Round-trip format dollar.ToString("N"); // "2,765.43" Number format (no currency) ``` -------------------------------- ### C# Unit Test Example with AAA and NSubstitute Source: https://github.com/remyduijkeren/nodamoney/blob/master/tests/NodaMoney.Tests/README.md Demonstrates a typical unit test structure using Arrange, Act, and Assert. It utilizes NSubstitute for mocking dependencies and xUnit's Fact attribute for test identification. This pattern is useful for testing service methods with repository dependencies. ```csharp [Fact] public void WhenContactIdExists_ReturnsContact() { // Arrange var contactId = Guid.NewGuid(); var contact = new Contact { Id = contactId }; var contactRepository = Substitute.For(); contactRepository.GetById(contactId).Returns(contact); var sut = new ContactService(contactRepository); // Act var result = sut.GetContact(contactId); // Assert Assert.Equal(contact, result); } ``` -------------------------------- ### Register Exchange Providers in Dependency Injection Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/proposals/README.md Example of registering an exchange rate provider within an IServiceCollection. ```csharp using Microsoft.Extensions.DependencyInjection; using NodaMoney.Context; using NodaMoney.Exchange; public static class ServiceCollectionExtensions { public static IServiceCollection AddFx(this IServiceCollection services) { services.AddSingleton(sp => { var eur = CurrencyInfo.FromCode("EUR"); var inMem = new InMemoryExchangeRateProvider(new[] { new ExchangeRate(eur, CurrencyInfo.FromCode("USD"), 1.0850m, new RateContext("seed")), }, id: "inmem"); return new CachedExchangeRateProvider(new CompositeExchangeRateProvider(inMem), TimeSpan.FromMinutes(5)); }); return services; } } ``` -------------------------------- ### System.Text.Json Source-Generated Context Example Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/proposals/12-serialization-ecosystem-completeness.md Example of using source-generated JsonSerializerContext with System.Text.Json for improved AOT compatibility and performance. This approach helps ensure that all necessary serialization logic is known at compile time. ```csharp [JsonSerializable(typeof(Money))] internal partial class AppJsonContext : JsonSerializerContext { } ``` -------------------------------- ### Initialize Currency and CurrencyInfo Source: https://github.com/remyduijkeren/nodamoney/blob/master/README.md Shows how to instantiate Currency units and retrieve metadata via CurrencyInfo from various sources like CultureInfo or RegionInfo. ```csharp // Create Currency unit Currency euro = Currency.FromCode("EUR"); // Create CurrencyInfo (for metadata about Currency) CurrencyInfo ci = CurrencyInfo.FromCode("EUR"); CurrencyInfo ci = CurrencyInfo.GetInstance(euro); // From Currency unit To CurrencyInfo Currencyinfo ci = CurrencyInfo.GetInstance(CultureInfo.CurrentCulture); Currencyinfo ci = CurrencyInfo.GetInstance(RegionInfo.CurrentRegion); Currencyinfo ci = CurrencyInfo.GetInstance(NumberFormatInfo.InvariantInfo); CurrencyInfo ci = CurrencyInfo.CurrentCurrency; Currency euro = ci; // Implicit cast to Currency Unit // From CultureInfo or RegionInfo CurrencyInfo ci1 = new CultureInfo("en-US").GetCurrencyInfo(); CurrencyInfo ci2 = new RegionInfo("US").GetCurrencyInfo(); ``` -------------------------------- ### Create Money Instances in C# Source: https://context7.com/remyduijkeren/nodamoney/llms.txt Demonstrates various ways to create Money instances, including using explicit currency codes, Currency objects, factory methods, and handling auto-rounding to the currency's minor unit. Also shows deconstruction of Money objects. ```csharp using NodaMoney; // Create money with explicit currency code var price = new Money(12.99m, "USD"); var euros = new Money(6.54m, "EUR"); // Create from Currency or CurrencyInfo var currency = Currency.FromCode("GBP"); var pounds = new Money(100m, currency); // Factory methods for common currencies var dollar = Money.USDollar(99.99m); var euro = Money.Euro(50m); var yen = Money.Yen(1000); var yuan = Money.Yuan(888.88m); var sterling = Money.PoundSterling(75.50m); // Auto-rounding to minor unit (EUR has 2 decimal places) var rounded = new Money(765.425m, "EUR"); // Results in EUR 765.42 // From different numeric types var fromDouble = new Money(19.99, "USD"); var fromLong = new Money(100L, "JPY"); // Deconstruction var (amount, curr) = price; Console.WriteLine($"Amount: {amount}, Currency: {curr.Code}"); // Output: Amount: 12.99, Currency: USD ``` -------------------------------- ### Initializing Currency and CurrencyInfo Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Shows how to create Currency units from currency codes and CurrencyInfo objects for currency metadata. CurrencyInfo can be obtained from various sources including culture information and existing Currency units. ```csharp // Create Currency unit Currency euro = Currency.FromCode("EUR"); // Create CurrencyInfo (for metadata about Currency) CurrencyInfo ci = CurrencyInfo.FromCode("EUR"); CurrencyInfo ci = CurrencyInfo.GetInstance(euro); // From Currency unit To CurrencyInfo Currencyinfo ci = CurrencyInfo.GetInstance(CultureInfo.CurrentCulture); Currencyinfo ci = CurrencyInfo.GetInstance(RegionInfo.CurrentRegion); Currencyinfo ci = CurrencyInfo.GetInstance(NumberFormatInfo.InvariantInfo); CurrencyInfo ci = CurrencyInfo.CurrentCurrency; Currency euro = ci; // Implicit cast to Currency Unit ``` -------------------------------- ### Configure MoneyContext via Configuration System Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney.DependencyInjection/README.md Loads configuration from the .NET configuration system. ```csharp // Option 1: Using pre-scoped configuration section var moneyContextSection = Configuration.GetSection("MoneyContext"); services.AddMoneyContext(moneyContextSection); // Option 2: Using root configuration with section path services.AddMoneyContext( Configuration, "MoneyContext" // or any custom path like "App:Finance:MoneyContext" ); ``` -------------------------------- ### Use Scoped MoneyContext in Controller Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney/Context/How-to-use.md Inject and use a scoped MoneyContext within a controller action. This example demonstrates creating a Money object with a specific currency and using the injected context for rounding. ```csharp [ApiController] [Route("[controller]")] public class CheckoutController : ControllerBase { private readonly MoneyContext _moneyContext; public CheckoutController(MoneyContext moneyContext) { _moneyContext = moneyContext; } [HttpPost] public IActionResult FinalizePurchase([FromBody] decimal totalAmount) { // Example currency setup var eur = new CurrencyInfo("EUR", 2, 0.01m); // Use the scoped MoneyContext var roundedMoney = new Money(totalAmount, eur, _moneyContext); return Ok(new { amount = roundedMoney.Round() }); } } ``` -------------------------------- ### Initialize Money Instances Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Various ways to instantiate Money objects, including explicit currency, helper methods, and culture-based defaults. ```csharp // Define money with explicit currency Money euros = new Money(6.54m, "EUR"); Money euros = new (6.54m, "EUR"); Money euros = new Money(6.54m, Currency.FromCode("EUR")); Money euros = new Money(6.54m, CurrencyInfo.FromCode("EUR")); // From existing money Money dollars = euros with { Currency = CurrencyInfo.FromCode("USD") }; Money myEuros = euros with { Amount = 10.12m }; // Define money explicit using helper method for most used currencies in the world Money euros = Money.Euro(6.54m); Money dollars = Money.USDollar(6.54m); Money pounds = Money.PoundSterling(6.54m); Money yens = Money.Yen(6); // Implicit Currency based on current culture/region. // When culture is 'NL-nl' code below results in Euros. Money euros = new Money(6.54m); Money euros = new (6.54m); Money euros = (Money)6.54m; // Auto-rounding to the minor unit will take place with MidpointRounding.ToEven // also known as banker's rounding Money euro = new Money(765.425m, "EUR"); // EUR 765.42 Money euro = new Money(765.425m, "EUR", MidpointRounding.AwayFromZero); // EUR 765.43 // Deconstruct money Money money = new Money(10m, "EUR"); var (amount, currency) = money; ``` -------------------------------- ### Canonical JSON Schema Example Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/proposals/12-serialization-ecosystem-completeness.md A proposed canonical JSON schema for representing monetary values. It includes currency code, amount as a string for precision, and an optional context name. The schema allows for opt-in numeric amounts for convenience, but string is recommended for lossless representation. ```json { "currencyCode": "USD", "amount": "1234.56", "contextName": "default"? } ``` -------------------------------- ### Configure Money Formatting Providers via Dependency Injection Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/cldr/feature-provider-model.md Use these extension methods to register the desired formatting provider during service configuration. ```csharp services.AddMoneyFormatting().UseCldr() | .UseCulture() | .UseIcu4n() ``` -------------------------------- ### Configure MoneyContext via Options Instance Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney.DependencyInjection/README.md Directly provides a MoneyContextOptions instance for registration. ```csharp var options = new MoneyContextOptions { DefaultCurrency = Currency.FromCode("USD"), RoundingStrategy = new HalfUpRounding() }; services.AddMoneyContext(options); ``` -------------------------------- ### Manage Exchange Rates and Currency Conversion in C# Source: https://context7.com/remyduijkeren/nodamoney/llms.txt Demonstrates creating exchange rates, performing bidirectional conversions, parsing strings, and handling conversion errors. ```csharp using NodaMoney; using NodaMoney.Exchange; // Create exchange rate EUR/USD at 1.2591 var rate = new ExchangeRate("EUR", "USD", 1.2591m); Console.WriteLine(rate); // EUR/USD 1.2591 // Alternative constructors var rate2 = new ExchangeRate( Currency.FromCode("GBP"), Currency.FromCode("JPY"), 188.50m ); // Convert EUR to USD var euros = Money.Euro(100.99m); Money usd = rate.Convert(euros); Console.WriteLine($"{euros} -> {usd}"); // EUR 100.99 -> USD 127.16 // Convert back USD to EUR (uses same rate inversely) Money backToEur = rate.Convert(usd); Console.WriteLine($"{usd} -> {backToEur}"); // USD 127.16 -> EUR 100.99 // Parse exchange rate from string ExchangeRate parsed = ExchangeRate.Parse("USD/JPY 149.50"); Console.WriteLine(parsed); // USD/JPY 149.50 // TryParse for safe parsing if (ExchangeRate.TryParse("GBP/EUR 1.1650", out ExchangeRate gbpEur)) { var pounds = Money.PoundSterling(500); Money converted = gbpEur.Convert(pounds); Console.WriteLine($"{pounds} = {converted}"); } // Deconstruction var (baseCurrency, quoteCurrency, value) = rate; Console.WriteLine($"Base: {baseCurrency.Code}, Quote: {quoteCurrency.Code}, Rate: {value}"); // Handle conversion errors try { var yen = Money.Yen(1000); Money invalid = rate.Convert(yen); // Throws - JPY not in EUR/USD pair } catch (ArgumentException ex) { Console.WriteLine($"Error: {ex.Message}"); } ``` -------------------------------- ### Register MoneyContext in Program.cs Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney/Context/How-to-use.md Register a default global MoneyContext and a customized MoneyContext using DI. The default context uses MidpointRounding.ToEven, while the custom one uses AwayFromZero with specific max scale and currency. ```csharp using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; var builder = WebApplication.CreateBuilder(args); // Example: Register a default global MoneyContext (MidpointRounding.ToEven) builder.Services.AddSingleton(MoneyContext.CreateDefault()); builder.Services.AddMoneyContext(options => { options.RoundingStrategy = new StandardRounding(MidpointRounding.AwayFromZero); options.MaxScale = 18; options.DefaultCurrency = CurrencyInfo.FromCode("USD"); }); var app = builder.Build(); app.MapGet("/", (Money money) => { // Example usage of a Money object with the global MoneyContext return $ ``` ```csharp Rounded Amount: {money.Round()} ``` ```csharp }); app.Run(); ``` -------------------------------- ### Creating and Registering Custom Currencies Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Illustrates the process of creating a custom currency with specific properties and registering it for use within the application domain. Custom currencies can also be created by modifying existing ones. ```csharp // Create custom currency CurrencyInfo myCurrency = CurrencyInfo.Create("BTA") with { Symbol = "$", Number = 1023, InternationalSymbol = "CC$", MinorUnit = MinorUnit.Two, EnglishName = "My Custom Currency", IsIso4217 = false, AlternativeSymbols = ["cc$"], IntroducedOn = new DateTime(2022, 1, 1), ExpiredOn = new DateTime(2030, 1, 1) }; // Fails because it's not registred var notExisting = CurrencyInfo.FromCode("BTA"); // throw exception // Register it for the life-time of the app domain CurrencyInfo.Register(myCurrency); var exists = CurrencyInfo.FromCode("BTA"); // returns myCurrency // Create custom currency based on existing currency CurrencyInfo myCurrency = CurrencyInfo.FromCode("EUR") with { Code = "EUA", EnglishName = "New Euro" }; CurrencyInfo.Register(myCurrency); var myEuro = Currency.FromCode("EUA"); // returns myCurrency // Replace currency for the life-time of the app domain CurrencyInfo oldEuro = CurrencyInfo.Unregister("EUR"); CurrencyInfo newEuro = oldEuro with { Symbol = "€U", EnglishName = "New Euro" }; CurrencyInfo.Register(newEuro); var myEuro = Currency.FromCode("EUR"); // returns newEuro ``` -------------------------------- ### Register and Inject MoneyContext Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney.DependencyInjection/README.md Registers the default MoneyContext and demonstrates how to inject it into a service. ```csharp // Add default MoneyContext with standard configuration services.AddMoneyContext(); // Access the MoneyContext in your services public class PaymentService { private readonly MoneyContext _moneyContext; public PaymentService(MoneyContext moneyContext) { _moneyContext = moneyContext; } // Use _moneyContext for calculations } ``` -------------------------------- ### Configure MoneyContext and Exchange Providers Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/proposals/README.md Sets up a global rounding strategy and builds a provider chain using in-memory rates with caching. ```csharp using NodaMoney; using NodaMoney.Context; using NodaMoney.Exchange; // 1) Configure a MoneyContext (precision/scale/rounding) var ctx = MoneyContext.CreateAndSetDefault(opt => { opt.RoundingStrategy = new StandardRounding(MidpointRounding.ToEven); opt.Precision = 19; opt.MaxScale = 4; // e.g., FastMoney-like }, name: "default"); // 2) Build an exchange provider chain var eur = CurrencyInfo.FromCode("EUR"); var usd = CurrencyInfo.FromCode("USD"); var gbp = CurrencyInfo.FromCode("GBP"); var seedRates = new[] { new ExchangeRate(eur, usd, 1.0850m, new RateContext("seed", AsOf: DateTimeOffset.UtcNow, Source: "fixture")), new ExchangeRate(eur, gbp, 0.8450m, new RateContext("seed", AsOf: DateTimeOffset.UtcNow, Source: "fixture")), }; IExchangeRateProvider provider = new CachedExchangeRateProvider( new CompositeExchangeRateProvider( new InMemoryExchangeRateProvider(seedRates, id: "in-memory-primary") // , new EcbHttpProvider(httpClient) // example of a real provider ), ttl: TimeSpan.FromMinutes(5)); // 3) Convert money with context-aware rounding var price = new Money(100m, eur); // a) Direct EUR → USD var usdPrice = price.ConvertTo(usd, provider); // b) With explicit query (e.g., historical date) var yesterday = DateTimeOffset.UtcNow.AddDays(-1); var usdYesterday = price.ConvertTo(usd, provider, new ConversionQuery( BaseCurrency: price.Currency, CounterCurrency: usd, At: yesterday, MoneyContext: MoneyContext.CurrentContext)); // c) Chained path (e.g., USD → GBP when only EUR legs exist) via custom provider that triangulates through EUR // See the Triangulating provider sketch below for that scenario. ``` -------------------------------- ### Unit tests for Money Split functionality Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/proposals/README.md Demonstrates various split scenarios including equal parts, ratio-based distribution, negative amounts, and remainder policies using xUnit and FluentAssertions. ```csharp public class MoneySplitSpec { [Fact] public void Split_EqualParts_SumsToOriginal() { using var _ = MoneyContext.CreateScope(o => { o.RoundingStrategy = new StandardRounding(MidpointRounding.ToEven); o.MaxScale = 2; }); var eur = CurrencyInfo.FromCode("EUR"); var m = new Money(1.00m, eur); var parts = m.Split(3); parts.Sum(p => p.Amount).Should().Be(m.Amount); parts.Select(p => p.Amount).Should().ContainInOrder(0.33m, 0.33m, 0.34m); } [Fact] public void Split_ByRatios_Proportional() { var eur = CurrencyInfo.FromCode("EUR"); var m = new Money(10.00m, eur); var parts = m.Split(SplitRemainderPolicy.LargestRemainder, 2, 3, 5); parts.Select(p => p.Amount).Should().ContainInOrder(2.00m, 3.00m, 5.00m); parts.Sum(p => p.Amount).Should().Be(10.00m); } [Fact] public void Split_NegativeAmount_DistributesCorrectly() { using var _ = MoneyContext.CreateScope(o => o.MaxScale = 2); var eur = CurrencyInfo.FromCode("EUR"); var m = new Money(-1.00m, eur); var parts = m.Split(3); parts.Select(p => p.Amount).Should().ContainInOrder(-0.33m, -0.33m, -0.34m); parts.Sum(p => p.Amount).Should().Be(-1.00m); } [Fact] public void Split_TowardZero_LeavesRemainder() { using var _ = MoneyContext.CreateScope(o => o.MaxScale = 2); var eur = CurrencyInfo.FromCode("EUR"); var m = new Money(1.00m, eur); var parts = m.Split(3, SplitRemainderPolicy.TowardZero); parts.Sum(p => p.Amount).Should().BeLessThan(m.Amount); // remainder left } } ``` -------------------------------- ### Handling Binary Operations with Different Contexts Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney/Context/How-to-use.md Demonstrates how to handle binary operations between Money instances with different MoneyContexts by aligning their contexts using the 'with' expression. ```csharp // Different contexts example var money1 = new Money(10.25m, "USD", MoneyContext.Create(MidpointRounding.ToEven)); var money2 = new Money(5.75m, "USD", MoneyContext.Create(MidpointRounding.AwayFromZero)); // This will throw an InvalidOperationException: var result = money1 + money2; // Instead, align contexts using the 'with' expression: var result = money1 + (money2 with { Context = money1.Context }); // Or make both use a different context: var retailContext = MoneyContext.Create(MidpointRounding.AwayFromZero); var money1WithRetailContext = money1 with { Context = retailContext }; var money2WithRetailContext = money2 with { Context = retailContext }; var result = money1WithRetailContext + money2WithRetailContext; ``` -------------------------------- ### Money Arithmetic and Comparison Source: https://github.com/remyduijkeren/nodamoney/blob/master/README.md Demonstrates basic instantiation, comparison, addition, subtraction, and increment/decrement operations. Note that operations between different currencies will throw an InvalidCurrencyException. ```csharp Money euro10 = Money.Euro(10); Money euro20 = Money.Euro(20); Money dollar10 = Money.USDollar(10); Money zeroDollar = Money.USDollar(0); // Compare money euro10 == euro20; // false euro10 != euro20; // true; euro10 == dollar10; // false; euro20 > euro10; // true; euro10 <= dollar10; // throws InvalidCurrencyException! zeroEuro == zeroDollar; // true; special zero handling // Add and Subtract Money euro30 = euro10 + euro20; Money euro10 = euro20 - euro10; Money m = euro10 + dollar10; // throws InvalidCurrencyException! Money euro10 = euro10 + zeroDollar; // doesn't throw when adding zero euro20 += euro10; // EUR 30 euro20 -= euro10; // EUR 10 // Add and Substract with implied Currency Context Money euro30 = euro10 + 20m; // decimal value is assumed to have the same currency context Money euro10 = euro20 - 10m; // decimal value is assumed to have the same currency context // Decrement and Increment by minor unit Money yen = new Money(765m, "JPY"); // the smallest unit is 1 yen Money euro = new Money(765.43m, "EUR"); // the smallest unit is 1 cent (1EUR = 100 cent) ++yen; // JPY 766 --yen; // JPY 765 ++euro; // EUR 765.44 --euro; // EUR 765.43 // Multiply Money m = euro10 * euro20; // doesn't compile! Money euro20 = euro10 * 2; Money discount = euro10 * 0.15m; // Divide decimal ratio = euro20 / euro10; Money euro5 = euro10 / 2; // Divide without losing money Money total = new Money(101m, "USD"); IEnumerable inShares = total.Split(4); // [USD 25, USD 25, USD 25, USD 26] IEnumerable byRatio = total.Split([2,1,3]); // [USD 33.67, USD 16.83, USD 50.50] // Modulus / Remainder Money total = new Money(105.50m, "USD"); Money unitPrice = new Money(20.00m, "USD"); // USD 20 * 5 = USD 100 Money remainder = total % unitPrice; // USD 5.50 ``` -------------------------------- ### Configure MoneyContext via Action Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney.DependencyInjection/README.md Configures the MoneyContext using an options delegate. ```csharp services.AddMoneyContext(options => { options.DefaultCurrency = Currency.FromCode("USD"); options.RoundingStrategy = new HalfUpRounding(); options.Precision = 28; options.MaxScale = 2; }); ``` -------------------------------- ### Retrieving All Currencies Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Demonstrates how to retrieve a list of all available currencies and how to perform fast lookups by currency code or symbol. ```csharp // Get all currencies var currencyList = CurrencyInfo.GetAllCurrencies(); // Fast lookup by code or symbol var findCurrencies = CurrencyInfo.GetAllCurrencies("$"); ``` -------------------------------- ### Create a Mock with NSubstitute Source: https://github.com/remyduijkeren/nodamoney/blob/master/tests/NodaMoney.Tests/README.md Use mocks to verify that specific commands were executed by the System Under Test. ```c# // Arrange var mockSomeThing = Substitute.For(); mockSomeThing.Execute(Arg.Any).Returns("Hello world!"); // Act // Assert mockSomeThing.Recieved().Execute(); // this will verify that the mock has be called ``` -------------------------------- ### Format and Parse Money with Custom Options Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/cldr/README.md Demonstrates formatting a Money object to a string with specific options and parsing a string into a Money object. Requires `IMoneyFormatter` and `IMoneyParser` services. Note the use of NBSP (non-breaking space) in the formatted output. ```csharp var options = new MoneyFormatOptions { Culture = CultureInfo.GetCultureInfo("nl-NL"), CurrencyDisplay = CurrencyDisplay.Symbol, SignDisplay = SignDisplay.Accounting, UseCashDigits = true }; IMoneyFormatter formatter = services.GetRequiredService(); var text = formatter.Format(new Money(1234.5m, Currency.EUR), options); // "€\u00A01.234,50" (NBSP) IMoneyParser parser = services.GetRequiredService(); if (parser.TryParse("$\u00A0(1,234.50)", CultureInfo.GetCultureInfo("en-US"), out var amount, new MoneyFormatOptions { SignDisplay = SignDisplay.Accounting })) { // amount == -1234.50 USD } ``` -------------------------------- ### Configure MoneyContext with Dependency Injection Source: https://context7.com/remyduijkeren/nodamoney/llms.txt Configure MoneyContext using an action, appsettings.json, or named contexts for multi-tenant scenarios. Inject MoneyContext into controllers or services. ```csharp using Microsoft.Extensions.DependencyInjection; using NodaMoney; using NodaMoney.Context; using NodaMoney.DependencyInjection; // In Program.cs or Startup.cs var builder = WebApplication.CreateBuilder(args); // Option 1: Configure with action builder.Services.AddMoneyContext(opt => { opt.DefaultCurrency = CurrencyInfo.FromCode("USD"); opt.RoundingStrategy = new StandardRounding(MidpointRounding.AwayFromZero); opt.MaxScale = 2; opt.EnforceZeroCurrencyMatching = true; }); // Option 2: Configure from appsettings.json // appsettings.json: // { // "MoneyContext": { // "DefaultCurrency": "EUR", // "Precision": 28, // "MaxScale": 2 // } // } builder.Services.AddMoneyContext(builder.Configuration.GetSection("MoneyContext")); // Option 3: Named contexts for multi-tenant scenarios builder.Services.AddMoneyContext(opt => { opt.DefaultCurrency = CurrencyInfo.FromCode("USD"); opt.MaxScale = 2; }, name: "us-retail"); builder.Services.AddMoneyContext(opt => { opt.DefaultCurrency = CurrencyInfo.FromCode("EUR"); opt.MaxScale = 4; }, name: "eu-wholesale"); var app = builder.Build(); // Inject MoneyContext in controllers/services app.MapGet("/price", (MoneyContext context) => { Console.WriteLine($"Default currency: {context.DefaultCurrency?.Code}"); var price = new Money(29.99m, "USD"); return price.ToString("C"); }); // Use named context app.MapGet("/wholesale-price", (IServiceProvider sp) => { using (MoneyContext.CreateScope("eu-wholesale")) { var price = new Money(19.9999m, "EUR"); // 4 decimal places return price.ToString(); } }); app.Run(); ``` -------------------------------- ### Use MoneyContext by instance or scope Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Demonstrates explicit context assignment and scoped context management for arithmetic operations. ```csharp // Explicit MoneyContext when creating a new Money object MoneyContext myOwnContext = MoneyContext.Create(opt => { opt.MaxScale = 2; opt.RoundingStrategy = new StandardRounding(MidpointRounding.AwayFromZero); opt.DefaultCurrency = CurrencyInfo.FromCode("EUR"); }); Money money1 = new Money(6.54m, "EUR", myOwnContext); // explicit MoneyContext Money money2 = new Money(6.54m, "EUR"); // implicit global MoneyContext // This will throw an InvalidOperationException because money1 and money2 have different contexts: var result = money1 + money2; // Instead, align contexts using the 'with' expression: var result = money1 + (money2 with { Context = money1.Context }); // Use MoneyContext in a scope (sets MoneyContext.ThreadContext for the duration of the scope): using (MoneyContext.CreateScope(myOwnContext)) { Money money3 = new Money(10.00m, "EUR"); Money money4 = new Money(5.00m, "EUR"); var total = money3 + money4; } ``` -------------------------------- ### Integrate RoundingProvider with MoneyContext Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/proposals/README.md Shows how MoneyContext can use an explicit RoundingStrategy, a RoundingProvider, or a default strategy. The query is built from context properties. ```csharp public sealed record MoneyContext { public IRoundingStrategy RoundingStrategy => Options.RoundingStrategy ?? Options.RoundingProvider?.GetRounding(BuildQuery()) ?? StandardRounding.ToEven; // sensible default private RoundingQuery BuildQuery() => new( Currency: DefaultCurrency, IsCash: Options.IsCashTransaction, Scale: MaxScale, When: DateTimeOffset.UtcNow, Attributes: Options.Attributes ); } ``` -------------------------------- ### Create a Stub with NSubstitute Source: https://github.com/remyduijkeren/nodamoney/blob/master/tests/NodaMoney.Tests/README.md Use stubs to provide input for the System Under Test without verifying calls. ```c# // Arrange var stubSomeThing = Substitute.For(); stubSomeThing.Execute(Arg.Any).Returns("Hello world!"); // Act // Assert // We don't assert a stub! ``` -------------------------------- ### Split Money by Ratio Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Demonstrates splitting a Money object into shares based on a provided ratio of integers. ```csharp IEnumerable byRatio = total.Split([2,1,3]); // [USD 33.67, USD 16.83, USD 50.50] ``` -------------------------------- ### FastMoney Usage and Conversions Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Demonstrates creating FastMoney instances, performing currency-safe arithmetic operations, and converting to and from Money, SqlMoney, and OLE Automation Currency types. Use FastMoney for performance-critical operations where 4-decimal precision is sufficient. ```csharp using NodaMoney; var eur = new FastMoney(10.1234m, "EUR"); var fee = new FastMoney(0.1000m, "EUR"); var total = eur + fee; // currency-safe operations // Convert to Money for formatting/rounding/presentation Money display = eur.ToMoney(); // or (Money)eur var text = display.ToString("C"); // Convert from Money (explicit) var money = new Money(12.34m, "EUR"); FastMoney fast = (FastMoney)money; // or new FastMoney(money) // Convert from SqlMoney SqlMoney sqlMoney = db.MoneyFromDb(); FastMoney? fast = FastMoney.FromSqlMoney(sqlMoney, Currency.FromCode("EUR")); // or (FastMoney?)sqlMoney // Convert to SqlMoney SqlMoney sqlMoney1 = fast.ToSqlMoney(Currency.FromCode("EUR")); // Convert from OLE Automation Currency long oaCurrency = db.CurrencyFromDb(); FastMoney fast = FastMoney.FromOACurrency(oaCurrency, Currency.FromCode("EUR")); // Convert to OLE Automation Currency long oaCurrency = fast.ToOACurrency(); ``` -------------------------------- ### Basic Arithmetic Operations with Money Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Demonstrates addition and subtraction of Money objects, including operations with decimal values that imply currency context. ```csharp euro20 += euro10; // EUR 30 euro20 -= euro10; // EUR 10 // Add and Substract with implied Currency Context Money euro30 = euro10 + 20m; // decimal value is assumed to have the same currency context Money euro10 = euro20 - 10m; // decimal value is assumed to have the same currency context ``` -------------------------------- ### Register Money Formatting Services Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/cldr/feature-examples.md Configures the money formatting provider during dependency injection registration. ```csharp // Default to CLDR provider when the package is referenced services.AddMoneyFormatting().UseCldr(); // Or explicitly use Culture fallback services.AddMoneyFormatting().UseCulture(); ``` -------------------------------- ### Implement an in-memory exchange rate provider Source: https://github.com/remyduijkeren/nodamoney/blob/master/features/proposals/README.md Use this provider for testing, fixtures, or bootstrapping scenarios. It supports simple inverse rate calculation if the direct rate is missing. ```csharp using System.Collections.Concurrent; using NodaMoney.Exchange; public sealed class InMemoryExchangeRateProvider : IExchangeRateProvider { private readonly ConcurrentDictionary<(string Base, string Ccy), ExchangeRate> _rates; public InMemoryExchangeRateProvider(IEnumerable rates, string? id = null) { Id = id ?? "in-memory"; _rates = new ConcurrentDictionary<(string, string), ExchangeRate>( rates.ToDictionary(r => (r.BaseCurrency.Code, r.CounterCurrency.Code), r => r)); } public string Id { get; } public bool TryGetRate(in ConversionQuery query, out ExchangeRate rate) { if (_rates.TryGetValue((query.BaseCurrency.Code, query.CounterCurrency.Code), out rate)) return true; // Simple inverse fallback if present if (_rates.TryGetValue((query.CounterCurrency.Code, query.BaseCurrency.Code), out var inverse)) { rate = new ExchangeRate( BaseCurrency: query.BaseCurrency, CounterCurrency: query.CounterCurrency, Factor: inverse.Factor == 0m ? 0m : 1m / inverse.Factor, Context: inverse.Context with { ProviderId = Id, Source = "inverse" } ); return true; } rate = null!; // out param return false; } } ``` -------------------------------- ### Perform Money Operations Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Demonstrates comparison, addition, and subtraction of Money objects, including handling of currency mismatches. ```csharp Money euro10 = Money.Euro(10); Money euro20 = Money.Euro(20); Money dollar10 = Money.USDollar(10); Money zeroDollar = Money.USDollar(0); // Compare money euro10 == euro20; // false euro10 != euro20; // true; euro10 == dollar10; // false; euro20 > euro10; // true; euro10 <= dollar10; // throws InvalidCurrencyException! zeroEuro == zeroDollar; // true; special zero handling // Add and Subtract Money euro30 = euro10 + euro20; Money euro10 = euro20 - euro10; Money m = euro10 + dollar10; // throws InvalidCurrencyException! Money euro10 = euro10 + zeroDollar; // doesn't throw when adding zero ``` -------------------------------- ### Split Money into Equal Shares Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Shows how to split a Money object into a specified number of equal shares, distributing any remainder. ```csharp // Divide without losing money Money total = new Money(101m, "USD"); IEnumerable inShares = total.Split(4); // [USD 25, USD 25, USD 25, USD 26] ``` -------------------------------- ### Set DefaultGlobal MoneyContext at Startup Source: https://github.com/remyduijkeren/nodamoney/blob/master/src/NodaMoney/Context/How-to-use.md Initialize the global MoneyContext by assigning a new MoneyContext instance to MoneyContext.DefaultGlobal. This ensures all subsequent Money calculations use this configuration unless overridden. ```csharp // Set global MoneyContext at application startup MoneyContext.DefaultGlobal = MoneyContext.Create(MidpointRounding.AwayFromZero, 18, 2); var context = MoneyContext.Create(opt => { opt.MaxScale = 4; opt.RoundingStrategy = new StandardRounding(MidpointRounding.AwayFromZero); opt.DefaultCurrency = CurrencyInfo.FromCode("USD"); }); ``` -------------------------------- ### Explicit Culture and Standard Format Specifiers for Money Source: https://github.com/remyduijkeren/nodamoney/blob/master/docs/README.md Demonstrates how to format Money objects using a specific CultureInfo object and standard format specifiers for currency, numbers, and compact representations. ```csharp // Explicit format for culture 'nl-NL' var ci = new CultureInfo("nl-NL"); yen.ToString(ci); // "¥ 2.765" euro.ToString(ci); // "€ 2.765,43" dollar.ToString(ci); // "$ 2.765,43" dinar.ToString(ci); // "BD 2.765,432" // Standard Formats when currenct culture is 'nl-NL' dollar.ToString("C"); // "$ 2.765,43" Currency symbol format dollar.ToString("C0");// "$ 2.765" Currency symbol format with precision specifier dollar.ToString("c"); // "$ 2.8K" Compact Currency symbol format dollar.ToString("I"); // "US$ 2.765,43" International Currency symbol format dollar.ToString("i"); // "US$ 2.8K" Compact International Currency symbol format dollar.ToString("G"); // "USD 2.765,43" ISO currency code format (= C but with currency code) dollar.ToString("g"); // "USD 2.8K" Compact ISO currency code format (international) dollar.ToString("L"); // "2.765,43 dollar" English name format dollar.ToString("l"); // "2.8K dollar" Compact English name format dollar.ToString("R"); // "USD 2,765.43" Round-trip format dollar.ToString("N"); // "2,765.43" Number format (no currency) dollar.ToString("F"); // "2765,43" Fixed point format (no currency) ```