WebRateProvider Class
Definition
- Namespace
- Bodu.Financial.ExchangeRates
- Assembly
- Bodu.Financial.ExchangeRates.dll
- Package
- Bodu.Financial.ExchangeRates 1.0.0
- Source
- WebRateProvider.cs
Provides the shared machinery for an exchange-rate provider that materializes a remote feed into an in-memory RateBook snapshot: it owns the accumulator and the immutable snapshot, implements the full synchronous and asynchronous lookup matrix once, and optionally owns the HttpClient used to reach the feed. Derived types supply only the feed-specific fetch.
public abstract class WebRateProvider : IDatedRateProvider, IRateProvider, IPairRateLoader, IHistoricalRateProvider, IDisposable
- Inheritance
-
WebRateProvider
- Implements
- Derived
- Inherited Members
- Extension Methods
Remarks
HttpClient ownership. A derived provider constructed without a caller-supplied client builds and
owns its own (typically through Create(string?, TimeSpan, long)),
passing it to this base so it is disposed with the provider. A provider constructed with a caller-supplied client
passes null as the owned client, leaving its lifetime - and its HTTP contract (user agent,
timeout) - to the caller. This is the path a dependency-injection registration uses, supplying a client from
IHttpClientFactory.
Lookup matrix. All getters resolve against the current immutable snapshot. The synchronous point and range getters block to fetch on a miss only when AllowSynchronousNetworkAccess is enabled; the asynchronous getters always await a coverage-aware fetch. The undated getters resolve the most recent rate as of the current instant supplied by the time provider. Derived types implement the feed-specific EnsureLoadedAsync(CurrencyPair, DateOnly, DateOnly, CancellationToken) and IsLoaded(CurrencyPair, DateOnly, DateOnly) and accumulate fetched observations through AddObservations(IEnumerable<ExchangeRate>, DateTimeOffset?) followed by RebuildSnapshot(), all under SyncRoot.
using Bodu.Financial;
// A derived provider supplies only the feed-specific fetch and coverage check.
sealed class MyFeedProvider : WebRateProvider
{
public MyFeedProvider(HttpClient client) : base(client, timeProvider: null) { }
protected override string ProviderId => "MyFeed";
protected override bool AllowSynchronousNetworkAccess => false;
protected override TimeSpan DefaultLookback => TimeSpan.FromDays(7);
protected override bool IsLoaded(CurrencyPair pair, DateOnly startDate, DateOnly endDate) =>
false; // real implementations track covered windows
protected override async ValueTask EnsureLoadedAsync(
CurrencyPair pair, DateOnly startDate, DateOnly endDate, CancellationToken cancellationToken)
{
IEnumerable<ExchangeRate> fetched = await FetchFromFeedAsync(pair, startDate, endDate, cancellationToken);
lock (SyncRoot)
{
AddObservations(fetched, DateTimeOffset.UtcNow);
RebuildSnapshot();
}
}
}
Constructors
WebRateProvider(HttpClient?, TimeProvider?)
Initializes a new instance of the WebRateProvider class.
protected WebRateProvider(HttpClient? ownedHttpClient, TimeProvider? timeProvider)
Parameters
ownedHttpClientHttpClientThe HTTP client this provider should own and dispose, or null when the client is caller-supplied and its lifetime is the caller's responsibility.
timeProviderTimeProviderThe time source used to resolve the current instant for the undated surfaces. null selects System.
Properties
AllowSynchronousNetworkAccess
Gets a value indicating whether a synchronous lookup may block to fetch a missing window on demand.
protected abstract bool AllowSynchronousNetworkAccess { get; }
Property Value
Remarks
When enabled, the synchronous getters block on the async fetch, which can deadlock if invoked on a thread carrying a captured SynchronizationContext (classic ASP.NET, a WPF/WinForms UI thread). The synchronous path guards against this by throwing InvalidOperationException when Current is non-null; enable this only for code that calls the getters from a thread-pool thread (or use the asynchronous API).
DefaultLookback
Gets the look-back window used when a single-rate lookup must fetch on demand; the provider fetches the window ending on the requested date and spanning this duration.
protected abstract TimeSpan DefaultLookback { get; }
Property Value
- TimeSpan
The look-back window.
HistoryAvailability
Gets the history depth this provider advertises: how far back it can serve rates.
public virtual RateHistoryAvailability HistoryAvailability { get; }
Property Value
- RateHistoryAvailability
The advertised availability; the base reports Unbounded. A derived type whose feed publishes only a bounded window overrides this to declare it.
ProviderId
Gets the provider identifier stamped on every rate this provider produces.
protected abstract string ProviderId { get; }
Property Value
- string
The provider identifier.
SyncRoot
Gets the synchronization object guarding the accumulator and snapshot; derived types lock on it while checking coverage and accumulating a fetch so the fetch publishes atomically.
protected object SyncRoot { get; }
Property Value
- object
The synchronization object.
TimeProvider
Gets the time source used to resolve the current instant.
protected TimeProvider TimeProvider { get; }
Property Value
- TimeProvider
The time provider.
Methods
AddObservations(IEnumerable<ExchangeRate>, DateTimeOffset?)
Upserts a batch of fetched observations into the accumulator under the provider's identifier, invoking OnObservationIngested(ExchangeRate) for each. The caller must hold SyncRoot and follow the batch with RebuildSnapshot().
protected int AddObservations(IEnumerable<ExchangeRate> rates, DateTimeOffset? fetchedAtUtc = null)
Parameters
ratesIEnumerable<ExchangeRate>The observations to upsert.
fetchedAtUtcDateTimeOffset?The UTC instant at which the batch was downloaded, recorded at the series grain as load provenance, or null when not tracked.
Returns
- int
The number of observations upserted.
CreateRangeInvertedException(DateOnly, DateOnly)
Creates the exception thrown when an inclusive date range is inverted. Derived types may override to throw a feed-specific exception type.
protected virtual Exception CreateRangeInvertedException(DateOnly startDate, DateOnly endDate)
Parameters
startDateDateOnlyThe inclusive start of the range.
endDateDateOnlyThe inclusive end of the range.
Returns
- Exception
The exception to throw.
Dispose()
Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
public void Dispose()
Dispose(bool)
Releases the resources used by this provider; disposes the owned HttpClient when one was created.
protected virtual void Dispose(bool disposing)
Parameters
EnsureLoadedAsync(CurrencyPair, DateOnly, DateOnly, CancellationToken)
Ensures the inclusive window for a pair has been fetched and accumulated, idempotently. Implementations perform their own coverage check, request coalescing, fetch, and accumulation (via AddObservations(IEnumerable<ExchangeRate>, DateTimeOffset?) and RebuildSnapshot() under SyncRoot).
protected abstract ValueTask EnsureLoadedAsync(CurrencyPair pair, DateOnly startDate, DateOnly endDate, CancellationToken cancellationToken)
Parameters
pairCurrencyPairThe currency pair to ensure data for. Feeds that fetch by range, feed, or file may ignore it.
startDateDateOnlyThe inclusive start of the window.
endDateDateOnlyThe inclusive end of the window.
cancellationTokenCancellationTokenA token to observe while awaiting the fetch.
Returns
- ValueTask
A task that completes when the window has been loaded.
FormatRateNotFound(string, string, DateOnly)
Formats the message for the KeyNotFoundException thrown when a single-rate lookup fails. Derived types may override to use a feed-specific resource string.
protected virtual string FormatRateNotFound(string fromIsoCode, string toIsoCode, DateOnly date)
Parameters
fromIsoCodestringThe source-currency ISO code.
toIsoCodestringThe destination-currency ISO code.
dateDateOnlyThe requested date.
Returns
- string
The exception message.
GetLoadedBook()
Returns the immutable book of every observation this provider has fetched and accumulated so far.
public RateBook GetLoadedBook()
Returns
Remarks
The returned book is immutable and pinned at call time: later fetches replace the provider's internal book wholesale and never mutate an instance already handed out, so the result is safe to share across threads, to query after the provider is disposed, and to use as a deterministic offline snapshot. Call again after further loads to observe newly accumulated data; no reference identity is promised across calls.
The book is the composable export primitive: rewrap it with FixedDatedRateProvider(RateBook, IEnumerable<string>) to apply a custom provider-priority policy, or use ToBuilder() to edit a copy. For the ready-to-query equivalent see GetLoadedSnapshot().
Exceptions
- ObjectDisposedException
Thrown when the provider has been disposed.
GetLoadedPairs()
Gets the distinct currency pairs for which observations have been accumulated so far.
public IReadOnlyCollection<CurrencyPair> GetLoadedPairs()
Returns
- IReadOnlyCollection<CurrencyPair>
A snapshot of the loaded pairs; empty when nothing has been loaded.
GetLoadedSnapshot()
Returns an immutable, ready-to-query provider over every observation this provider has fetched and accumulated so far.
public FixedDatedRateProvider GetLoadedSnapshot()
Returns
- FixedDatedRateProvider
The current immutable FixedDatedRateProvider snapshot; it resolves no rates until the first fetch completes.
Remarks
The snapshot is the instance this provider itself reads from - it is rebuilt once per fetch, so handing it out costs nothing - and it is pinned at call time: later fetches replace it wholesale and never mutate an instance already handed out. Use it for deterministic, offline, disposal-independent lookups over what has been loaded. Its Book is the same instance GetLoadedBook() returns at the same moment.
Exceptions
- ObjectDisposedException
Thrown when the provider has been disposed.
GetRate(string, string, RateLookupOptions?)
Resolves the most recent available exchange rate from fromIsoCode to
toIsoCode under options, throwing if no rate is available.
public RateLookupResult GetRate(string fromIsoCode, string toIsoCode, RateLookupOptions? options = null)
Parameters
fromIsoCodestringThe source-currency ISO-style code.
toIsoCodestringThe destination-currency ISO-style code.
optionsRateLookupOptionsThe lookup rules to apply. null selects the implementation's default most-recent policy.
Returns
- RateLookupResult
The resolved RateLookupResult.
Exceptions
- ArgumentNullException
Thrown if
fromIsoCodeortoIsoCodeis null.- ArgumentException
Thrown if either ISO code is not a three-character uppercase ASCII code, or if
optionsis invalid.- KeyNotFoundException
Thrown if no rate is available for the request.
GetRate(string, string, DateOnly, RateLookupOptions?)
Resolves the exchange rate from fromIsoCode to toIsoCode on
date under options, throwing if no rate is available.
public RateLookupResult GetRate(string fromIsoCode, string toIsoCode, DateOnly date, RateLookupOptions? options = null)
Parameters
fromIsoCodestringThe source-currency ISO-style code.
toIsoCodestringThe destination-currency ISO-style code.
dateDateOnlyThe calendar date for which a rate is required.
optionsRateLookupOptionsThe lookup rules to apply, including date-resolution policy and tolerance. null is treated as Exact.
Returns
- RateLookupResult
The resolved RateLookupResult.
Exceptions
- ArgumentNullException
Thrown if
fromIsoCodeortoIsoCodeis null.- ArgumentException
Thrown if either ISO code is not a three-character uppercase ASCII code, or if
optionsis invalid (for example, Exact with non-zero tolerance).- ArgumentOutOfRangeException
Thrown if
optionscontains a negative tolerance or an undefined enum value.- KeyNotFoundException
Thrown if no rate is available for the request under the supplied options.
GetRateAsync(string, string, RateLookupOptions?, CancellationToken)
Asynchronously resolves the most recent available exchange rate from fromIsoCode to
toIsoCode under options, throwing if no rate is available.
public ValueTask<RateLookupResult> GetRateAsync(string fromIsoCode, string toIsoCode, RateLookupOptions? options = null, CancellationToken cancellationToken = default)
Parameters
fromIsoCodestringThe source-currency ISO-style code.
toIsoCodestringThe destination-currency ISO-style code.
optionsRateLookupOptionsThe lookup rules to apply. null selects the implementation's default most-recent policy.
cancellationTokenCancellationTokenA token to observe while awaiting the operation.
Returns
- ValueTask<RateLookupResult>
A task that yields the resolved RateLookupResult.
Exceptions
- ArgumentNullException
Thrown if
fromIsoCodeortoIsoCodeis null.- ArgumentException
Thrown if either ISO code is not a three-character uppercase ASCII code, or if
optionsis invalid.- KeyNotFoundException
Thrown if no rate is available for the request.
GetRateAsync(string, string, DateOnly, RateLookupOptions?, CancellationToken)
Asynchronously resolves the exchange rate from fromIsoCode to toIsoCode
on date under options, throwing if no rate is available.
public ValueTask<RateLookupResult> GetRateAsync(string fromIsoCode, string toIsoCode, DateOnly date, RateLookupOptions? options = null, CancellationToken cancellationToken = default)
Parameters
fromIsoCodestringThe source-currency ISO-style code.
toIsoCodestringThe destination-currency ISO-style code.
dateDateOnlyThe calendar date for which a rate is required.
optionsRateLookupOptionsThe lookup rules to apply, including date-resolution policy and tolerance. null is treated as Exact.
cancellationTokenCancellationTokenA token to observe while awaiting the operation.
Returns
- ValueTask<RateLookupResult>
A task that yields the resolved RateLookupResult.
Exceptions
- ArgumentNullException
Thrown if
fromIsoCodeortoIsoCodeis null.- ArgumentException
Thrown if either ISO code is not a three-character uppercase ASCII code, or if
optionsis invalid.- ArgumentOutOfRangeException
Thrown if
optionscontains a negative tolerance or an undefined enum value.- KeyNotFoundException
Thrown if no rate is available for the request under the supplied options.
GetRates(string, string, DateOnly, DateOnly)
Returns every available rate from fromIsoCode to toIsoCode whose
observation date falls within the inclusive range startDate to endDate.
public RateRangeResult GetRates(string fromIsoCode, string toIsoCode, DateOnly startDate, DateOnly endDate)
Parameters
fromIsoCodestringThe source-currency ISO-style code.
toIsoCodestringThe destination-currency ISO-style code.
startDateDateOnlyThe inclusive start of the range.
endDateDateOnlyThe inclusive end of the range.
Returns
- RateRangeResult
An RateRangeResult carrying the rates in the range ordered by date, the requested window, and the observed span; the result is empty when no rates are available.
Remarks
The lookup is range-based and does not apply a date-resolution policy: it returns the observations that exist within the window rather than resolving a single date. An implementation backed by a remote feed may block to fetch on demand, or serve only already-loaded data; use GetRatesAsync(string, string, DateOnly, DateOnly, CancellationToken) to fetch without blocking.
Exceptions
- ArgumentNullException
Thrown if
fromIsoCodeortoIsoCodeis null.- ArgumentException
Thrown if either ISO code is not a three-character uppercase ASCII code, or if
endDateprecedesstartDate.
GetRatesAsync(string, string, DateOnly, DateOnly, CancellationToken)
Asynchronously returns every available rate from fromIsoCode to
toIsoCode whose observation date falls within the inclusive range
startDate to endDate.
public ValueTask<RateRangeResult> GetRatesAsync(string fromIsoCode, string toIsoCode, DateOnly startDate, DateOnly endDate, CancellationToken cancellationToken = default)
Parameters
fromIsoCodestringThe source-currency ISO-style code.
toIsoCodestringThe destination-currency ISO-style code.
startDateDateOnlyThe inclusive start of the range.
endDateDateOnlyThe inclusive end of the range.
cancellationTokenCancellationTokenA token to observe while awaiting the operation.
Returns
- ValueTask<RateRangeResult>
A task that yields an RateRangeResult carrying the rates in the range ordered by date, the requested window, and the observed span; the result is empty when no rates are available.
Remarks
The lookup is range-based and does not apply a date-resolution policy: it returns the observations that exist within the window rather than resolving a single date. Implementations backed by a remote feed may fetch on demand, which is why the method is asynchronous.
Exceptions
- ArgumentNullException
Thrown if
fromIsoCodeortoIsoCodeis null.- ArgumentException
Thrown if either ISO code is not a three-character uppercase ASCII code, or if
endDateprecedesstartDate.
IsLoaded(CurrencyPair, DateOnly, DateOnly)
Reports whether the inclusive window for a pair has already been fetched, so the synchronous lookup path can skip a redundant blocking fetch.
protected abstract bool IsLoaded(CurrencyPair pair, DateOnly startDate, DateOnly endDate)
Parameters
pairCurrencyPairThe currency pair to test.
startDateDateOnlyThe inclusive start of the window.
endDateDateOnlyThe inclusive end of the window.
Returns
LoadCoalescedAsync(string, Func<CancellationToken, Task>, CancellationToken)
Runs load for key, or joins the load already in flight for that key, so
concurrent callers requesting the same endpoint window share a single fetch rather than each issuing a duplicate
request. Derived types call this from
EnsureLoadedAsync(CurrencyPair, DateOnly, DateOnly, CancellationToken) with a key identifying the
unit they download - an era, a feed, a date range, a pair-and-window.
protected Task LoadCoalescedAsync(string key, Func<CancellationToken, Task> load, CancellationToken cancellationToken)
Parameters
keystringThe key identifying the load; equal keys share one in-flight fetch.
loadFunc<CancellationToken, Task>The fetch to run on a miss, invoked with a token decoupled from any single caller.
cancellationTokenCancellationTokenA token that abandons this caller's wait on the shared fetch.
Returns
- Task
A task that completes when the load for
keycompletes.
Remarks
The shared fetch runs under None, so one caller's cancellation abandons only its own wait and never faults the fetch for the other joiners - appropriate for the idempotent cache-warming loads whose result populates the shared snapshot. The in-flight entry is released as soon as the fetch completes, including on failure, so a fault never poisons the key and the next caller starts a fresh attempt.
Exceptions
- ArgumentNullException
Thrown when
keyorloadis null.- OperationCanceledException
Thrown when
cancellationTokenis signalled before the shared fetch completes; the shared fetch continues to completion for any other joiner.
LoadPairAsync(string, string, DateOnly, DateOnly, CancellationToken)
Fetches and accumulates the inclusive window for a currency pair, unless that window is already covered.
public Task LoadPairAsync(string fromIsoCode, string toIsoCode, DateOnly startDate, DateOnly endDate, CancellationToken cancellationToken = default)
Parameters
fromIsoCodestringThe source-currency ISO code.
toIsoCodestringThe destination-currency ISO code.
startDateDateOnlyThe inclusive start of the range.
endDateDateOnlyThe inclusive end of the range.
cancellationTokenCancellationTokenA token to observe while awaiting the fetch.
Returns
- Task
A task that completes when the pair's window has been loaded.
Remarks
The load is delegated to the feed-specific EnsureLoadedAsync(CurrencyPair, DateOnly, DateOnly, CancellationToken), so it warms whatever unit the provider downloads - a single pair, an era, a feed, or a date range - to cover the requested window.
Exceptions
- ArgumentNullException
Thrown when an ISO code is null.
- ArgumentException
Thrown when an ISO code is malformed, the range is inverted, or the provider does not serve the requested pair.
OnObservationIngested(ExchangeRate)
Called once per observation as it is ingested, for derived-type diagnostics. The default does nothing.
protected virtual void OnObservationIngested(ExchangeRate rate)
Parameters
rateExchangeRateThe observation being ingested.
OnSynchronousNetworkFetch(DateOnly)
Called after a synchronous lookup blocks to fetch on demand, for derived-type diagnostics. The default does nothing.
protected virtual void OnSynchronousNetworkFetch(DateOnly date)
Parameters
dateDateOnlyThe date around which the fetch was performed.
RebuildSnapshot()
Rebuilds the immutable book and lookup snapshot from the accumulator. The caller must hold SyncRoot.
protected void RebuildSnapshot()
TryGetRate(string, string, DateOnly, RateLookupOptions?, out RateLookupResult)
Attempts to resolve the exchange rate from fromIsoCode to toIsoCode on
date under options, returning a flag indicating whether a rate was
found.
public bool TryGetRate(string fromIsoCode, string toIsoCode, DateOnly date, RateLookupOptions? options, out RateLookupResult result)
Parameters
fromIsoCodestringThe source-currency ISO-style code.
toIsoCodestringThe destination-currency ISO-style code.
dateDateOnlyThe calendar date for which a rate is required.
optionsRateLookupOptionsThe lookup rules to apply, including date-resolution policy and tolerance. null is treated as Exact.
resultRateLookupResultWhen this method returns true, contains the resolved RateLookupResult; otherwise, contains default.
Returns
Exceptions
- ArgumentNullException
Thrown if
fromIsoCodeortoIsoCodeis null.- ArgumentException
Thrown if either ISO code is not a three-character uppercase ASCII code, or if
optionsis invalid.- ArgumentOutOfRangeException
Thrown if
optionscontains a negative tolerance or an undefined enum value.
ValidateRangeRequest(string, string, DateOnly, DateOnly)
Validates a range request against feed-specific preconditions before any fetch is attempted. The default does nothing; derived types may override to reject unsupported pairs (for example, a single-issuer feed that quotes only against one base currency).
protected virtual void ValidateRangeRequest(string fromIsoCode, string toIsoCode, DateOnly startDate, DateOnly endDate)
Parameters
Explicit Interface Implementations
IRateProvider.GetRate(string, string)
Returns the exchange rate that converts one unit of fromIsoCode to units of
toIsoCode.
decimal IRateProvider.GetRate(string fromIsoCode, string toIsoCode)
Parameters
fromIsoCodestringThe source currency's ISO 4217 code.
toIsoCodestringThe destination currency's ISO 4217 code.
Returns
- decimal
The rate.
Exceptions
- KeyNotFoundException
No rate is available for the requested pair.
Applies to
| Product | Versions |
|---|---|
| .NET | 8, 10 |