Configuration & Options

15 questions found

How does the ASP.NET Core configuration system layer multiple sources (appsettings.json, environment variables, command line) together?

Beginner
IConfiguration builds a unified key-value configuration tree by combining multiple providers in a specific registration order, where later-registered providers override earlier ones for the same key -- the default host builder registers appsettings.json first, then appsettings.{Environment}.json, then user secrets (development only), then environment variables, then command-line arguments, meaning environment variables and command-line args can always override file-based settings, which is essential for containerized deployments.
// Default precedence (later overrides earlier):
// appsettings.json -> appsettings.Production.json -> environment variables -> command-line args

// appsettings.json: "ConnectionStrings:Db": "local-connection"
// Environment variable ConnectionStrings__Db="prod-connection" overrides it
Real-world example A containerized application ships with a generic appsettings.json for local development, while the actual production connection string is injected via a Kubernetes-managed environment variable, overriding the file-based default without needing separate builds per environment.

Common follow-ups: Why does the environment variable provider use double underscores (__) instead of colons for nested keys?;How would you add a custom configuration source, like a database provider?

Secrets Management & Configuration Providers (Key Vault User Secrets);CI/CD Publishing & Deployment

What is the Options pattern, and why is it preferred over directly injecting IConfiguration into services?

Intermediate
The Options pattern binds a strongly-typed C# class to a section of configuration (via IOptions<T>, IOptionsSnapshot<T>, or IOptionsMonitor<T>), giving you compile-time type safety, IntelliSense, and centralized validation instead of scattering magic string-based Configuration["Key:SubKey"] lookups (which are unchecked at compile time and easy to typo) throughout your codebase.
public class SmtpSettings {
    public string Host { get; set; } = "";
    public int Port { get; set; }
}

builder.Services.Configure<SmtpSettings>(builder.Configuration.GetSection("Smtp"));

public class EmailService {
    private readonly SmtpSettings _settings;
    public EmailService(IOptions<SmtpSettings> options) => _settings = options.Value;
}
Real-world example A team migrates dozens of scattered Configuration["Smtp:Host"] string lookups (some with typos causing silent null values) to a single strongly-typed SmtpSettings class, catching configuration key mismatches at compile time instead of in production.

Common follow-ups: What's the difference between IOptions, IOptionsSnapshot, and IOptionsMonitor?;How do you validate options at startup using IValidateOptions?

Dependency Injection;Diagnostics & Performance

What is the difference between IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T>?

Advanced
IOptions<T> is registered as a singleton, computed once at first access and never refreshed even if the underlying configuration source changes at runtime -- suitable for genuinely static settings. IOptionsSnapshot<T> is scoped, recomputed once per request/scope, picking up configuration changes on the next request but staying consistent within a single request. IOptionsMonitor<T> is a singleton that can notify subscribers immediately when configuration changes via its OnChange callback, ideal for singleton services (like a BackgroundService) that need to react to live configuration updates.
// IOptionsSnapshot: fresh value per HTTP request
public MyController(IOptionsSnapshot<FeatureSettings> options) { ... }

// IOptionsMonitor: live change notifications in a singleton service
public class MyBackgroundService : BackgroundService {
    public MyBackgroundService(IOptionsMonitor<WorkerSettings> monitor) {
        monitor.OnChange(settings => Console.WriteLine("Settings changed!"));
    }
}
Real-world example A long-running BackgroundService uses IOptionsMonitor to immediately pick up a changed polling interval from a hot-reloadable appsettings.json without needing to restart the entire application, while a typical scoped controller uses IOptionsSnapshot for simpler per-request configuration access.

Common follow-ups: Why can't a singleton service safely use IOptionsSnapshot?;What triggers the OnChange callback in IOptionsMonitor?

Background Services;Dependency Injection

How does appsettings.{Environment}.json layering work, and how is the current environment determined?

