Table of Contents

CachingRateOptions Class

Definition

Namespace
Bodu.Financial.ExchangeRates.Caching
Assembly
Bodu.Financial.ExchangeRates.Caching.dll
Package
Bodu.Financial.ExchangeRates.Caching 1.0.0
Source
CachingRateOptions.cs

Configures a CachingRateProvider: the on-disk cache location, the default time a cached rate stays fresh, and per-provider overrides of that default.

public sealed class CachingRateOptions
Inheritance
CachingRateOptions
Inherited Members
Extension Methods

Examples

var options = new CachingRateOptions
{
    CacheDirectory = "/var/cache/fx",        // null/blank -> a bodu-exchange-rates temp folder
    DefaultExpiry = TimeSpan.FromHours(24),  // applies to any source without an override
};

// Override the default for specific sources, keyed by the name the source is cached under.
options.ProviderExpiry["RBA"] = TimeSpan.FromDays(7);
options.ProviderExpiry["Yahoo"] = TimeSpan.FromHours(1);

TimeSpan rbaExpiry = options.GetExpiry("RBA");      // 7 days
TimeSpan ecbExpiry = options.GetExpiry("ECB");      // 24 hours (the default)

Remarks

Every member carries a working default, so the options bind cleanly through Microsoft.Extensions.Options and require no configuration for the common case.

The Cache*LogLevel members set the LogLevel at which each cache diagnostic is logged, so consumers can re-tune verbosity per concern without category-wide log filters. The per-lookup hit and miss events default to Information because they run on the read hot path; the range events default to Debug. The per-serve RateProvenanceLogLevel records the lineage of every served rate - live versus cache hit, the backend identity, and the served data's age - and also defaults to Debug. Set any member to None to suppress that event entirely.

Constructors

CachingRateOptions()

public CachingRateOptions()

Properties

CacheDirectory

Gets or sets the directory used by the on-disk cache.

public string? CacheDirectory { get; set; }

Property Value

string

The cache directory, or null to use a bodu-exchange-rates folder under the system temporary path.

CacheHitLogLevel

Gets or sets the level at which a single-date lookup served from the cache is logged.

public LogLevel CacheHitLogLevel { get; set; }

Property Value

LogLevel

The log level; defaults to Information.

CacheMissLogLevel

Gets or sets the level at which a single-date cache miss resolved from a source and then cached is logged.

public LogLevel CacheMissLogLevel { get; set; }

Property Value

LogLevel

The log level; defaults to Information.

CacheRangeHitLogLevel

Gets or sets the level at which a range lookup served entirely from the cache is logged.

public LogLevel CacheRangeHitLogLevel { get; set; }

Property Value

LogLevel

The log level; defaults to Debug.

CacheRangeRefetchLogLevel

Gets or sets the level at which a range lookup refetched from a source and re-cached is logged.

public LogLevel CacheRangeRefetchLogLevel { get; set; }

Property Value

LogLevel

The log level; defaults to Debug.

DefaultExpiry

Gets or sets the default duration a cached rate stays fresh, applied to any provider without a specific override in ProviderExpiry.

public TimeSpan DefaultExpiry { get; set; }

Property Value

TimeSpan

The default caching duration; defaults to 24 hours.

Remarks

Validation requires only a strictly positive duration; there is no upper bound. This property and the ProviderExpiry overrides accept an arbitrarily large value, so an extreme duration leaves cached rows fresh indefinitely and effectively disables expiry for the affected provider.

DefaultLookupOptions

Gets or sets the lookup options applied by the timeless GetRate(string, string) surface, which resolves the rate for the current UTC date.

public RateLookupOptions DefaultLookupOptions { get; set; }

Property Value

RateLookupOptions

The lookup options used for timeless lookups; defaults to Exact.

ExpiryJitter

Gets or sets the maximum fraction of a pair's caching duration that is deterministically shaved off per pair, so entries warmed together do not all expire - and refetch - at the same instant.

public double ExpiryJitter { get; set; }

Property Value

double

A fraction in [0, 1); defaults to 0, which disables jitter and preserves the exact configured expiry.

Remarks

The reduction is derived from a stable hash of the provider and pair (not from randomness), so a given pair's effective expiry is identical across instances and processes and deterministic under test. Jitter only ever shortens the duration, never extends it, so no rate is served longer than the configured expiry allows; the trade-off is that a pair may refetch up to this fraction of its expiry early. Inverse-pair probes jitter by the inverse pair's own key, matching the data actually being read.

HistoryClampLogLevel

Gets or sets the level at which a fetch skipped or clamped because of the inner source's advertised history (see RespectHistoryAvailability) is logged.

public LogLevel HistoryClampLogLevel { get; set; }

Property Value

LogLevel

The log level; defaults to Debug.

ProviderExpiry

Gets the per-provider expiry overrides, keyed by the provider name supplied to the caching provider.

public IDictionary<string, TimeSpan> ProviderExpiry { get; }

