C# Configuration
Introduction
Configuration is a critical aspect of any software application, allowing developers to control application behavior without modifying and recompiling code. In modern C# web development, particularly with ASP.NET Core, configuration management has been redesigned to be flexible, extensible, and environment-aware.
In this guide, you'll learn how to effectively manage configuration settings in your C# web applications, from simple key-value pairs to complex hierarchical configurations. We'll cover the built-in configuration providers, different configuration sources, and best practices for storing sensitive information securely.
Configuration Basics in ASP.NET Core
ASP.NET Core applications use a configuration system based on key-value pairs that can be set up from multiple sources. The framework provides a unified approach to handle configuration regardless of where the settings are stored.
Core Configuration Concepts
- Configuration Providers: Sources of configuration key-value pairs
- Configuration Builder: Combines multiple providers
- Options Pattern: Strongly-typed access to configuration sections
- Environment-specific Configuration: Different settings for development/production
Setting Up Basic Configuration
The appsettings.json File
The most common configuration source in ASP.NET Core applications is the appsettings.json
file. This is automatically created when you generate a new project.
Here's a simple appsettings.json
file:
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft": "Warning",
"Microsoft.Hosting.Lifetime": "Information"
}
},
"AllowedHosts": "*",
"AppSettings": {
"SiteName": "My Awesome Website",
"AdminEmail": "[email protected]",
"MaxItemsPerPage": 20
}
}
Reading Configuration Values
To read these values in your C# code, you can use the IConfiguration
interface. Here's how you might access the configuration in a controller:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
public class HomeController : Controller
{
private readonly IConfiguration _configuration;
public HomeController(IConfiguration configuration)
{
_configuration = configuration;
}
public IActionResult Index()
{
// Reading a simple value
string siteName = _configuration["AppSettings:SiteName"];
// Reading a nested value with GetValue (with type conversion and default value)
int maxItems = _configuration.GetValue<int>("AppSettings:MaxItemsPerPage", 10);
ViewBag.SiteName = siteName;
ViewBag.MaxItems = maxItems;
return View();
}
}
The Options Pattern
While directly accessing configuration values works, using the Options pattern provides a more structured approach by binding configuration sections to strongly-typed classes.
Step 1: Create a Settings Class
First, create a class that represents your configuration section:
public class AppSettings
{
public string SiteName { get; set; }
public string AdminEmail { get; set; }
public int MaxItemsPerPage { get; set; }
}
Step 2: Register in Program.cs
In your Program.cs
file (or Startup.cs
for older projects), register the options:
var builder = WebApplication.CreateBuilder(args);
// Register the AppSettings options
builder.Services.Configure<AppSettings>(
builder.Configuration.GetSection("AppSettings"));
// Add other services...
builder.Services.AddControllersWithViews();
var app = builder.Build();
// Configure middleware...
Step 3: Inject and Use the Options
Now you can inject the options into your controllers or services:
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;
public class HomeController : Controller
{
private readonly AppSettings _appSettings;
public HomeController(IOptions<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
public IActionResult Index()
{
// Use the strongly-typed settings
ViewBag.SiteName = _appSettings.SiteName;
ViewBag.MaxItems = _appSettings.MaxItemsPerPage;
return View();
}
}
Configuration Sources
ASP.NET Core supports multiple configuration sources, which are processed in order. Later sources can override values from earlier ones.
Common Configuration Providers
- JSON files (appsettings.json)
- Environment variables
- Command-line arguments
- User secrets (development environment)
- Azure Key Vault
- In-memory collection
Environment-Specific Configuration
It's common to have different configuration settings for different environments. ASP.NET Core supports this with environment-specific configuration files:
appsettings.json
- Base configurationappsettings.Development.json
- Development overridesappsettings.Production.json
- Production overrides
For example, a typical appsettings.Development.json
might look like:
{
"Logging": {
"LogLevel": {
"Default": "Debug",
"Microsoft": "Information"
}
},
"AppSettings": {
"DetailedErrors": true
}
}
The framework automatically loads the right environment-specific file based on the ASPNETCORE_ENVIRONMENT
variable.
Working with Custom Configuration Files
Sometimes you might want to load configuration from a custom file:
var builder = WebApplication.CreateBuilder(args);
// Load custom configuration file
builder.Configuration.AddJsonFile("customsettings.json", optional: true, reloadOnChange: true);
// Continue with services registration...
Securing Sensitive Configuration
For sensitive information like connection strings, API keys, and passwords, you should avoid storing them directly in configuration files that might be committed to source control.
User Secrets (Development)
For development, use the User Secrets feature:
dotnet user-secrets init
dotnet user-secrets set "ConnectionStrings:DefaultConnection" "Server=myserver;Database=mydb;User Id=sa;Password=Secret_password!"
This stores the configuration in a location outside your project folder.
Environment Variables (Production)
For production environments, environment variables are often a better choice:
var builder = WebApplication.CreateBuilder(args);
// Environment variables will be loaded automatically
// You can also specify a prefix
builder.Configuration.AddEnvironmentVariables("MYAPP_");
On your server or cloud provider, you would set:
MYAPP_ConnectionStrings__DefaultConnection=Server=prod-db;Database=mydb;User Id=app;Password=prod_password!
Note that in environment variables, the :
separator is replaced with __
(double underscore).
Configuration Change Notification
ASP.NET Core can reload configuration when the underlying files change:
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);
To respond to these changes, you can use IOptionsSnapshot<T>
instead of IOptions<T>
:
public class HomeController : Controller
{
private readonly AppSettings _appSettings;
// IOptionsSnapshot will update when configuration changes
public HomeController(IOptionsSnapshot<AppSettings> appSettings)
{
_appSettings = appSettings.Value;
}
// Controller actions...
}
Real-World Example: Multi-Tenant Configuration
Let's build a more complex example of a multi-tenant web application where configuration changes based on the current tenant:
Step 1: Define Configuration Classes
public class TenantSettings
{
public List<Tenant> Tenants { get; set; } = new List<Tenant>();
public string DefaultTenantId { get; set; }
}
public class Tenant
{
public string Id { get; set; }
public string Name { get; set; }
public string Theme { get; set; }
public string ConnectionString { get; set; }
}
Step 2: Add Configuration in appsettings.json
{
"TenantSettings": {
"DefaultTenantId": "tenant1",
"Tenants": [
{
"Id": "tenant1",
"Name": "Company A",
"Theme": "blue",
"ConnectionString": "Server=server1;Database=CompanyA;Trusted_Connection=True;"
},
{
"Id": "tenant2",
"Name": "Company B",
"Theme": "green",
"ConnectionString": "Server=server1;Database=CompanyB;Trusted_Connection=True;"
}
]
}
}
Step 3: Create a Tenant Service
public interface ITenantService
{
Tenant GetCurrentTenant();
}
public class TenantService : ITenantService
{
private readonly TenantSettings _tenantSettings;
private readonly IHttpContextAccessor _httpContextAccessor;
public TenantService(
IOptions<TenantSettings> tenantSettings,
IHttpContextAccessor httpContextAccessor)
{
_tenantSettings = tenantSettings.Value;
_httpContextAccessor = httpContextAccessor;
}
public Tenant GetCurrentTenant()
{
// Get tenant from subdomain or header or query string
var hostName = _httpContextAccessor.HttpContext?.Request.Host.Value ?? "";
string tenantId;
if (hostName.Contains("."))
{
// Extract tenant from subdomain (tenant1.myapp.com)
tenantId = hostName.Split('.')[0];
}
else
{
// Default tenant
tenantId = _tenantSettings.DefaultTenantId;
}
// Find the tenant in settings
return _tenantSettings.Tenants.FirstOrDefault(t => t.Id == tenantId)
?? _tenantSettings.Tenants.First(t => t.Id == _tenantSettings.DefaultTenantId);
}
}
Step 4: Register and Use the Service
// In Program.cs
builder.Services.Configure<TenantSettings>(
builder.Configuration.GetSection("TenantSettings"));
builder.Services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
builder.Services.AddScoped<ITenantService, TenantService>();
// In a controller
public class HomeController : Controller
{
private readonly ITenantService _tenantService;
public HomeController(ITenantService tenantService)
{
_tenantService = tenantService;
}
public IActionResult Index()
{
var currentTenant = _tenantService.GetCurrentTenant();
ViewBag.TenantName = currentTenant.Name;
ViewBag.TenantTheme = currentTenant.Theme;
// Use the tenant-specific connection string for database operations
// var connectionString = currentTenant.ConnectionString;
return View();
}
}
Summary
Configuration management is a fundamental aspect of C# web development. ASP.NET Core provides a robust and flexible configuration system that supports multiple sources, strong typing through the Options pattern, and environment-specific settings.
Key takeaways:
- Use
appsettings.json
as your primary configuration source - Implement the Options pattern for strongly-typed configuration
- Create environment-specific configuration files
- Use User Secrets or environment variables for sensitive data
- Leverage change notifications with
IOptionsSnapshot
when needed - Consider custom configuration strategies for complex scenarios like multi-tenancy
Additional Resources
- Official Microsoft Documentation on ASP.NET Core Configuration
- User Secrets in ASP.NET Core
- Options Pattern in ASP.NET Core
Exercises
-
Create a new ASP.NET Core web application and add a custom configuration section for "EmailSettings" with properties for SMTP server, port, username, and password.
-
Implement the Options pattern to access your EmailSettings in a service or controller.
-
Create an environment-specific configuration that overrides some settings in Development mode.
-
Add a configuration value that can be changed at runtime and use
IOptionsSnapshot
to detect and respond to the changes. -
Implement a custom configuration provider that loads settings from a database table.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)