Bodu.Financial.ExchangeRates.Caching 1.0.0

dotnet add package Bodu.Financial.ExchangeRates.Caching --version 1.0.0
                    
NuGet\Install-Package Bodu.Financial.ExchangeRates.Caching -Version 1.0.0
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Bodu.Financial.ExchangeRates.Caching" Version="1.0.0" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Bodu.Financial.ExchangeRates.Caching" Version="1.0.0" />
                    
Directory.Packages.props
<PackageReference Include="Bodu.Financial.ExchangeRates.Caching" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Bodu.Financial.ExchangeRates.Caching --version 1.0.0
                    
#r "nuget: Bodu.Financial.ExchangeRates.Caching, 1.0.0"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Bodu.Financial.ExchangeRates.Caching@1.0.0
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Bodu.Financial.ExchangeRates.Caching&version=1.0.0
                    
Install as a Cake Addin
#tool nuget:?package=Bodu.Financial.ExchangeRates.Caching&version=1.0.0
                    
Install as a Cake Tool

Bodu.Financial.ExchangeRates.Caching

API stability — Stable. The public API surface is committed; breaking changes are reserved for a major-version bump per SemVer.

A caching and composition layer for Bodu.Financial exchange-rate providers.

For the full walkthrough — quickstart, stacking (tiered read-through), aggregation, "when to use which", observability, and troubleshooting — see the Caching and aggregating exchange rates guide.

The provider classes (Yahoo, RBA, ECB, BoE) are pure fetchers — they know nothing of caching. This package adds two orthogonal pieces that each implement the same IDatedRateProvider contract (and the timeless IRateProvider), so they compose anywhere a provider is expected:

Caller
  │  IDatedRateProvider / IRateProvider
  ▼
AggregatingRateProvider     ── groups named children; routes per FX pair and
  │                                    combines them with a strategy (priority / average)
  ├── CachingRateProvider("RBA")  ── read-through cache over ONE source + ONE cache
  │       └── RbaRateProvider
  └── CachingRateProvider("ECB")
          └── EcbRateProvider

Caching (one cache = one provider)

CachingRateProvider wraps exactly one inner IDatedRateProvider over one single-provider IRateCache. On a lookup it first tries the cache's fresh rows (reusing FixedDatedRateProvider for date-resolution, inverse, and identity handling) and, on a miss, delegates to the inner provider and stores what it returns. It also exposes the timeless IRateProvider.GetRate(from, to), which resolves the current UTC date under CachingRateOptions.DefaultLookupOptions.

The cache is bound to a single provider, so its surface carries no provider argument:

Type Role
IRateCache Single-provider cache contract (Provider; GetRates/Store by pair).
RateCacheBase<TOptions> Storage-agnostic core: freshness filtering + merge/prune. No physical layout.
IFileRateCache File-storage seam (CacheDirectory, ResolveFilePath, ResolveDirectory, ResolvePartitionPath).
FileRateCacheBase<TOptions> File plumbing: layout-driven directory + file-name resolution, date partitioning, best-effort IO.
TomlFileRateCache Sealed TOML leaf — <dir>/<provider>/<from><to>.toml, decimals quoted for lossless round-trips, self-describing Provider/From/To header.
JsonFileRateCache Sealed JSON leaf — <dir>/<provider>/<from><to>.json, decimals as JSON numbers, the same self-describing header.
RateCacheFileLayout Where a pair's files live and whether they split by date: SingleFile (default), Yearly, Monthly, Daily, or Create(strategy, directoryFunc?, fileNameFunc?).
RateCachePartitionStrategy The date split a layout applies: Single, Yearly, Monthly, Daily, or Custom(...).
InMemoryRateCache In-memory cache reusing the same expiry mechanism; nothing persisted.
NullRateCache No-op cache (NullRateCache.Create(provider)).
CachingRateProvider Read-through caching decorator over one source + one cache.
CachingRateOptions Cache location, default + per-provider expiry, log levels, timeless lookup options.

Craft your own storage by implementing IRateCache, extending RateCacheBase<TOptions> (storage-agnostic), or extending FileRateCacheBase<TOptions> (a new file format).

File layout and date partitioning

Both file caches store one file per pair by default (<dir>/<provider>/<from><to>.toml or .json) and write a self-describing Provider/From/To header into each file, so a file no longer depends on its name or folder for identity. Set FileRateCacheOptions.Layout to control the folder hierarchy, file name, and whether a pair's rows split across files by date — RateCacheFileLayout.Yearly / .Monthly / .Daily write one file per calendar period under a per-pair folder (for example <dir>/RBA/AUDUSD/2023-01.toml), and RateCacheFileLayout.Create(...) builds a custom layout from a partition strategy and optional directory/file-name delegates:

var monthly = new TomlFileRateCache(new FileRateCacheOptions
{
    Provider = "RBA",
    CacheDirectory = "/var/cache/fx",
    Layout = RateCacheFileLayout.Monthly,
});

// JSON instead of TOML, same layout/partitioning surface.
var json = new JsonFileRateCache(new FileRateCacheOptions
{
    Provider = "RBA",
    CacheDirectory = "/var/cache/fx",
});

Aggregation (group many providers behind one entry point)