Intermediate
ASP.NET Core loads appsettings.json first as a base configuration, then layers appsettings.{EnvironmentName}.json on top (where settings in the environment-specific file override matching keys from the base file), with the environment determined by the ASPNETCORE_ENVIRONMENT environment variable (commonly Development, Staging, or Production), letting you maintain environment-specific overrides without duplicating the entire configuration file.
// appsettings.json (base, all environments)
{ "Logging": { "LogLevel": { "Default": "Information" } } }

// appsettings.Development.json (overrides base only in Development)
{ "Logging": { "LogLevel": { "Default": "Debug" } } }

// Set via: ASPNETCORE_ENVIRONMENT=Development
Real-world example A team keeps verbose Debug-level logging enabled only in appsettings.Development.json, while production automatically uses the quieter Information-level default from the base appsettings.json, without any code changes between environments.

Common follow-ups: How do you add a custom environment beyond the standard three?;What happens if ASPNETCORE_ENVIRONMENT isn't set at all?

Logging;CI/CD Publishing & Deployment

How does the configuration binder handle nested objects, arrays, and complex types when binding a JSON section to a C# class?

Advanced
The configuration binder uses reflection to match configuration keys (flattened into a colon-delimited hierarchy internally, e.g., "Parent:Child:Grandchild") to corresponding property names on the target class, recursively binding nested objects, and mapping JSON arrays to numerically-indexed keys ("Items:0", "Items:1") that bind to List<T> or array properties -- requiring the target class to have public settable properties (or, in newer versions, support for required/init-only properties and primary constructors in some scenarios) matching the configuration structure.
// appsettings.json
{
  "Settings": {
    "MaxRetries": 3,
    "Endpoints": ["https://a.com", "https://b.com"],
    "Nested": { "Timeout": 30 }
  }
}

public class Settings {
    public int MaxRetries { get; set; }
    public List<string> Endpoints { get; set; } = new();
    public NestedSettings Nested { get; set; } = new();
}
public class NestedSettings { public int Timeout { get; set; } }
Real-world example A team debugging why a configuration array wasn't binding correctly discovers their JSON used an object with string keys instead of a JSON array, since the binder specifically expects sequential numeric indices to map to a List<T> or array property.

Common follow-ups: How does binding handle configuration keys that don't match any property?;Can the binder populate a Dictionary<string, T> from configuration?

Configuration & Options;Diagnostics & Performance

How do you validate configuration options at application startup to fail fast on misconfiguration?

Intermediate
Using ValidateDataAnnotations() combined with ValidateOnStart() on the options builder ensures configuration is validated immediately when the application starts (rather than lazily on first use), causing the application to fail to launch with a clear error message if required settings are missing or invalid -- far preferable to a confusing NullReferenceException or incorrect behavior discovered only when a specific code path first accesses the misconfigured option deep into runtime.
public class SmtpSettings {
    [Required] public string Host { get; set; } = "";
    [Range(1, 65535)] public int Port { get; set; }
}

builder.Services.AddOptions<SmtpSettings>()
    .Bind(builder.Configuration.GetSection("Smtp"))
    .ValidateDataAnnotations()
    .ValidateOnStart();
Real-world example A misconfigured production deployment missing the required Smtp:Host setting now fails immediately at startup with a clear 'DataAnnotation validation failed' error, instead of silently running until the first email-send attempt fails hours later with a confusing null reference exception.

Common follow-ups: How do you write custom validation logic beyond DataAnnotations using IValidateOptions?;What happens without ValidateOnStart -- when does validation actually run?

Diagnostics & Performance;Global Exception Handling & Middleware

How does named options support configuring multiple distinct instances of the same options type?

Advanced
Named options let you register multiple configurations of the same C# options class under different string names (via Configure<T>(name, ...)), then resolve a specific named instance using IOptionsSnapshot<T>.Get(name) or IOptionsMonitor<T>.Get(name) -- useful when you need several independently-configured instances of the same settings shape, like multiple SMTP configurations for different notification types.
builder.Services.Configure<SmtpSettings>("Marketing", builder.Configuration.GetSection("Smtp:Marketing"));
builder.Services.Configure<SmtpSettings>("Transactional", builder.Configuration.GetSection("Smtp:Transactional"));

