.NET Configuration Management
When deploying .NET applications, proper configuration management is essential to ensure your application runs correctly across different environments. This guide will walk you through the fundamentals of configuration in .NET, how to implement it effectively, and best practices for real-world scenarios.
Introduction to Configuration in .NET
Configuration in .NET refers to the settings and parameters that control how your application behaves. Instead of hardcoding values like database connection strings, API keys, or feature flags, you store them in configuration sources. This approach makes your application more flexible, secure, and maintainable.
Modern .NET applications use a configuration system built on the following key components:
- Configuration providers
- Strongly-typed configuration objects
- Environment-specific settings
- Secret management
Configuration Providers
.NET uses a provider-based configuration system that can read settings from various sources:
- JSON files (appsettings.json)
- Environment variables
- Command-line arguments
- Azure Key Vault
- User secrets (for development)
- And many more
Setting Up Basic Configuration
First, let's look at how to set up basic configuration in a .NET application:
// Program.cs in a .NET 6+ application
var builder = WebApplication.CreateBuilder(args);
// Configuration is already set up by default
var configuration = builder.Configuration;
// Access configuration values
Console.WriteLine($"App Name: {configuration["AppName"]}");
var app = builder.Build();
// Rest of your application setup
The default configuration includes:
appsettings.json
appsettings.{Environment}.json
Environment variables
Command-line arguments
Working with appsettings.json
The most common configuration source is the appsettings.json file. Here's a typical example:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"ConnectionStrings": {
"DefaultConnection": "Server=myserver;Database=mydb;User Id=myuser;Password=mypassword;"
},
"AppSettings": {
"ApiKey": "my-api-key",
"MaxItemsPerPage": 50,
"EnableCache": true
}
}
Reading Configuration Values
You can access configuration values in several ways:
// Direct access
string apiKey = configuration["AppSettings:ApiKey"];
// Nested section
var loggingSection = configuration.GetSection("Logging");
string defaultLogLevel = loggingSection["LogLevel:Default"];
// Connection string (helper method)
string connString = configuration.GetConnectionString("DefaultConnection");
Strongly-Typed Configuration
Instead of accessing configuration values directly with string keys, you can bind configuration sections to strongly-typed classes:
// Define a POCO class for your settings
public class AppSettings
{
public string ApiKey { get; set; }
public int MaxItemsPerPage { get; set; }
public bool EnableCache { get; set; }
}
// In Program.cs or Startup.cs
// Register configuration as strongly-typed object
builder.Services.Configure<AppSettings>(
builder.Configuration.GetSection("AppSettings"));
// In a controller or service using dependency injection
public class MyController : ControllerBase
{
private readonly AppSettings _appSettings;
public MyController(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public IActionResult Index()
{
// Use _appSettings.ApiKey, _appSettings.MaxItemsPerPage, etc.
return Ok($"Using API key: {_appSettings.ApiKey}");
}
}
Environment-Specific Configuration
One of the most powerful features of .NET's configuration system is the ability to have different settings for different environments (Development, Staging, Production).
Setting the Environment
The environment is determined by the ASPNETCORE_ENVIRONMENT
environment variable (or DOTNET_ENVIRONMENT
for non-web apps):
- Development (default during local development)
- Staging
- Production
Environment-Specific appsettings Files
Create environment-specific configuration files using this naming pattern:
- appsettings.json (base settings)
- appsettings.Development.json (development-specific settings)
- appsettings.Production.json (production-specific settings)
For example, you might have different database connection strings:
// appsettings.Development.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=localhost;Database=devdb;User Id=devuser;Password=devpass;"
},
"Logging": {
"LogLevel": {
"Default": "Debug"
}
}
}
// appsettings.Production.json
{
"ConnectionStrings": {
"DefaultConnection": "Server=prodserver;Database=proddb;User Id=produser;Password=prodpass;"
},
"Logging": {
"LogLevel": {
"Default": "Warning"
}
}
}
At runtime, .NET will load appsettings.json first, then overlay the environment-specific settings, with later values overriding earlier ones.
Configuration Hierarchy and Precedence
When multiple configuration sources provide the same setting, there's a defined precedence order (from lowest to highest priority):
Default values built into your application
appsettings.json
appsettings.{Environment}.json
User secrets (in Development)
Environment variables
Command-line arguments
This means command-line arguments will override environment variables, which override JSON settings.
Secure Configuration with User Secrets
For local development, you shouldn't store secrets (API keys, passwords) in appsettings.json files that might be committed to source control. Instead, use the User Secrets feature:
dotnet user-secrets init
dotnet user-secrets set "AppSettings:ApiKey" "my-secret-development-only-api-key"
Secrets are stored in your user profile, not in the project directory, keeping them out of source control.
Real-World Application: Configuring a Web API
Let's build a simple API that uses configuration effectively:
- First, set up your configuration classes:
// ExternalServiceSettings.cs
public class ExternalServiceSettings
{
public string ApiEndpoint { get; set; }
public string ApiKey { get; set; }
public int TimeoutSeconds { get; set; }
}
// CacheSettings.cs
public class CacheSettings
{
public bool Enabled { get; set; }
public int ExpirationMinutes { get; set; }
}
- Configure the services in your application startup:
// Program.cs
var builder = WebApplication.CreateBuilder(args);
// Add configuration
builder.Services.Configure<ExternalServiceSettings>(
builder.Configuration.GetSection("ExternalService"));
builder.Services.Configure<CacheSettings>(
builder.Configuration.GetSection("Cache"));
// Register a service that uses configuration
builder.Services.AddTransient<IProductService, ProductService>();
var app = builder.Build();
// Rest of your application setup
- Use the configuration in a service:
// ProductService.cs
public class ProductService : IProductService
{
private readonly ExternalServiceSettings _serviceSettings;
private readonly CacheSettings _cacheSettings;
private readonly HttpClient _httpClient;
public ProductService(
IOptions<ExternalServiceSettings> serviceSettings,
IOptions<CacheSettings> cacheSettings,
HttpClient httpClient)
{
_serviceSettings = serviceSettings.Value;
_cacheSettings = cacheSettings.Value;
_httpClient = httpClient;
_httpClient.BaseAddress = new Uri(_serviceSettings.ApiEndpoint);
_httpClient.DefaultRequestHeaders.Add("X-API-Key", _serviceSettings.ApiKey);
_httpClient.Timeout = TimeSpan.FromSeconds(_serviceSettings.TimeoutSeconds);
}
public async Task<ProductData> GetProductAsync(int id)
{
// Implement caching based on settings
if (_cacheSettings.Enabled)
{
// Check cache first before making API call
// ...
}
// Get product from external service
var response = await _httpClient.GetAsync($"api/products/{id}");
response.EnsureSuccessStatusCode();
return await response.Content.ReadFromJsonAsync<ProductData>();
}
}
- Create corresponding appsettings.json:
{
"ExternalService": {
"ApiEndpoint": "https://api.example.com/",
"ApiKey": "placeholder-will-be-replaced-by-environment-variables",
"TimeoutSeconds": 30
},
"Cache": {
"Enabled": true,
"ExpirationMinutes": 60
}
}
- In production, you might override sensitive settings with environment variables:
EXTERNALSERVICE__APIKEY=real-production-api-key
EXTERNALSERVICE__TIMEOUTSECONDS=60
Configuration in Containers and Cloud
When deploying to containers or cloud services, you'll typically use environment variables for configuration:
Docker Example
FROM mcr.microsoft.com/dotnet/aspnet:7.0
WORKDIR /app
COPY --from=build /app/publish .
ENV ASPNETCORE_ENVIRONMENT=Production
ENV ConnectionStrings__DefaultConnection="Server=db-server;Database=myapp;User=dbuser;Password=dbpass;"
ENV Logging__LogLevel__Default=Warning
ENTRYPOINT ["dotnet", "MyApp.dll"]
Azure App Service Example
In Azure Portal, you can configure Application Settings, which become environment variables for your application:
-
Name:
ConnectionStrings__DefaultConnection
Value:Server=azure-sql.database.windows.net;Database=mydb;User Id=myuser;Password=mypassword;
-
Name:
AppSettings__ApiKey
Value:my-production-api-key
Best Practices for Configuration Management
- Don't store secrets in code or configuration files that get committed to source control
- Use strongly-typed configuration instead of string-based access
- Follow the Options pattern for dependency injection
- Validate configuration at startup to fail fast if required settings are missing
- Use hierarchical configuration to organize related settings
- Prefer environment variables for sensitive data in production
- Keep configurations DRY (Don't Repeat Yourself) by using default appsettings.json and only overriding what's different
Validating Configuration
Always validate your configuration at startup:
// Extend the options setup with validation
builder.Services.AddOptions<ExternalServiceSettings>()
.Bind(builder.Configuration.GetSection("ExternalService"))
.ValidateDataAnnotations() // Validate attributes like [Required]
.Validate(settings =>
{
if (string.IsNullOrEmpty(settings.ApiEndpoint))
return false;
if (string.IsNullOrEmpty(settings.ApiKey))
return false;
if (settings.TimeoutSeconds <= 0)
return false;
return true;
}, "External service settings are invalid.")
.ValidateOnStart();
Summary
.NET's configuration system is flexible and powerful, allowing you to:
- Store settings in various sources (JSON files, environment variables, etc.)
- Override settings based on environment (Development, Staging, Production)
- Use strongly-typed configurations for type safety
- Keep sensitive information secure
- Adapt your application behavior for different deployment scenarios
By effectively managing your application's configuration, you make your code more maintainable, secure, and adaptable to different environments.
Additional Resources
- Official Microsoft Documentation: Configuration in .NET
- Options pattern in .NET
- Safe storage of app secrets in development in ASP.NET Core
Exercises
- Create a simple .NET console application that reads configuration from both appsettings.json and command-line arguments.
- Build a web API with different configuration settings for Development and Production environments.
- Implement the Options pattern with validation for a database connection configuration class.
- Practice overriding configuration values using environment variables and verify they take precedence over file-based settings.
- Set up user secrets for local development to store sensitive API keys securely.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)