Runnable samples
The repository ships runnable, self-contained sample projects for the financial packages under
samples/Financial/. Every
sample runs fully offline - exchange-rate samples build their providers from committed
static data files instead of calling live feeds - and each carries a clearly fenced comment
block showing how to switch to a real web provider. The samples are members of bodu.slnx and
are built and executed by CI, so the code they show cannot drift from the current API.
Run any sample from the repository root:
dotnet run --project samples/Financial/<SampleName>
The samples
Bodu.Financial.Samples.MoneyBasics
The core value types, offline with in-code data only. Covers the three-tier rounding model
(Money<TCurrency> per step, CalculatedMoney deferred,
Fraction<BigInteger> exact via MultiplyExact), the typed↔runtime bridges (As<T>,
TryAs<T>, casts), sum-preserving allocation and cash rounding, the format-specifier
vocabulary and MoneyFormatterBuilder, the four
MoneyParseMode levels, MoneyBag ledgers with
ConvertToWithAudit, and the three FinancialJsonPolicy
shapes. Packages: Bodu.Financial.
Bodu.Financial.Samples.OfflineRates
The flagship static-rate-file pattern: a committed CSV poured through
RateTableBuilder into an immutable
RateBook, served through
FixedDatedRateProvider - the same contracts the live web
providers implement. Works the four RateLookupOptions
date-resolution modes over weekend gaps and converts typed and runtime money through dated
rates. Packages: Bodu.Financial.
Bodu.Financial.Samples.CachedRates
The caching layer against an offline source: the read-through
CachingRateProvider, coverage-based range serving
(including negative caching of empty windows), tiered stacking (in-memory L1 over durable file
L2, surviving a simulated restart), and the
RateHistoryAvailability clamping model. A closing scenario
runs the two durable backends: a SQLite-backed cache read back by a second provider after the
first is disposed, and two providers over one IDistributedCache standing in for two
processes - with the result's RateProvenance still reporting Origin=Cache so a stored
answer is never mistaken for a fresh fetch. A small counting decorator makes hit-vs-fetch
behaviour visible throughout. Packages: Bodu.Financial,
Bodu.Financial.ExchangeRates.Caching, …Caching.Sqlite, …Caching.Distributed.
Bodu.Financial.Samples.AggregatedRates
Multi-provider aggregation over two offline feeds with complementary coverage:
AggregatingRateProvider with priority fallback,
AverageStrategy (and its synthetic provenance
label), per-pair CurrencyPairRoute overrides, and
the fluent AddAggregatedRateProvider DI builder with keyed per-child resolution. Packages:
Bodu.Financial, Bodu.Financial.ExchangeRates.Caching, Bodu.Financial.DependencyInjection.
Bodu.Financial.Samples.CurrencyServices
Currency services and host wiring: the ambient
CurrencyResolution seam (PushScoped over a restricted-lookup
decorator), named MonetaryContext registrations, and the
AddFinancialService composition root with UseCurrencyResolution. Packages:
Bodu.Financial, Bodu.Financial.DependencyInjection.
Bodu.Financial.Samples.JsonSerialization
System.Text.Json integration from the Bodu.Financial.Serialization.Json companion:
AddFinancialJsonConverters() round-tripping Money, Money<TCurrency>, and MoneyBag;
ExchangeRate and CurrencyPair converters; the FinancialJsonPolicy Strict/Lenient/Compact
wire shapes; and the AddFinancialJson() DI registration exposing keyed JsonSerializerOptions
(key "Financial"). Packages: Bodu.Financial, Bodu.Financial.Serialization.Json.
Bodu.Financial.Samples.UnitPricing
Higher-than-currency precision for unit prices - a six-decimal-place share price in two-decimal
USD - and preserving it through serialization: a Money carrying an explicit
scale (settled through a custom-scale MonetaryContext) whose Strict JSON shape
records a scale property, unrounded CalculatedMoney written verbatim, and
six-place prices round-tripping inside a POCO price list - see
Monetary precision & unit pricing. Packages:
Bodu.Financial, Bodu.Financial.Serialization.Json.
Bodu.Financial.Samples.CustomProvider (+ .Test)
Consumer extensibility: a custom CsvFileRateProvider in the recommended shape (builder →
book → delegated fixed provider), used directly, through the conversion extensions, and under
the caching decorator. Its companion test project derives
DatedRateProviderContractTests<CsvFileRateProvider> from the in-repo
Bodu.Financial.ExchangeRates.Testing project - see Testing your own provider.
Packages: Bodu.Financial, Bodu.Financial.ExchangeRates.Caching; the test project also references
the in-repo Bodu.Financial.ExchangeRates.Testing project, which is not published to NuGet.
Bodu.Financial.Samples.LiveRates
The one sample that goes online (and is therefore excluded from the CI samples run): it
fetches real published rates from a live web provider for a computed historical date - the most
recent Wednesday at least five days old, with a PreviousWithin(5) tolerance so a published
fixing is near-certain - plus that date's trailing week as a single range read. The ECB feed is
active by default; RBA, BoE, Yahoo, OFX, OANDA, XE, Fixer, exchangerate.host, FRED, and IMF are
comment-switchable blocks (the API-key sources need a key set), and every
provider package is referenced so the switch is a comment flip. Packages: one of the
Bodu.Financial.ExchangeRates.<Source> provider packages.
Offline by default, live by choice
With the exception of LiveRates above, the samples never touch the network. Where a live feed
could be used, a fenced comment block shows the exact switch:
// --- To use the live Reserve Bank of Australia feed instead -----------------
// 1. dotnet add package Bodu.Financial.ExchangeRates.Rba
// 2. Replace the offline source with:
//
// using var rba = new RbaRateProvider(new RbaRateProviderOptions());
// await rba.LoadRangeAsync(new DateOnly(2024, 1, 1), new DateOnly(2024, 6, 30));
// ----------------------------------------------------------------------------
Because every provider in the family serves the same IDatedRateProvider / IRateProvider contracts, the rest of each sample works unchanged after the switch.