ASP.NET Core manages configuration using multiple sources. Environment-specific sources override common configuration values.
Configuration priority
With the default setup, later sources override earlier ones:
appsettings.json
↓ overridden by
appsettings.{Environment}.json
↓
User Secrets in Development
↓
Environment variables
↓
Command-line arguments
1. Common configuration
appsettings.json contains settings shared by all environments:
{
"ApplicationSettings": {
"ApplicationName": "Portfolio"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
}
}
2. Environment-specific configuration
appsettings.Development.json:
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}
appsettings.Production.json:
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
ASP.NET Core automatically loads the file matching the current environment.
3. Set the environment
Common environment names are:
DevelopmentStagingProduction
Windows PowerShell:
$env:ASPNETCORE_ENVIRONMENT = "Development"
Windows Command Prompt:
set ASPNETCORE_ENVIRONMENT=Development
Linux:
export ASPNETCORE_ENVIRONMENT=Production
For local development, launchSettings.json can set it:
{
"profiles": {
"MyMvcApp": {
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
launchSettings.json is only for local development and is not published.
4. Read configuration
public class HomeController : Controller
{
private readonly IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Index()
{
string? appName =
_configuration["ApplicationSettings:ApplicationName"];
return View();
}
}
5. Use the Options pattern
Create a strongly typed class:
public class ApplicationSettings
{
public string ApplicationName { get; set; } = string.Empty;
}
Register it:
builder.Services.Configure<ApplicationSettings>(
builder.Configuration.GetSection("ApplicationSettings"));
Inject it:
public class HomeController : Controller
{
private readonly ApplicationSettings _settings;
public HomeController(
IOptions<ApplicationSettings> options)
{
_settings = options.Value;
}
}
In ASP.NET Core web applications, the Options API is built in; an external library normally isn’t required.
6. Manage secrets safely
Do not store passwords, API keys or production connection strings in appsettings.json.
For local development, use User Secrets:
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=..."
Read it normally:
var connectionString =
builder.Configuration.GetConnectionString("DefaultConnection");
For production, use:
- Environment variables
- Azure Key Vault
- AWS Secrets Manager
- Kubernetes Secrets
Nested configuration keys use double underscores in environment variables:
ConnectionStrings__DefaultConnection
This represents:
ConnectionStrings:DefaultConnection
Environment checks
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
Key points
- Keep shared settings in
appsettings.json. - Keep environment-specific non-secret settings in corresponding JSON files.
- Use User Secrets only for local development.
- Use a secure secret store or environment variables in production.
- Prefer strongly typed Options over repeatedly accessing
IConfiguration. - Environment variables override values from
appsettings.json.
Explain the ASP.NET Core configuration-provider hierarchy.
A configuration provider reads settings from a source such as JSON files, User Secrets, environment variables, or command-line arguments.
When the same key exists in multiple providers:
The provider added later overrides the earlier provider.
Default hierarchy
WebApplication.CreateBuilder(args) loads application configuration in this order, from lowest to highest priority:
| Priority | Provider | Typical purpose |
|---|---|---|
| 1 | appsettings.json |
Common settings |
| 2 | appsettings.{Environment}.json |
Environment-specific settings |
| 3 | User Secrets | Local development secrets |
| 4 | Environment variables | Deployment and production settings |
| 5 | Command-line arguments | Runtime overrides |
Command-line arguments ← Highest priority
Environment variables
User Secrets (Development)
appsettings.{Environment}.json
appsettings.json ← Lowest priority
Override example
appsettings.json:
{
"ApiSettings": {
"Timeout": 30
}
}
appsettings.Production.json:
{
"ApiSettings": {
"Timeout": 60
}
}
Environment variable:
ApiSettings__Timeout=90
Command line:
dotnet run --ApiSettings:Timeout=120
Final value:
int timeout =
builder.Configuration.GetValue<int>("ApiSettings:Timeout");
// Result: 120
The command-line value wins because it has the highest priority.
Hierarchical keys
JSON uses nested objects:
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost"
}
}
Access it with ::
string? connectionString =
builder.Configuration["ConnectionStrings:DefaultConnection"];
Environment variables use __ because : is not supported consistently across operating systems:
ConnectionStrings__DefaultConnection=Server=production
ASP.NET Core automatically converts __ into :.
User Secrets
User Secrets are loaded only in the Development environment:
dotnet user-secrets set "ApiSettings:ApiKey" "secret-value"
They override JSON files but are overridden by environment variables and command-line arguments.
launchSettings.json
launchSettings.json is not directly an application configuration provider. It is a local development file that sets environment variables when the application is started through Visual Studio or dotnet run.
{
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
It is not published with the application.
Customizing the hierarchy
You can add another provider:
var builder = WebApplication.CreateBuilder(args);
builder.Configuration.AddJsonFile(
"customsettings.json",
optional: true,
reloadOnChange: true);
Because it is added after the default providers, its matching values can override them.
Other available providers include:
- Azure Key Vault
- Azure App Configuration
- XML and INI files
- In-memory collections
- Key-per-file
- Custom providers
Some providers require their corresponding NuGet package.
Host vs application configuration
ASP.NET Core also has host configuration for startup settings such as:
- Environment name
- Application name
- Content root
- Web root
Common host environment variables include:
DOTNET_ENVIRONMENT
ASPNETCORE_ENVIRONMENT
Application configuration normally has higher priority than fallback host configuration.
Key points
- Configuration is stored as case-insensitive key-value pairs.
- Later providers override earlier providers for matching keys.
- Environment-specific JSON overrides
appsettings.json. - Environment variables override JSON files and User Secrets.
- Command-line arguments have the highest default application priority.
- Never store production passwords or API keys in JSON files.
- Prefer the Options pattern for related configuration values.
Reference: Microsoft—Configuration in ASP.NET Core.
How do you use the Options pattern?
The Options pattern converts configuration sections into strongly typed C# classes.
It is better than repeatedly using:
configuration["EmailSettings:SmtpServer"]
1. Add configuration
appsettings.json:
{
"EmailSettings": {
"SmtpServer": "smtp.example.com",
"Port": 587,
"FromAddress": "admin@example.com"
}
}
2. Create an Options class
public class EmailSettings
{
public const string SectionName = "EmailSettings";
public string SmtpServer { get; set; } = string.Empty;
public int Port { get; set; }
public string FromAddress { get; set; } = string.Empty;
}
Property names should match the configuration keys.
3. Register it in Program.cs
builder.Services.Configure<EmailSettings>(
builder.Configuration.GetSection(EmailSettings.SectionName));
4. Inject and use it
using Microsoft.Extensions.Options;
public class EmailService
{
private readonly EmailSettings _settings;
public EmailService(IOptions<EmailSettings> options)
{
_settings = options.Value;
}
public void SendEmail()
{
Console.WriteLine(_settings.SmtpServer);
Console.WriteLine(_settings.Port);
}
}
Register the service:
builder.Services.AddScoped<EmailService>();
Options interfaces
| Interface | Lifetime | Configuration changes |
|---|---|---|
IOptions<T> |
Singleton | Does not read changes after startup |
IOptionsSnapshot<T> |
Scoped | Reads changes once per request |
IOptionsMonitor<T> |
Singleton | Reads current values and supports notifications |
IOptions<T>
Use for configuration that does not need to change while the application is running:
public EmailService(IOptions<EmailSettings> options)
{
_settings = options.Value;
}
IOptionsSnapshot<T>
Use in controllers or Scoped services when configuration may change:
public EmailService(
IOptionsSnapshot<EmailSettings> options)
{
_settings = options.Value;
}
A new snapshot is created for each HTTP request. It cannot be injected into a Singleton.
IOptionsMonitor<T>
Use in Singleton services or when immediate change notifications are required:
public class EmailService
{
private readonly IOptionsMonitor<EmailSettings> _options;
public EmailService(
IOptionsMonitor<EmailSettings> options)
{
_options = options;
}
public void SendEmail()
{
var settings = _options.CurrentValue;
}
}
Listen for changes:
_options.OnChange(settings =>
{
Console.WriteLine($"New server: {settings.SmtpServer}");
});
Validate configuration
Add validation attributes:
using System.ComponentModel.DataAnnotations;
public class EmailSettings
{
public const string SectionName = "EmailSettings";
[Required]
public string SmtpServer { get; set; } = string.Empty;
[Range(1, 65535)]
public int Port { get; set; }
[Required, EmailAddress]
public string FromAddress { get; set; } = string.Empty;
}
Register with validation:
builder.Services
.AddOptions<EmailSettings>()
.Bind(builder.Configuration.GetSection(
EmailSettings.SectionName))
.ValidateDataAnnotations()
.ValidateOnStart();
ValidateOnStart() stops the application during startup if the configuration is invalid.
Library information
For a normal ASP.NET Core Web application, the Options API is included. Usually, no additional package is required.
Namespaces:
using Microsoft.Extensions.Options;
using System.ComponentModel.DataAnnotations;
For a non-web project, or if ValidateDataAnnotations() is unavailable:
dotnet add package Microsoft.Extensions.Options.ConfigurationExtensions
dotnet add package Microsoft.Extensions.Options.DataAnnotations
Key points
- Options provide strongly typed configuration.
- Register them using
Configure<T>()orAddOptions<T>(). - Use
IOptions<T>for fixed settings. - Use
IOptionsSnapshot<T>for per-request refreshed settings. - Use
IOptionsMonitor<T>for live settings and Singleton services. - Validate important configuration during application startup.
Reference: Microsoft—Options pattern in ASP.NET Core.
What is the difference between IOptions<T>, IOptionsSnapshot<T>, and IOptionsMonitor<T>?
All three provide strongly typed configuration, but they differ in lifetime and how they handle configuration changes.
| Feature | IOptions<T> |
IOptionsSnapshot<T> |
IOptionsMonitor<T> |
|---|---|---|---|
| DI lifetime | Singleton | Scoped | Singleton |
| Reads changes | No | Once per request | Immediately/current value |
| Access property | .Value |
.Value |
.CurrentValue |
| Change notification | No | No | Yes, using OnChange() |
| Named options | No | Yes | Yes |
| Can inject into Singleton | Yes | No | Yes |
| Best used for | Fixed settings | Updated settings per request | Live settings/Singleton services |
IOptions<T>
Reads the configuration and keeps the same value for the application lifetime.
public EmailService(IOptions<EmailSettings> options)
{
EmailSettings settings = options.Value;
}
Use when:
- Settings do not change while the application is running.
- The application can restart to apply configuration changes.
- You need options inside any service lifetime.
IOptionsSnapshot<T>
Creates a new configuration snapshot once per HTTP request.
public EmailService(
IOptionsSnapshot<EmailSettings> options)
{
EmailSettings settings = options.Value;
}
Use when:
- Configuration changes should be reflected on the next request.
- The consuming service is Scoped or Transient.
It cannot be injected into a Singleton because IOptionsSnapshot<T> is Scoped.
IOptionsMonitor<T>
Provides the current configuration value and supports change notifications.
public class EmailService
{
private readonly IOptionsMonitor<EmailSettings> _options;
public EmailService(
IOptionsMonitor<EmailSettings> options)
{
_options = options;
_options.OnChange(settings =>
{
Console.WriteLine(
$"New server: {settings.SmtpServer}");
});
}
public void SendEmail()
{
EmailSettings settings = _options.CurrentValue;
}
}
Use when:
- Configuration changes must be available without restarting.
- Change notifications are required.
- Options must be injected into a Singleton or background service.
The configuration provider must support reloads. The default JSON provider supports them with reloadOnChange.
Simple memory technique
IOptions<T>→ Fixed for the application.IOptionsSnapshot<T>→ Refreshed for each request.IOptionsMonitor<T>→ Current value plus change notifications.
For most normal settings, start with IOptions<T>. Use Snapshot or Monitor only when runtime configuration changes are genuinely required.