Skip to main content

.NET Configuration

The .NET configuration system provides a flexible, extensible mechanism for managing application settings from multiple sources. It uses a layered approach where configuration values from different sources are merged, with later sources overriding earlier ones.

Configuration Sources

.NET applications can pull configuration from various sources, each serving different purposes and scenarios.

appsettings.json

The default JSON configuration file included in every ASP.NET Core project.

{
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=CommandDB;"
}
}

When to use:

  • Default application settings
  • Settings that are common across all environments
  • Non-sensitive configuration values
  • Settings that might be modified by operations teams

Characteristics:

  • Checked into source control
  • Visible to anyone with repository access
  • Easy to edit and maintain
  • Can be overridden by other sources

appsettings.{Environment}.json

Environment-specific configuration files that override base settings.

// appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
},
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=CommandDB_Dev;"
}
}

When to use:

  • Environment-specific values (Development, Staging, Production)
  • Overriding default appsettings for specific environments
  • Different logging levels per environment
  • Environment-specific connection strings

Characteristics:

  • Automatically loaded based on ASPNETCORE_ENVIRONMENT variable
  • Overrides values in base appsettings.json
  • Also checked into source control
  • Keeps environment differences visible and maintainable

User Secrets

Stored locally on the developer's machine, outside the project directory.

dotnet user-secrets set "ApiKeys:OpenAI" "sk-proj-abc123..."
// Stored in: ~/.microsoft/usersecrets/{user-secrets-id}/secrets.json
{
"ApiKeys": {
"OpenAI": "sk-proj-abc123..."
}
}

When to use:

  • Local development secrets (API keys, passwords)
  • Connection strings with credentials during development
  • Any sensitive data needed for local testing
  • Avoiding accidental commits of secrets to source control

Characteristics:

  • Development only - not available in production
  • Stored per user, per machine
  • Not checked into source control
  • Accessed via Secret Manager tool
  • Requires UserSecretsId in .csproj file

Environment Variables

System-level or process-level variables set on the host machine.

# Linux/macOS
export ConnectionStrings__DefaultConnection="Server=prod-db;Database=CommandDB;"

# Windows
set ConnectionStrings__DefaultConnection=Server=prod-db;Database=CommandDB;

When to use:

  • Production secrets and sensitive configuration
  • Container and cloud deployments (Docker, Kubernetes)
  • CI/CD pipeline configuration
  • Server-specific settings
  • Configuration that varies per deployment instance

Characteristics:

  • Highly secure - not stored in code or files
  • Supported across all platforms and hosting environments
  • Use double underscore __ for hierarchical keys (e.g., ConnectionStrings__DefaultConnection)
  • Essential for cloud-native applications

Command-Line Arguments

Configuration passed when starting the application.

dotnet run --ConnectionStrings:DefaultConnection="Server=localhost;Database=TestDB;"

When to use:

  • Temporary overrides during testing
  • Script-based deployments
  • Debugging specific scenarios
  • One-off configuration changes

Characteristics:

  • Highest precedence - overrides all other sources
  • Useful for quick testing without modifying files
  • Use colon : for hierarchical keys in command line
  • Not persistent

Azure Key Vault / Cloud Secrets Managers

Cloud-based secret management services (Azure Key Vault, AWS Secrets Manager, etc.).

When to use:

  • Production secrets requiring enterprise-grade security
  • Centralized secret management across applications
  • Secrets with rotation requirements
  • Compliance and audit requirements

Characteristics:

  • Requires additional NuGet packages
  • Network dependency for configuration loading
  • Cached after initial load
  • Can integrate with managed identities

Order of Precedence

Configuration sources are applied in a specific order, with later sources overriding earlier ones:

1. appsettings.json (lowest priority)

2. appsettings.{Environment}.json

3. User Secrets (Development only)

4. Environment Variables

5. Command-Line Arguments (highest priority)

Example: If the same setting exists in multiple sources:

  • appsettings.json: "LogLevel": "Information"
  • appsettings.Development.json: "LogLevel": "Debug"
  • Environment Variable: Logging__LogLevel__Default=Warning
  • Command-Line: --Logging:LogLevel:Default=Error

Result: LogLevel = Error (command-line wins)

Accessing Configuration

Configuration is accessed through the IConfiguration interface, which is registered in the dependency injection container by default.

In Program.cs

var builder = WebApplication.CreateBuilder(args);

// Access configuration directly from builder
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var apiKey = builder.Configuration["ApiKeys:OpenAI"];
var logLevel = builder.Configuration["Logging:LogLevel:Default"];

// Use configuration to setup services
builder.Services.AddDbContext<AppDbContext>(options =>
options.UseNpgsql(builder.Configuration.GetConnectionString("DefaultConnection"))
);

var app = builder.Build();

In Controllers or Services

Inject IConfiguration through constructor dependency injection:

public class PlatformsController : ControllerBase
{
private readonly IConfiguration _configuration;

public PlatformsController(IConfiguration configuration)
{
_configuration = configuration;
}

[HttpGet("info")]
public ActionResult GetInfo()
{
var appName = _configuration["ApplicationName"];
var environment = _configuration["ASPNETCORE_ENVIRONMENT"];

return Ok(new { appName, environment });
}
}

Strongly-Typed Configuration

Bind configuration sections to strongly-typed classes using the Options pattern:

// Configuration class
public class DatabaseOptions
{
public string ConnectionString { get; set; } = string.Empty;
public int MaxRetries { get; set; }
public int CommandTimeout { get; set; }
}

// In Program.cs
builder.Services.Configure<DatabaseOptions>(
builder.Configuration.GetSection("Database")
);

// In a service
public class DataService
{
private readonly DatabaseOptions _dbOptions;

public DataService(IOptions<DatabaseOptions> dbOptions)
{
_dbOptions = dbOptions.Value;
}
}

Configuration Keys

Access nested configuration using colon : notation:

{
"Database": {
"ConnectionString": "...",
"Pool": {
"MaxSize": 100
}
}
}
var maxSize = _configuration["Database:Pool:MaxSize"];
// or
var maxSize = _configuration.GetValue<int>("Database:Pool:MaxSize");

Best Practices

Do:

  • Store non-sensitive defaults in appsettings.json
  • Use User Secrets for local development secrets
  • Use environment variables for production secrets
  • Use strong typing with the Options pattern
  • Document required configuration keys

Don't:

  • Commit secrets to source control
  • Hardcode sensitive values in code
  • Use appsettings.json for production secrets
  • Access configuration everywhere - centralize in services

Common Pitfalls

  1. Case sensitivity: Configuration keys are case-insensitive, but it's best to be consistent
  2. Hierarchical separators: Use : in code, __ in environment variables
  3. User Secrets in Production: They only work in Development environment
  4. Missing configuration: Always provide defaults or validate required settings at startup

The .NET configuration system's flexibility and layered approach makes it suitable for everything from local development to enterprise production deployments, while maintaining security and ease of use.


Further Reading: