Environment-Based Configuration in ASP.NET Core
Every application behaves differently across environments. You want detailed error pages in development, but never in production. You want a local database for testing, but a managed instance in staging. ASP.NET Core's environment system makes this straightforward.
The Hosting Environment
ASP.NET Core uses the ASPNETCORE_ENVIRONMENT environment variable to determine the current environment. The framework recognises three conventional values: Development, Staging, and Production. If the variable is not set, the default is Production.
var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();
// Check the current environment
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/error");
app.UseHsts();
}
app.Run();
You can also check for custom environments:
if (app.Environment.IsEnvironment("QA"))
{
// QA-specific configuration
}
Environment-Specific Configuration Files
WebApplication.CreateBuilder() loads configuration files in this order:
appsettings.json— base configuration for all environmentsappsettings.{Environment}.json— overrides for the current environment- User secrets (Development only)
- Environment variables
- Command-line arguments
Later sources override earlier ones. So appsettings.Production.json overrides values in appsettings.json when running in Production:
{
"ConnectionStrings": {
"Default": "Server=localhost;Database=MyApp;Trusted_Connection=true"
},
"Logging": {
"LogLevel": {
"Default": "Information"
}
},
"FeatureFlags": {
"EnableBetaFeatures": false
}
}
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft.AspNetCore": "Information"
}
},
"FeatureFlags": {
"EnableBetaFeatures": true
}
}
{
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
In Production, the connection string comes from appsettings.json (unless overridden by an environment variable), and the log level is set to Warning.
User Secrets for Development
Never store secrets (API keys, connection strings with passwords) in appsettings.json — it ends up in source control. In Development, use User Secrets:
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:Default" "Server=localhost;Database=MyApp;User=sa;Password=s3cret"
dotnet user-secrets set "ExternalApi:ApiKey" "dev-key-12345"
User secrets are stored outside the project directory and loaded automatically in Development. They are never committed to source control.
Environment Variables for Production
In Production, use environment variables or a secrets manager. ASP.NET Core maps environment variable names to configuration keys using double underscores as section separators:
# Sets ConnectionStrings:Default
export ConnectionStrings__Default="Server=prod-db;Database=MyApp;User=app;Password=***"
# Sets ExternalApi:ApiKey
export ExternalApi__ApiKey="prod-key-98765"
In containerised deployments, set these in your Dockerfile, Docker Compose file, or Kubernetes secrets:
services:
web:
environment:
- ASPNETCORE_ENVIRONMENT=Production
- ConnectionStrings__Default=Server=db;Database=MyApp;User=app;Password=***
Conditional Service Registration
Register services differently based on the environment:
var builder = WebApplication.CreateBuilder(args);
if (builder.Environment.IsDevelopment())
{
builder.Services.AddSingleton<IEmailSender, ConsoleEmailSender>();
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
}
else
{
builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>();
}
This is cleaner than using #if DEBUG preprocessor directives because it responds to the runtime environment, not the build configuration. You can run a Release build locally with ASPNETCORE_ENVIRONMENT=Development and still get development behaviour.
Launch Settings for Local Development
The launchSettings.json file controls environment variables when running locally with dotnet run or from Visual Studio:
{
"profiles": {
"Development": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
},
"Staging": {
"commandName": "Project",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Staging"
}
}
}
}
Switch profiles to test environment-specific behaviour locally without changing deployed configuration.
Environment Tag Helpers in Razor
If you use Razor views, the <environment> tag helper conditionally renders markup:
<environment include="Development">
<link rel="stylesheet" href="~/css/site.css" />
<script src="~/js/site.js"></script>
</environment>
<environment exclude="Development">
<link rel="stylesheet" href="~/css/site.min.css" />
<script src="~/js/site.min.js"></script>
</environment>
Key Takeaways
Use appsettings.{Environment}.json for environment-specific overrides, user secrets for development credentials, and environment variables for production secrets. Check the environment at startup to register different services and configure different middleware. Never rely on #if DEBUG for environment decisions — the runtime environment and build configuration are independent concerns.