ASP.NET Core Configuration Guide
ASP.NET Core Configuration Guide
Overview
ASP.NET Core uses a layered configuration system where later sources override earlier ones for the same key.
Configuration Priority Order
| Priority | Source | When Loaded |
|---|---|---|
| 1 (lowest) | appsettings.json |
Always |
| 2 | appsettings.{Environment}.json |
Always |
| 3 | User Secrets | Development only |
| 4 | Environment variables | Always |
| 5 | Command-line arguments | Always |
| 6 (highest) | Azure Key Vault (if registered) | When explicitly added in code |
Rule: Later sources override earlier ones for the same key.
Environment Detection
ASP.NET Core determines the current environment from the ASPNETCORE_ENVIRONMENT variable.
Common values:
DevelopmentStagingProduction
This is typically set in launchSettings.json for local development:
{
"profiles": {
"Dev": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "http://localhost:5056"
},
"Production": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Production"
},
"applicationUrl": "http://localhost:5055"
}
}
}
Checking environment in code
// During host building
if (builder.Environment.IsDevelopment())
{
// development-only setup
}
// After app is built
if (app.Environment.IsProduction())
{
// production-only setup
}
1. appsettings.json Files
Base configuration (always loaded)
appsettings.json
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*"
}
Environment-specific overrides
appsettings.Development.json
{
"Logging": {
"LogLevel": {
"Default": "Debug"
}
},
"ConnectionStrings": {
"MyDiaryDb": "Server=127.0.0.1;Port=5432;Database=MyDiaryDb_Dev;UserId=postgres;Password=12345;"
}
}
appsettings.Production.json
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
How it merges
When running in Development:
appsettings.json → LogLevel: Default = "Information" appsettings.Development.json → LogLevel:Default = "Debug" ← wins
The environment-specific file overrides matching keys, but non-overlapping keys from appsettings.json are still available.
2. User Secrets
What are they?
A development-only mechanism for storing sensitive config values (passwords, API keys) outside the project directory so they are never committed to source control.
Where are they stored?
| OS | Path |
|---|---|
| Windows | %APPDATA%\Microsoft\UserSecrets\<UserSecretsId>\secrets.json |
| Linux/Mac | ~/.microsoft/usersecrets/<UserSecretsId>/secrets.json |
Setup
1. Initialize (one-time per project)
dotnet user-secrets init
This adds a <UserSecretsId> to your .csproj:
<PropertyGroup>
<TargetFramework>net10.0</TargetFramework>
<UserSecretsId>a1b2c3d4-e5f6-7890-abcd-ef1234567890</UserSecretsId>
</PropertyGroup>
2. Set secrets
dotnet user-secrets set "ConnectionStrings:MyDiaryDb" "Server=127.0.0.1;Port=5432;Database=MyDiaryDb_Dev;User Id=postgres;Password=SecretPassword;"
3. List secrets
dotnet user-secrets list
4. Remove a secret
dotnet user-secrets remove "ConnectionStrings:MyDiaryDb"
5. Clear all secrets
dotnet user-secrets clear
How they are loaded
You do not need to register them manually. WebApplication.CreateBuilder(args) automatically loads User Secrets when ASPNETCORE_ENVIRONMENT=Development:
This happens internally — you don't write this yourself:
if (builder.Environment.IsDevelopment()) { builder.Configuration.AddUserSecrets(); }
Priority
User Secrets override appsettings.Development.json for the same key:
appsettings.json → ConnectionStrings:MyDiaryDb = (not set) appsettings.Development.json → ConnectionStrings:MyDiaryDb = "local-dev-string" User Secrets → ConnectionStrings:MyDiaryDb = "secret-string" ← wins
Key characteristics
| Question | Answer |
|---|---|
| In source control? | No |
| Works in Production? | No, Development only |
| Overrides appsettings.Development.json? | Yes |
| Needs manual registration? | No, automatic in Development |
| Shared across team? | No, per-developer |
When to use
- Local database passwords
- API keys for third-party services during development
- Any secret you don't want in Git
3. Environment Variables
How they work
Environment variables override all file-based config. Use __ (double underscore) as a section separator:
Linux/Mac
export ConnectionStrings__MyDiaryDb="Server=prod-server;..."
Windows (PowerShell)
$env:ConnectionStrings__MyDiaryDb = "Server=prod-server;..."
In code
// Read normally — the framework resolves the source automatically var connectionString = builder.Configuration.GetConnectionString("MyDiaryDb");
Priority
Environment variables override User Secrets and appsettings files:
appsettings.Development.json → ConnectionStrings:MyDiaryDb = "dev-string" User Secrets → ConnectionStrings:MyDiaryDb = "secret-string" Environment Variable → ConnectionStrings:MyDiaryDb = "env-string" ← wins
Common use cases
- CI/CD pipelines
- Docker containers
- Azure App Service (Application Settings)
4. Azure Key Vault
What it does
Stores secrets centrally in Azure. Your app fetches them at startup and they become part of the configuration system.
Setup
Install package
dotnet add package Azure.Identity dotnet add package Azure.Extensions.AspNetCore.Configuration.Secrets
Register in Program.cs (conditionally)
using Azure.Identity;
var builder = WebApplication.CreateBuilder(args);
// Only connect to Key Vault in non-Development environments
if (!builder.Environment.IsDevelopment())
{
var keyVaultUrl = new Uri("https://your-vault-name.vault.azure.net/");
builder.Configuration.AddAzureKeyVault(keyVaultUrl, new DefaultAzureCredential());
}
// Connection string is resolved from whatever source is available
builder.Services.AddDbContext<DataContext>(options => {
options.UseNpgsql(builder.Configuration.GetConnectionString("MyDiaryDb"));
});
Secret naming in Key Vault
Use -- as separator (Key Vault doesn't allow :):
| Key Vault Secret Name | Maps to Configuration Key |
|---|---|
ConnectionStrings--MyDiaryDb |
ConnectionStrings:MyDiaryDb |
Jwt--Key |
Jwt:Key |
Jwt--Issuer |
Jwt:Issuer |
Priority
Key Vault is the highest priority when registered (added last):
appsettings.json → ConnectionStrings:MyDiaryDb = (not set) appsettings.Production.json → ConnectionStrings:MyDiaryDb = (not set) Environment Variables → ConnectionStrings:MyDiaryDb = "env-string" Azure Key Vault → ConnectionStrings:MyDiaryDb = "vault-string" ← wins
Authentication methods (DefaultAzureCredential)
DefaultAzureCredential tries these in order:
- Environment variables (
AZURE_CLIENT_ID,AZURE_TENANT_ID,AZURE_CLIENT_SECRET) - Managed Identity (Azure App Service, VM, Container Apps)
- Visual Studio credentials
- Azure CLI (
az login) - Azure PowerShell
Why wrap in environment check
// BAD — tries to connect to Azure even in Development
var keyVaultUrl = new Uri("https://your-vault.vault.azure.net/");
builder.Configuration.AddAzureKeyVault(keyVaultUrl, new DefaultAzureCredential());
// GOOD — only connects in Production
if (!builder.Environment.IsDevelopment())
{
var keyVaultUrl = new Uri("https://your-vault.vault.azure.net/");
builder.Configuration.AddAzureKeyVault(keyVaultUrl, new DefaultAzureCredential());
}
Without the check, AddAzureKeyVault attempts to authenticate to Azure immediately at startup. If credentials are not available locally, the app crashes or hangs before it ever reads your local config files.
5. Command-Line Arguments
Highest priority among built-in sources (before Key Vault):
dotnet run --ConnectionStrings:MyDiaryDb="Server=override;..."
Useful for one-off overrides during testing.
Complete Example: Program.cs
using Azure.Identity; using Microsoft.EntityFrameworkCore;
var builder = WebApplication.CreateBuilder(args);
// Configuration is already loaded in this order:
// 1. appsettings.json
// 2. appsettings.{Environment}.json
// 3. User Secrets (Development only, automatic)
// 4. Environment variables
// 5. Command-line args
// 6. Azure Key Vault (Production only, manual)
if (!builder.Environment.IsDevelopment())
{
var keyVaultUrl = new Uri("https://your-vault.vault.azure.net/");
builder.Configuration.AddAzureKeyVault(keyVaultUrl, new DefaultAzureCredential());
}
// This resolves from whichever source has the highest priority
var connectionString = builder.Configuration.GetConnectionString("MyDiaryDb");
builder.Services.AddDbContext<DataContext>(options => { options.UseNpgsql(connectionString); });
var app = builder.Build(); app.Run();
Summary: Which source to use when
| Environment | Recommended secret source |
|---|---|
| Local Development | User Secrets |
| CI/CD Pipeline | Environment variables |
| Azure App Service | Azure Key Vault or App Settings |
| Docker/Container | Environment variables |
Quick Reference: Common Commands
Initialize user secrets
dotnet user-secrets init
Set a secret
dotnet user-secrets set "ConnectionStrings:MyDiaryDb" "your-connection-string"
List all secrets
dotnet user-secrets list
Clear all secrets
dotnet user-secrets clear
Azure CLI login (for local Key Vault access)
az login
Common Mistakes
1. Key Vault without environment check
// Crashes locally if no Azure credentials
builder.Configuration.AddAzureKeyVault(keyVaultUrl, new DefaultAzureCredential());
2. Secrets in appsettings.json committed to Git
// DON'T — this goes into source control
{
"ConnectionStrings": {
"MyDiaryDb": "Server=prod;Password=secret123;"
}
}
3. Using Production profile for local development
// Ensure your local profile uses Development
{
"Dev": {
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
4. Assuming User Secrets work in Production
They don't. User Secrets are Development-only by design.