public class EmailService {
    public EmailService(IOptionsSnapshot<SmtpSettings> options) {
        var marketingSettings = options.Get("Marketing");
        var transactionalSettings = options.Get("Transactional");
    }
}
Real-world example An application sending both marketing newsletters (via a bulk email provider) and transactional receipts (via a different, more reliable SMTP relay) uses named options to maintain two independently-configured SmtpSettings instances from the same class definition.

Common follow-ups: How does the default (unnamed) options instance relate to named ones?;Can you validate each named instance independently?

Configuration & Options;Dependency Injection

How do environment variables map to nested configuration keys in ASP.NET Core, given environment variables can't contain colons on some platforms?

Intermediate
The environment variable configuration provider uses double underscores (__) as the hierarchy separator instead of colons, since some shells and operating systems don't support colons in environment variable names -- ASP.NET Core automatically translates ConnectionStrings__DefaultConnection into the equivalent "ConnectionStrings:DefaultConnection" configuration key internally.
# Setting a nested configuration value via environment variable
export ConnectionStrings__DefaultConnection="Server=prod;Database=MyApp;"

# Equivalent to this in appsettings.json:
# { "ConnectionStrings": { "DefaultConnection": "Server=prod;Database=MyApp;" } }
Real-world example A Kubernetes deployment manifest sets Logging__LogLevel__Default=Warning as a container environment variable, correctly overriding the nested Logging:LogLevel:Default key from the base appsettings.json without any code changes.

Common follow-ups: Does this convention apply consistently across Windows, Linux, and macOS?;How do you configure array values via environment variables?

Docker & Containerization;CI/CD Publishing & Deployment

How does configuration hot-reload work with reloadOnChange for appsettings.json, and what are its limitations?

Advanced
When appsettings.json is registered with reloadOnChange: true (the default), a file system watcher detects changes to the file and triggers IConfiguration to reload its values in-memory without an application restart -- code using IOptionsSnapshot<T> or IOptionsMonitor<T> automatically picks up the new values, but IOptions<T> (computed once, cached forever) does not, and structural changes requiring different binding (like adding a new required section) may not always propagate cleanly to already-running dependent objects.
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

// A service using IOptionsMonitor will see updated values automatically:
public MyService(IOptionsMonitor<FeatureSettings> monitor) {
    monitor.OnChange(settings => _logger.LogInformation("Config reloaded: {Settings}", settings));
}
Real-world example An ops team updates a feature toggle percentage directly in a mounted ConfigMap-backed appsettings.json file in a running Kubernetes pod, and the application picks up the change within seconds via hot-reload, without needing to restart the pod.

Common follow-ups: Why doesn't IOptions<T> reflect hot-reloaded changes?;What are the risks of enabling hot-reload for security-sensitive settings?

Secrets Management & Configuration Providers (Key Vault User Secrets);Background Services

What is user secrets (dotnet user-secrets), and why should it only be used for local development, never production?

Intermediate
User secrets stores sensitive configuration values (API keys, connection strings) outside the project directory (in a per-user, per-project JSON file in the user's profile folder), keeping them out of source control accidentally while still being automatically loaded into IConfiguration during local development -- it's explicitly unencrypted and stored in plain text on the local disk, making it entirely unsuitable for production use, where a proper secret manager (Azure Key Vault, AWS Secrets Manager) should be used instead.
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Db" "Server=local;Database=Dev;"

// Automatically loaded in Development environment via:
// builder.Configuration.AddUserSecrets<Program>();  (added by default host builder in dev)
Real-world example A developer avoids accidentally committing their local database password to a shared Git repository by storing it in user secrets instead of directly in appsettings.Development.json, which would otherwise get committed alongside the rest of the code.

Common follow-ups: Where are user secrets actually stored on disk?;Why is user secrets specifically excluded from being enabled in Production by default?

Secrets Management & Configuration Providers (Key Vault User Secrets);.NET CLI SDK & Project Structure (csproj)

Showing 1–10 of 15