Configuration and the Options Pattern
|
This section documents ASP.NET Core on .NET 10 (LTS), the current release — the minimal hosting model, the middleware pipeline, dependency injection, Minimal APIs, MVC & Razor Pages, Blazor with the current render modes, SignalR and gRPC, EF Core, ASP.NET Core Identity and policy-based authorization, output caching, rate limiting, and Native-AOT-aware building — as described by the official documentation at Microsoft Learn, which is the reference these pages are written and verified against. This content was generated with the assistance of AI and should be verified against the official documentation before being relied on in production. .NET ships a major release every November and its APIs continue to evolve: the examples here target .NET 10 / C# 14. This section’s bibliography lists the reference material consulted while preparing these pages. |
Configuration is a set of key/value pairs assembled from layered providers. The Options pattern binds sections of that configuration to typed classes and injects them where they are needed.
Providers and precedence
WebApplication.CreateBuilder adds providers in this order — later providers win:
-
appsettings.json -
appsettings.{Environment}.json -
User Secrets (Development only)
-
Environment variables
-
Command-line arguments
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;Database=app;Trusted_Connection=True"
# environment variable overriding a nested key (":" -> "__" for portability)
export ConnectionStrings__Default="Server=prod;Database=app;User Id=app;Password=..."
dotnet run --Feature:BetaSearch=true
Hierarchical keys use : in code and JSON; use __ (double underscore) in environment-variable names. Add
extra providers (Azure Key Vault, JSON files, in-memory) explicitly:
builder.Configuration.AddJsonFile("shared.json", optional: true, reloadOnChange: true);
Reading configuration directly
IConfiguration config = builder.Configuration;
int pageSize = config.GetValue<int>("Search:PageSize", 20);
string? cs = config.GetConnectionString("Default"); // ConnectionStrings:Default
IConfigurationSection smtp = config.GetSection("Smtp");
string host = smtp["Host"]!;
Reading keys ad hoc is fine for one-offs; for anything reused, bind to a class (below).
The Options pattern
Define a class, bind a section to it, inject IOptions<T>:
public sealed class SmtpOptions
{
public const string Section = "Smtp";
public required string Host { get; init; }
public int Port { get; init; } = 25;
public bool UseTls { get; init; } = true;
}
builder.Services.Configure<SmtpOptions>(builder.Configuration.GetSection(SmtpOptions.Section));
// consume it
public sealed class Mailer(IOptions<SmtpOptions> options)
{
private readonly SmtpOptions _smtp = options.Value;
}
| Accessor | Semantics |
|---|---|
|
Singleton, value computed once. Use for values that never change at runtime. |
|
Scoped; recomputed per request. Picks up |
|
Singleton with |
Named options bind several instances of the same type:
builder.Services.Configure<SmtpOptions>("Primary", config.GetSection("Smtp:Primary"));
builder.Services.Configure<SmtpOptions>("Fallback", config.GetSection("Smtp:Fallback"));
public sealed class Mailer(IOptionsMonitor<SmtpOptions> monitor)
{
private readonly SmtpOptions _primary = monitor.Get("Primary");
}
Validation
Validate options at startup so a misconfigured app fails fast rather than at first use:
builder.Services.AddOptions<SmtpOptions>()
.Bind(builder.Configuration.GetSection(SmtpOptions.Section))
.ValidateDataAnnotations() // [Required], [Range], ...
.Validate(o => o.Port is > 0 and < 65536, "Port out of range")
.ValidateOnStart(); // run all validators during app start
For complex rules implement IValidateOptions<T>. The configuration-binding source generator and the
options-validation source generator (enable EnableConfigurationBindingGenerator /
[OptionsValidator]) remove the runtime reflection and make binding/validation trimming- and AOT-friendly.
Never put credentials in appsettings.json — keep them in User Secrets locally and a secret store in
production. See Security hardening.