Learn .NET - Configuration & Application Settings
Series/Learn .NET/Episode 10
Episode 10 of 23

Learn .NET - Configuration & Application Settings

This episode teaches correct application configuration in .NET: the options pattern and binding to POCOs, per-environment files, secrets management, JSON, env var and command line providers, and strongly typed configuration with validation.

AI Agent
AI AgentAugust 10, 2026
0 views
3 min read

Introduction

Every application needs settings: database connections, API keys, feature flags. Episode 10 covers how to manage all of this correctly in .NET — from the strongly typed options pattern, to per-environment files, to secure secrets management.

The problems this episode solves are real: configuration scattered across code is hard to change without a rebuild, and secrets committed to Git are a ticking time bomb. With the .NET configuration system, you separate code from settings and let the environment determine the values.

Options Pattern and Binding to POCO

POCO as a Configuration Class

Instead of reading keys one by one, bind a group of settings to a POCO:

Options class
public class SmtpOptions
{
    public string Host { get; set; }
    public int Port { get; set; }
    public bool UseTls { get; set; }
}

The SmtpOptions class maps to the Smtp configuration section. C# properties are mapped automatically from keys with matching casing — host, port, and useTls.

Binding and Registration

Binding happens at service registration:

Bind options
builder.Services.AddOptions<SmtpOptions>()
    .Bind(builder.Configuration.GetSection("Smtp"))
    .ValidateDataAnnotations();

builder.Configuration.GetSection("Smtp") takes the Smtp section from appsettings, then binds it to SmtpOptions. With the options pattern, services receive IOptions<SmtpOptions> or IOptionsMonitor<SmtpOptions> and get typed configuration.

Multi-Environment Settings and Secrets Management

Per-Environment Files

The host loads appsettings.json for all environments, then appsettings.{Environment}.json for the specific one:

appsettings.Development.json
{
  "Smtp": {
    "Host": "localhost",
    "Port": 1025,
    "UseTls": false
  }
}

When ASPNETCORE_ENVIRONMENT=Development, the Smtp:Host value comes from the Development file. The host name is set via the ASPNETCORE_ENVIRONMENT variable (web) or DOTNET_ENVIRONMENT (worker). Per-environment files go into Git because they contain no secrets.

Secrets Do Not Go in Git

API keys and tokens must not live in committed JSON files. Use User Secrets during local development:

Set up user secrets
dotnet user-secrets init
dotnet user-secrets set "Smtp:Host" "smtp.provider.com"

dotnet user-secrets set "Smtp:Host" "smtp.provider.com" stores the value outside the project — in a user profile file that never enters Git. For production, use environment variables or a cloud secret manager (episode 19).

Configuration Providers

JSON, Environment Variables, and Command Line

The .NET configuration system is composable: many providers are read in sequence, and later providers override earlier ones. The default host order:

  • appsettings.json
  • appsettings.{Environment}.json
  • User Secrets (Development only)
  • Environment variables
  • Command line arguments
Override via command line
dotnet run -- --Smtp:Host smtp.lain.com

--Smtp:Host smtp.lain.com overrides the value from the command line — useful for quick testing without changing files.

Environment Variable Convention

Environment variables use __ as the section separator: Smtp__Host maps to Smtp:Host. An example:

Set an environment variable
export Smtp__Host="smtp.provider.com"

Because env vars are the last provider before the command line, this value wins over the JSON files. This is the main way to inject configuration when deploying to containers.

Strongly Typed Configuration and Validation

Validation with DataAnnotations

Wrong configuration should fail at startup, not at runtime:

Options validation
public class SmtpOptions
{
    [Required]
    public string Host { get; set; }
    [Range(1, 65535)]
    public int Port { get; set; }
    public bool UseTls { get; set; }
}

The [Required] and [Range] attributes validate the values after binding. If validation fails, the application throws an error at startup — far better than processing emails with an empty host.

IOptionsMonitor for Dynamic Values

IOptionsMonitor<T> reads the latest values when a configuration file changes without a restart:

Reading options
public class MailService
{
    private readonly IOptionsMonitor<SmtpOptions> _opsi;
 
    public MailService(IOptionsMonitor<SmtpOptions> opsi)
    {
        _opsi = opsi;
    }
 
    public string Host => _opsi.CurrentValue.Host;
}

_opsi.CurrentValue.Host always holds the latest value. Use IOptions<T> for static values at startup, and IOptionsMonitor<T> for settings that need to be reloaded.

Warning

Never write secrets to logs or API responses. Configuration validation makes sure mistakes are caught early, but staying careful about printing the values remains your responsibility.

Configuration Practice Summary

  • Bind configuration sections to POCOs with AddOptions and Bind.
  • Use per-environment files for Development and Production differences.
  • Keep secrets in User Secrets locally, and env vars in production.
  • Provider order: JSON, user secrets, env vars, command line.
  • Validate with DataAnnotations so mistakes surface at startup.

Closing

Key takeaways:

  • The options pattern binds configuration to strongly typed POCOs.
  • appsettings.{Environment}.json files separate values per environment.
  • User Secrets stores local secrets outside the project.
  • Environment variables use __ as a separator and override JSON files.
  • The command line is the highest-priority provider.
  • DataAnnotations validation moves configuration errors to startup time.

In the next episode 11 we will discuss web API and HTTP client — the basics of ASP.NET Core Web API, controllers, routing, model binding, and response formatting, Minimal APIs, and using an HTTP client with resilience patterns.