AggregatingRateProvider groups several named children and resolves each request through a pluggable IRateAggregationStrategy, with optional per-FX-pair routing.

  • PriorityFallbackStrategy — first child that resolves wins (the default).
  • AverageStrategy — arithmetic mean of every child that resolves, tagged Average.
  • Implement IRateAggregationStrategy for your own (weighted, median, …).
  • RateAggregationOptions.Routes maps a pair to an ordered child list and an optional per-pair strategy, so AUD/USD can prefer [RBA, ECB] while USD/GBP prefers [ECB, RBA].
  • TryGetProvider(name, out provider) resolves a specific child directly.
// The caching provider is storage-agnostic — you supply the IRateCache.
var rba = new CachingRateProvider(
    rbaSource, new TomlFileRateCache(new FileRateCacheOptions { Provider = "RBA", CacheDirectory = "/var/cache/fx" }), options);
var ecb = new CachingRateProvider(
    ecbSource, new TomlFileRateCache(new FileRateCacheOptions { Provider = "ECB", CacheDirectory = "/var/cache/fx" }), options);

var agg = new RateAggregationOptions();
agg.Routes[new CurrencyPair(CurrencyCode.AUD, CurrencyCode.USD)] = new CurrencyPairRoute(new[] { "RBA", "ECB" });
agg.Routes[new CurrencyPair(CurrencyCode.USD, CurrencyCode.GBP)] = new CurrencyPairRoute(new[] { "ECB", "RBA" });

IDatedRateProvider provider = new AggregatingRateProvider(
    new[]
    {
        new NamedDatedRateProvider("RBA", rba),
        new NamedDatedRateProvider("ECB", ecb),
    },
    agg);

For dependency-injection wiring, see Bodu.Financial.ExchangeRates.Caching.DependencyInjection.

Logging

Both the caching decorator and the aggregator log through Microsoft.Extensions.Logging. Pass an ILogger to the constructor, or let the *.DependencyInjection package wire one for you. When no logger is supplied it defaults to NullLogger.Instance, so logging is entirely opt-in and free when unused.

Single-date lookups happen on the read hot path, so their hit/miss diagnostics default to Trace; coarser range operations default to Debug. Every level is individually configurable on CachingRateOptions:

Event Default level Option property
A single-date lookup served from the cache Trace CacheHitLogLevel
A single-date cache miss resolved from a source and cached Trace CacheMissLogLevel
A range lookup served entirely from the cache Debug CacheRangeHitLogLevel
A range lookup refetched from a source and re-cached Debug CacheRangeRefetchLogLevel

The aggregator's route-selected, aggregated, and unresolved diagnostics are configurable on RateAggregationOptions.

Persistent backends may add their own diagnostics. The SQLite cache logs best-effort storage degradation at Warning under EventId 4520 (first failure immediately, then rate-limited to one per minute) so a silently-degrading cache is visible — see the Bodu.Financial.ExchangeRates.Caching.Sqlite package and the observability section of the caching guide.

Served-rate provenance & data age

Every RateLookupResult carries a RateProvenance describing where the rate came from:

  • Origin == Live — the value was resolved directly by a provider (for a cache-fronted provider, a miss the inner provider satisfied). Backend, CachedAtUtc, and Age are all null.
  • Origin == Cache — the value was served from a cache without consulting the provider. Backend is the cache's runtime identity, CachedAtUtc is the instant the served data was written to the cache, and Age is the elapsed time since then, clamped to be never negative (Age >= 0, since a row may be written marginally ahead of the lookup clock).

Backend is a diagnostic runtime identity (the cache type's name), not a stable key — do not parse it or branch on it as if it were part of the contract.

Two ages travel with a cache-served rate and are deliberately distinct:

  • Cache-write age — Provenance.Age (and Provenance.CachedAtUtc) is anchored to when the row was written to the cache.
  • Data age — ExchangeRate.FetchedAtUtc carries the upstream fetch instant end to end. A provider stamps it when it loads a rate; the cache persists it (as CachedRate.ObservedAtUtc) and restores it onto the rate it serves, so a cache-served rate reports the original fetch instant. Data age is now - ExchangeRate.FetchedAtUtc, independent of how recently the row happened to be (re)written to the cache.

FetchedAtUtc is excluded from ExchangeRate equality. The TOML and JSON file caches persist it as an optional ObservedAtUtc entry key per row; an entry written before the instant was tracked (or whose source never supplied one) has no key and reads back null. The SQLite and distributed caches persist it the same way through their own additive fields.

Part of the Bodu utility library.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 was computed.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Bodu.Financial.ExchangeRates.Caching:

Package Downloads
Bodu.Financial.ExchangeRates.Caching.Distributed

Distributed (Redis-capable) exchange-rate cache for Bodu.Financial. Provides DistributedRateCache, an IRateCache implementation over Microsoft.Extensions.Caching.Distributed.IDistributedCache that persists a single provider's rates and fetch-coverage windows as a per-pair JSON blob, with the same freshness, merge, coverage, and validation semantics as the in-memory, TOML, and SQLite caches. Includes the dependency-injection registrations (AddDistributedRateCache, AddRedisRateCache) on IFinancialServiceBuilder.

Bodu.Financial.ExchangeRates.Caching.Sqlite

SQLite-backed exchange-rate cache for Bodu.Financial. Provides SqliteRateCache, an IRateCache implementation that persists a single provider's rates and fetch-coverage windows in a SQLite database, with the same freshness, merge, coverage, and validation semantics as the in-memory and TOML caches. Includes the dependency-injection registration (AddSqliteRateCache) on IFinancialServiceBuilder.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
1.0.0 52 9/24/2026
0.7.0 113 9/24/2026