Configuration & Options

15 questions found

You implement IConfigurationSource (returning an IConfigurationProvider instance) and a corresponding ConfigurationProvider subclass overriding Load() to populate the provider's internal Data dictionary from your custom source, then register it via an extension method calling configurationBuilder.Add(new MyCustomSource()) -- this integrates seamlessly with the rest of the configuration system, including layering with other providers and (if you implement change detection) triggering hot-reload.
public class DatabaseConfigurationProvider : ConfigurationProvider {
    public override void Load() {
        using var connection = new SqlConnection(_connectionString);
        var settings = connection.Query("SELECT [Key], [Value] FROM AppSettings");
        foreach (var s in settings) Data[s.Key] = s.Value;
    }
}

public class DatabaseConfigurationSource : IConfigurationSource {
    public IConfigurationProvider Build(IConfigurationBuilder builder) => new DatabaseConfigurationProvider();
}

// Usage: builder.Configuration.Add(new DatabaseConfigurationSource());
Real-world example A multi-tenant SaaS platform builds a custom configuration provider that loads tenant-specific feature flags from a database table at startup, integrating seamlessly alongside the standard appsettings.json and environment variable providers.

Common follow-ups: How would you add live change detection (polling or push notifications) to this custom provider?;How does provider registration order affect precedence for a custom source?

Entity Framework Core & Data Access;Multiple Inheritance & MRO
The command-line provider (added by default in the host builder) parses arguments in --Key=Value or --Key Value format (and supports colon-delimited nested keys) as the highest-precedence configuration source by default, letting you override any setting for a single run without modifying files or environment variables -- useful for quick local testing or scripted one-off overrides.
dotnet run --ConnectionStrings:DefaultConnection="Server=test;" --Logging:LogLevel:Default=Debug

// Equivalent to setting these keys via any other provider, but only for this single run
Real-world example A developer debugging a production-like issue locally runs `dotnet run --Logging:LogLevel:Default=Trace` for one session to get maximally verbose logging without permanently changing any configuration file.

Common follow-ups: Does the command-line provider support short-form argument aliases?;How does this interact with environment-variable overrides in terms of precedence?

.NET CLI SDK & Project Structure (csproj);Logging
Common approaches include: a custom configuration provider that loads and merges tenant-specific overrides on top of global defaults at request time (using the current tenant context, often resolved via IHttpContextAccessor or a middleware-set value), or a tenant-aware options resolution pattern (a custom ITenantOptionsProvider service) that layers a base IOptions<T> with tenant-specific overrides fetched from a database or cache -- since the standard Options pattern's IOptionsSnapshot is scoped per-request but not inherently tenant-aware, additional logic is needed to select the right values for the current tenant.
public class TenantSettingsResolver {
    private readonly IOptionsMonitor<Dictionary<string, TenantSettings>> _allTenantSettings;
    public TenantSettings GetForCurrentTenant(string tenantId) {
        return _allTenantSettings.CurrentValue.TryGetValue(tenantId, out var settings)
            ? settings
            : _defaultSettings;
    }
}
Real-world example A B2B SaaS platform resolves each tenant's custom branding colors, feature flags, and rate limits through a tenant-aware settings resolver that layers tenant-specific database overrides on top of a global default configuration baseline.

Common follow-ups: How do you cache tenant-specific settings efficiently without a database hit on every request?;How does this pattern interact with configuration hot-reload for tenant-specific changes?

Microservices & Distributed Architecture Patterns;Caching (In-Memory Distributed & Redis)
GetSection(key) returns an IConfigurationSection representing a subtree of configuration (useful for binding to an options class or enumerating children), returning an empty, non-null section even if the key doesn't exist (requires checking .Exists()). GetValue<T>(key) directly reads and converts a single leaf value to the specified type, returning the type's default (or a specified fallback) if the key is missing, making it more convenient for simple scalar reads.
var section = configuration.GetSection("Smtp");  // returns a section, even if "Smtp" doesn't exist
if (!section.Exists()) Console.WriteLine("Smtp section missing");

var port = configuration.GetValue<int>("Smtp:Port", defaultValue: 587);  // direct scalar read with fallback
Real-world example A configuration validation utility uses GetSection(...).Exists() to check for optional feature configuration blocks before attempting to bind them, avoiding null reference exceptions on genuinely optional settings.

Common follow-ups: What does IConfigurationSection.Value return for a section with children instead of a leaf value?;How does GetValue handle type conversion failures?

Configuration & Options;Diagnostics & Performance
Configuration providers are evaluated in the order they're added to the ConfigurationBuilder, with each subsequently added provider's values overriding any previously set value for the same key -- meaning provider registration order directly determines precedence, which is why understanding and controlling this order (files first, then environment variables, then command-line args, as the conventional pattern) is essential for predictable configuration behavior across environments.
var builder = WebApplication.CreateBuilder(args);
// Default order already applied: appsettings.json -> appsettings.{Env}.json -> user secrets -> env vars -> command line

// Adding a custom source LAST gives it HIGHEST precedence
builder.Configuration.AddInMemoryCollection(new Dictionary<string, string> {
    ["Override:Key"] = "this wins over everything registered before it"
});
Real-world example A debugging session reveals a setting isn't taking effect because a custom configuration source was registered before the environment variable provider instead of after, causing the environment variable to silently override the intended custom value.

Common follow-ups: How would you inspect the final resolved value along with which provider supplied it?;Why does WebApplication.CreateBuilder set up this particular default order?

Configuration & Options;Secrets Management & Configuration Providers (Key Vault User Secrets)

Showing 11–15 of 15