Skip to main content

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

  1. Configuration Providers: Sources of configuration key-value pairs
  2. Configuration Builder: Combines multiple providers
  3. Options Pattern: Strongly-typed access to configuration sections
  4. 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:

json
{
"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:

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

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

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

csharp
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

  1. JSON files (appsettings.json)
  2. Environment variables
  3. Command-line arguments
  4. User secrets (development environment)
  5. Azure Key Vault
  6. 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 configuration
  • appsettings.Development.json - Development overrides
  • appsettings.Production.json - Production overrides

For example, a typical appsettings.Development.json might look like:

json
{
"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:

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

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

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

csharp
builder.Configuration.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true);

To respond to these changes, you can use IOptionsSnapshot<T> instead of IOptions<T>:

csharp
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

csharp
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

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

csharp
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

csharp
// 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:

  1. Use appsettings.json as your primary configuration source
  2. Implement the Options pattern for strongly-typed configuration
  3. Create environment-specific configuration files
  4. Use User Secrets or environment variables for sensitive data
  5. Leverage change notifications with IOptionsSnapshot when needed
  6. Consider custom configuration strategies for complex scenarios like multi-tenancy

Additional Resources

Exercises

  1. 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.

  2. Implement the Options pattern to access your EmailSettings in a service or controller.

  3. Create an environment-specific configuration that overrides some settings in Development mode.

  4. Add a configuration value that can be changed at runtime and use IOptionsSnapshot to detect and respond to the changes.

  5. 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! :)