Dependency injection and configuration
Bodu's Microsoft.Extensions.DependencyInjection registrations come in three families - calendars, money and exchange rates, and configuration sources - and their namespaces do not always match their packages: AddFinancialService is in Bodu.Financial, not in a DependencyInjection namespace, and every exchange-rate provider and cache registers from Bodu.Financial.ExchangeRates. This page lists every registration with its package, namespace and lifetime, wires one host that uses all three families, and states the order the registrations must run in.
Every registration adds its services as singletons, because each service it registers is safe for concurrent use; see thread safety.
Calendars
Every calendar registration is declared in the Bodu.Globalization.Calendar namespace, whichever package ships it.
| Method | Package | Registers | Lifetime |
|---|---|---|---|
AddNotableDateService |
Bodu.Globalization.Calendar.DependencyInjection |
INotableDateService over a resource or a resource factory; the overloads that take a string key register a keyed service instead. |
Singleton; the first registration wins. |
AddReloadableNotableDateService |
Bodu.Globalization.Calendar.DependencyInjection |
INotableDateService, and the INotableDateResourceProvider whose resource can be swapped at run time. The generic overload rebuilds the resource on every change to its options type. |
Singleton; the first registration wins. |
AddCachedNotableDateService |
Bodu.Globalization.Calendar.Caching |
Replaces the registered INotableDateService with a CachingNotableDateService over it, binding Calendar:NotableDateCache. |
Singleton. |
AddNotableDateCacheWarmup |
Bodu.Globalization.Calendar.Caching |
A hosted service that fills the cache when the host starts, binding Calendar:NotableDateCacheWarmup. |
Hosted service. |
AddSqliteNotableDateCache |
Bodu.Globalization.Calendar.Caching.Sqlite |
SqliteNotableDateCache, as INotableDateCache, binding Calendar:NotableDateCache:Sqlite. |
Singleton; the first registration wins. |
AddDistributedNotableDateCache, AddRedisNotableDateCache |
Bodu.Globalization.Calendar.Caching.Distributed |
DistributedNotableDateCache, as INotableDateCache, over the registered IDistributedCache, binding Calendar:NotableDateCache:Distributed. The Redis overload registers the IDistributedCache first. |
Singleton; the first registration wins. |
Money and exchange rates
| Method | Package | Namespace | Registers | Lifetime |
|---|---|---|---|---|
AddFinancialService |
Bodu.Financial.DependencyInjection |
Bodu.Financial |
ICurrencyLookup and the FinancialOptions binding (Financial), and returns the IFinancialServiceBuilder the rest of this table chains from. |
Singleton; the first registration wins. |
AddCurrencyLookup<TLookup> |
Bodu.Financial.DependencyInjection |
Bodu.Financial |
Your ICurrencyLookup, in place of the default. |
Singleton; replaces the existing one. |
AddMonetaryContext |
Bodu.Financial.DependencyInjection |
Bodu.Financial |
A MonetaryContext, keyed by name. |
Keyed singleton. |
AddExchangeRateProvider, AddDatedExchangeRateProvider |
Bodu.Financial.DependencyInjection |
Bodu.Financial |
Your IRateProvider or IDatedRateProvider, by type or instance. |
Singleton; the first registration wins. |
Add<Source>ExchangeRates, such as AddEcbExchangeRates |
Bodu.Financial.ExchangeRates.<Source> |
Bodu.Financial.ExchangeRates |
The provider, as itself, IDatedRateProvider and IRateProvider, with a named HttpClient fitted with the standard resilience handler, binding Financial:<Source>. The overload on IServiceCollection calls AddFinancialService first. |
Singleton; the first registration wins. |
AddWebRateProvider |
Bodu.Financial.ExchangeRates.DependencyInjection |
Bodu.Financial.ExchangeRates |
The same plumbing, for a provider of your own. | Singleton; the first registration wins. |
AddCachedRateProvider<TProvider> |
Bodu.Financial.ExchangeRates.Caching |
Bodu.Financial.ExchangeRates |
A CachingRateProvider over the registered TProvider, as IDatedRateProvider and IRateProvider, binding Financial:RateCache. |
Singleton; overrides earlier registrations. |
AddAggregatedRateProvider |
Bodu.Financial.ExchangeRates.Caching |
Bodu.Financial.ExchangeRates |
An AggregatingRateProvider over cached children, as IDatedRateProvider and IRateProvider, with each child also a keyed IDatedRateProvider. |
Singleton; overrides earlier registrations. |
AddRateCacheWarmup |
Bodu.Financial.ExchangeRates.Caching |
Bodu.Financial.ExchangeRates |
A hosted service that fills the rate caches when the host starts, binding Financial:RateCacheWarmup. |
Hosted service. |
AddSqliteRateCache |
Bodu.Financial.ExchangeRates.Caching.Sqlite |
Bodu.Financial.ExchangeRates |
SqliteRateCache for one provider, as a keyed IRateCache under the provider's name, binding Financial:RateCache:Sqlite. |
Keyed singleton; the first one registered is also the unkeyed IRateCache. |
AddDistributedRateCache, AddRedisRateCache |
Bodu.Financial.ExchangeRates.Caching.Distributed |
Bodu.Financial.ExchangeRates |
DistributedRateCache over the registered IDistributedCache, likewise, binding Financial:RateCache:Distributed. The Redis overload registers the IDistributedCache first. |
Keyed singleton; the first one registered is also the unkeyed IRateCache. |
AddFinancialJson |
Bodu.Financial.Serialization.Json |
Bodu.Financial.Serialization.Json |
A JsonSerializerOptions holding the financial converters, keyed "Financial". |
Keyed singleton. |
After building the provider, UseCurrencyResolution (in Bodu.Financial) makes the registered ICurrencyLookup the one the static currency resolution uses.
Configuration sources
The Bodu.Extensions.Configuration.Text package adds configuration sources, all in the Bodu.Extensions.Configuration.Text namespace.
| Method | Extends | Adds |
|---|---|---|
AddTextConfigurationFile, AddTextConfiguration |
IConfigurationBuilder |
A file in the Bodu text configuration format, which can reload when the file changes. AddTextConfiguration looks for .boduconfig, then bodu.config. |
AddTextConfigurationStream, AddTextConfigurationDocument |
IConfigurationBuilder |
The same format from a stream or an already parsed document. |
AddTomlFile, AddTomlStream |
IConfigurationBuilder |
A TOML file or stream, read once. A table path becomes a colon-delimited section, so [Financial.Ecb] is the Financial:Ecb section. |
AddBencodeFile, AddBencodeStream |
IConfigurationBuilder |
A Bencode dictionary, read once. |
AddConfigurationOptions<TOptions> |
IServiceCollection |
Binds a section to IOptions<TOptions>. |
One host
This host reads its settings from TOML, registers a calendar service with a cache in front of it, and registers the core financial services with the ECB provider and a cache in front of that. Every registration that takes an IConfiguration reads its default section from it.
using System.Text;
using Bodu.Extensions.Configuration.Text;
using Bodu.Financial;
using Bodu.Financial.ExchangeRates;
using Bodu.Globalization.Calendar;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
const string settings = """
[Calendar.NotableDateCache]
CacheDirectory = "cache/holidays"
Ttl = "7.00:00:00"
[Financial.Ecb]
MaxResponseContentBufferSize = 16777216
[Financial.RateCache]
CacheDirectory = "cache/rates"
DefaultExpiry = "12:00:00"
""";
using var source = new MemoryStream(Encoding.UTF8.GetBytes(settings));
IConfiguration configuration = new ConfigurationBuilder()
.AddTomlStream(source)
.Build();
var services = new ServiceCollection();
// Calendars: the service first, then the cache in front of it.
services.AddNotableDateService(AmericasCalendarData.LoadResource("US"));
services.AddCachedNotableDateService(configuration);
// Money and exchange rates: the core services, the provider, then the cache in front of it. The provider's
// named HttpClient takes its response cap, 16 MiB here, from the [Financial.Ecb] options.
services.AddFinancialService(configuration)
.AddEcbExchangeRates(configuration)
.AddCachedRateProvider<EcbRateProvider>("ECB", configuration);
using ServiceProvider provider = services.BuildServiceProvider();
provider.UseCurrencyResolution();
INotableDateService holidays = provider.GetRequiredService<INotableDateService>();
IDatedRateProvider rates = provider.GetRequiredService<IDatedRateProvider>();
Console.WriteLine($"{holidays.GetType().Name}, {rates.GetType().Name}");
The last line prints CachingNotableDateService, CachingRateProvider: each consumer asks for the contract and gets the cached service. Under the Generic Host the same registrations go on builder.Services, and the options they validate with ValidateOnStart are checked when the host starts; with a plain ServiceProvider, as here, they are checked when the service is first resolved.
Ordering rules
- Register a service before the decorator in front of it.
AddCachedNotableDateServicewraps theINotableDateServiceregistered before it, and throwsInvalidOperationExceptionif there is none.AddCachedRateProvider<TProvider>wraps theTProviderregistered before it; registered first, it would registerTProvideritself, without theHttpClientthe provider's own registration supplies. - Keyed calendar services are not cached.
AddCachedNotableDateServicedecorates the most recent unkeyedINotableDateServiceregistration and leaves keyed ones, registered before or after it, in place and undecorated. With only keyed registrations it throwsInvalidOperationException. - Register warm-ups after the caches they warm.
AddNotableDateCacheWarmupfollowsAddCachedNotableDateService;AddRateCacheWarmupwarms every provider registered throughAddCachedRateProvider, and an aggregation child only when its name is listed in the warm-up options. - Compose a cache backend through the cache factory. The SQLite and distributed backends register a cache; the decorators use it only when you pass a factory that resolves it:
cacheFactory: sp => sp.GetRequiredService<INotableDateCache>()for calendars, andcacheFactory: (sp, name) => sp.GetRequiredKeyedService<IRateCache>(name)for rates. Without one, each decorator uses a TOML file cache under itsCacheDirectory. - Know which registrations win. Most registrations use
TryAdd, so when two register the same service the first wins: two calendar services, two rate providers, two cache backends.AddCachedRateProviderandAddAggregatedRateProvideradd their providers unconditionally, so the most recent wins, andAddCurrencyLookupreplaces the lookup outright. - Call
UseCurrencyResolutiononce, after the provider is built. It sets a process-wide default, so it belongs in the composition root.
Where to go next
- Calendar dependency injection - every calendar registration in depth, with keyed and reloadable services.
- Financial dependency injection - the financial builder, monetary contexts, and the Generic Host.
- Exchange-rate caching - the rate caches and the aggregating provider.
- Configuration sources - every configuration source, reloading, and layering.