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.

Example.cs
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:

Example.cs
if (app.Environment.IsEnvironment("QA"))
{
    // QA-specific configuration
}

Environment-Specific Configuration Files

WebApplication.CreateBuilder() loads configuration files in this order:

  1. appsettings.json — base configuration for all environments
  2. appsettings.{Environment}.json — overrides for the current environment
  3. User secrets (Development only)
  4. Environment variables
  5. Command-line arguments

Later sources override earlier ones. So appsettings.Production.json overrides values in appsettings.json when running in Production:

appsettings.json
{
  "ConnectionStrings": {
    "Default": "Server=localhost;Database=MyApp;Trusted_Connection=true"
  },
  "Logging": {
    "LogLevel": {
      "Default": "Information"
    }
  },
  "FeatureFlags": {
    "EnableBetaFeatures": false
  }
}
appsettings.Development.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsoft.AspNetCore": "Information"
    }
  },
  "FeatureFlags": {
    "EnableBetaFeatures": true
  }
}
appsettings.Production.json
{
  "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:

terminal
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:

terminal
# 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:

docker-compose.yml
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:

Program.cs
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:

Properties/launchSettings.json
{
  "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:

page.html
<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.