Property Value

IDictionary<string, TimeSpan>

A map from provider name to the duration its cached rates stay fresh. Providers absent from the map use DefaultExpiry.

RateProvenanceLogLevel

Gets or sets the level at which the provenance of every served rate is logged.

public LogLevel RateProvenanceLogLevel { get; set; }

Property Value

LogLevel

The log level; defaults to Debug.

Remarks

This per-serve diagnostic records the lineage of each result - whether it was resolved live or from the cache, the cache backend that served it, and the served data's age - and so is richer than the per-concern hit and miss events on the read hot path. Set it to None to suppress provenance entirely.

RefreshAheadFraction

Gets or sets the fraction of a pair's effective caching duration after which a cache hit, while still served immediately, additionally schedules a single background refresh of the served data, so a hot pair is refetched before it expires and no caller ever absorbs the refetch latency.

public double RefreshAheadFraction { get; set; }

Property Value

double

A fraction in [0, 1); defaults to 0, which disables refresh-ahead entirely and preserves the plain serve-until-expiry behaviour.

Remarks

Refresh-ahead is access-triggered: no timers run, and only a lookup that is actually served from the cache can schedule a refresh. When a hit's served data is older than this fraction of the pair's effective (post-jitter) expiry, at most one background refresh per pair and window is started; concurrent aged hits for the same key join the pending refresh rather than duplicating it. Because the fraction is below 1, the refresh always begins before the entry expires, so a continuously hot pair never surfaces a miss.

A background refresh that fails is swallowed after logging - the hit it piggybacked on was already served - and the next aged hit schedules a fresh attempt. Single-date hits served from the inverse pair's rows evaluate the threshold against the requested pair's effective expiry, a deliberate approximation that keeps the trigger cheap. Disposing the provider prevents new refreshes from being scheduled and abandons pending ones without draining them.

RespectHistoryAvailability

Gets or sets a value indicating whether the provider consults the inner source's advertised RateHistoryAvailability before delegating a miss, skipping or clamping fetches for dates the source has declared it cannot serve.

public bool RespectHistoryAvailability { get; set; }

Property Value

bool

true to respect the advertised history; defaults to true.

Remarks

The clamp applies only when the inner provider implements IHistoricalRateProvider; a non-aware inner is treated as unbounded and never skipped. A skipped single-date lookup surfaces as an ordinary miss, and a range window that starts before the advertised earliest date is fetched from that earliest date while the whole requested window is still recorded as covered, so the unavailable prefix is not refetched until normal expiry. Disable this to forward every request to the inner source unchanged.

SkipInverseRangeProbeWhenDirectCovered

Gets or sets a value indicating whether a range lookup that misses the direct pair's coverage skips the inverse-pair probe when the direct pair holds any fresh coverage at all, probing the inverse only when the direct pair's coverage is completely empty.

public bool SkipInverseRangeProbeWhenDirectCovered { get; set; }

Property Value

bool

true to skip the inverse probe when the direct pair carries fresh coverage; defaults to false, matching the historical behaviour of always probing the inverse on a direct miss.

Remarks

On a direct-coverage miss the provider normally issues a second backend read for the inverse pair. For a workload that only ever fetches one orientation - the common case - that probe is a guaranteed-empty read doubling backend I/O on every refetch. Enabling this option treats any fresh direct coverage as evidence the pair is actively fetched in the direct orientation and goes straight to the refetch, at the cost of a redundant refetch in the rare configuration where the inverse pair alone holds complete coverage for the window. The option has no effect when DefaultLookupOptions disallows inverse serves.

Methods

GetExpiry(string)

Resolves the caching duration for a provider, returning its specific override when present and the default otherwise.

public TimeSpan GetExpiry(string provider)

Parameters

provider string

The provider name.

Returns

TimeSpan

The duration cached rates for provider stay fresh.

TryValidate(out string?)

Attempts to validate the options without throwing, reporting the first invariant that is violated.

public bool TryValidate(out string? error)

Parameters

error string

When this method returns false, a message describing the first violated invariant; otherwise null.

Returns

bool

true when every invariant holds; otherwise false.

Remarks

The throwing Validate() method is expressed in terms of this method, and the dependency-injection registration wires it into ValidateOnStart so misconfiguration fails fast at application startup.

Validate()

Validates the option values, throwing when a rule is violated.

public void Validate()

Remarks

This throwing form preserves the ParamName of the offending option, which callers rely on. The dependency-injection registration instead wires TryValidate(out string?) into ValidateOnStart, which reports the same invariants without throwing.

Exceptions

ArgumentException

Thrown when DefaultExpiry or any ProviderExpiry entry is not strictly positive, when ProviderExpiry or DefaultLookupOptions is null, or when any Cache*LogLevel, HistoryClampLogLevel, or RateProvenanceLogLevel is not a defined LogLevel.

Applies to

ProductVersions
.NET8, 10