Configuration-driven provider selection with DI integration and dynamic reload support. Automatically discovers and registers providers, supports runtime configuration changes without restart. See https://github.com/noant/SharedNuget/tree/main/Shared.DI.ProvidersConfig for details.
$ dotnet add package Shared.DI.ProvidersConfigConfiguration-driven provider selection with DI integration and dynamic reload support.
appsettings.json without restarting the applicationIOptions<T>dotnet add package Shared.DI.ProvidersConfig
public enum MessageType { Email, Sms }
public interface IMessageProvider
{
Task SendAsync(string message);
}
public class MessageSender : IHasProviders<MessageType, IMessageProvider>
{
private readonly IProviders<MessageType, IMessageProvider> _providers;
public MessageSender(IProviders<MessageType, IMessageProvider> providers)
{
_providers = providers;
}
public async Task SendEmailAsync(string message)
{
var emailProvider = _providers.Of(MessageType.Email);
await emailProvider.SendAsync(message);
}
}
public class EmailProviderOptions
{
public string SmtpHost { get; set; } = string.Empty;
public int SmtpPort { get; set; }
}
public class EmailProvider : IMessageProvider, IProvider<IMessageProvider, EmailProviderOptions>
{
private readonly IOptionsSnapshot<EmailProviderOptions> _options;
public EmailProvider(IOptionsSnapshot<EmailProviderOptions> options)
{
_options = options;
}
public Task SendAsync(string message)
{
Console.WriteLine($"Sending via SMTP {_options.Value.SmtpHost}:{_options.Value.SmtpPort}");
return Task.CompletedTask;
}
}
{
"providersConfiguration": {
"MessageSender": {
"cacheLifetime": "00:00:15",
"reloadAssemblyInfo": false,
"defaultProvider": "Email",
"activeProviders": {
"Email": "EmailProvider",
"Sms": "SmsProvider"
},
"configurations": {
"Email": {
"EmailProvider": {
"SmtpHost": "smtp.example.com",
"SmtpPort": 587
}
},
"Sms": {
"SmsProvider": {
"ApiKey": "your-api-key",
"ApiUrl": "https://api.sms-provider.com"
}
}
}
}
}
}
Configuration Fields:
cacheLifetime (optional, default: 00:00:15): TimeSpan for assembly type cache expirationreloadAssemblyInfo (optional, default: false): If false, assembly type cache never expires after first loaddefaultProvider (required): Default enum key for provider selectionactiveProviders (required): Dictionary mapping enum keys to provider class namesconfigurations (required): Nested dictionary: {EnumKey}.{ProviderClassName}.{ProviderOptions}var configuration = new ConfigurationBuilder()
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.Build();
// Recommended: Register as Singleton for optimal caching performance
services.AddProvidersConfiguration<MessageSender>(configuration);
Note: AddProvidersConfiguration automatically registers the holder class (MessageSender) in DI with the specified lifetime (defaults to ServiceLifetime.Singleton for optimal caching performance).
var messageSender = serviceProvider.GetRequiredService<MessageSender>();
await messageSender.SendEmailAsync("Hello!");
Change active providers and their configurations while the application is running:
Edit appsettings.json:
"activeProviders": {
"Email": "EmailProvider",
"Sms": "SecondarySmsProvider"
},
"configurations": {
"Email": {
"EmailProvider": {
"SmtpHost": "smtp.newhost.com",
"SmtpPort": 465
}
},
"Sms": {
"SecondarySmsProvider": {
"ApiKey": "new-api-key",
"ApiUrl": "https://api.new-sms.com"
}
}
}
Save the file
Next provider resolution automatically uses the new provider and updated configuration!
Requirements:
reloadOnChange: true in ConfigurationBuilderThe enum-keyed configuration structure supports multiple instances of the same provider class with different configurations:
{
"providersConfiguration": {
"LlmService": {
"cacheLifetime": "00:00:15",
"reloadAssemblyInfo": false,
"defaultProvider": "Chat",
"activeProviders": {
"Chat": "OpenAiLlmProvider",
"Reasoner": "OpenAiLlmProvider"
},
"configurations": {
"Chat": {
"OpenAiLlmProvider": {
"ApiKey": "apikey",
"ModelName": "deepseek-chat",
"Uri": "https://api.deepseek.com/v1"
}
},
"Reasoner": {
"OpenAiLlmProvider": {
"ApiKey": "apikey",
"ModelName": "deepseek-reasoner",
"Uri": "https://api.deepseek.com/v1"
}
}
}
}
}
}
In this example, OpenAiLlmProvider is instantiated twice with different configurations for Chat and Reasoner enum keys.
services.AddProvidersConfiguration<MessageSender>(configuration);
services.AddProvidersConfiguration<IMessageSender, MessageSender>(configuration);
services.AddProvidersConfiguration<MessageSender>(configuration, ServiceLifetime.Scoped);
Note:
AddProvidersConfiguration automatically registers the holder class in DI with the specified lifetimeRegistration Phase:
SimpleProvidersOptions is configured from appsettings.jsonIProviders<TEnum, TProvider> and IProviderSwitcher are registered in DIMessageSender) is automatically registered in DIResolution Phase:
Of(enumKey), it:
TRealProvider (cached with configurable lifetime)IOptionsMonitor<T>IOptions<T> parameters, binds configuration from configurations.{EnumKey}.{ProviderClassName}Caching:
cacheLifetime and reloadAssemblyInfo settingscacheLifetimeSee EXAMPLES.md for detailed examples including:
MIT