Skip to content

Configuration in C# — Complete Guide

DodaTech Updated 2026-06-28 5 min read

In this tutorial, you will learn about Configuration in C#. We cover key concepts, practical examples, and best practices to help you master this topic.

Hook

Applications need to behave differently in development, staging, and production without code changes. The .NET configuration system provides a unified way to read settings from JSON files, environment variables, command-line arguments, and custom providers, all through the IConfiguration abstraction.

Learning Path

graph LR
  A[Configuration] --> B[appsettings.json]
  A --> C[Environment Variables]
  A --> D[Options Pattern]
  B --> E[Configuration Hierarchy]
  D --> F[IOptions]
  style A fill:#4a90d9,color:#fff
  style B fill:#4a90d9,color:#fff
  style C fill:#4a90d9,color:#fff
  style D fill:#4a90d9,color:#fff
  style E fill:#4a90d9,color:#fff
  style F fill:#4a90d9,color:#fff

The Configuration Hierarchy

C# configuration follows a layered approach where later sources override earlier ones.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;

var builder = new ConfigurationBuilder()
    .SetBasePath(Directory.GetCurrentDirectory())
    .AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
    .AddJsonFile($"appsettings.{Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"}.json",
        optional: true)
    .AddEnvironmentVariables()
    .AddCommandLine(args);

IConfigurationRoot configuration = builder.Build();

Reading Configuration Values

Access configuration through the IConfiguration interface.

// appsettings.json
{
  "AppSettings": {
    "ApplicationName": "MyApp",
    "Version": "1.0.0",
    "MaxItems": 100,
    "FeatureFlags": {
      "EnableNewUI": true,
      "UseCache": false
    }
  }
}

// Reading values
string appName = configuration["AppSettings:ApplicationName"];
string version = configuration.GetValue<string>("AppSettings:Version");
int maxItems = configuration.GetValue<int>("AppSettings:MaxItems");
bool enableNewUi = configuration.GetValue<bool>("AppSettings:FeatureFlags:EnableNewUI");

Console.WriteLine($"{appName} v{version}, MaxItems: {maxItems}, NewUI: {enableNewUi}");

Output:

MyApp v1.0.0, MaxItems: 100, NewUI: True

The Options Pattern

Bind configuration sections to strongly-typed classes using the options pattern.

public class AppSettings
{
    public string ApplicationName { get; set; } = "";
    public string Version { get; set; } = "";
    public int MaxItems { get; set; }
    public FeatureFlags FeatureFlags { get; set; } = new();
}

public class FeatureFlags
{
    public bool EnableNewUI { get; set; }
    public bool UseCache { get; set; }
}

// In Startup / Program.cs
var services = new ServiceCollection();
services.Configure<AppSettings>(
    configuration.GetSection("AppSettings"));

var provider = services.BuildServiceProvider();
var options = provider.GetRequiredService<IOptions<AppSettings>>();
AppSettings settings = options.Value;

Console.WriteLine($"App: {settings.ApplicationName}, Version: {settings.Version}");

IOptions, IOptionsSnapshot, and IOptionsMonitor

Choose the right options interface for your needs.

// IOptions<T>: Singleton, reads configuration once at registration
public class MyService
{
    private readonly AppSettings _settings;
    public MyService(IOptions<AppSettings> options)
    {
        _settings = options.Value; // Fixed for lifetime
    }
}

// IOptionsSnapshot<T>: Scoped, reads per request (reloads when changed)
public class MyScopedService
{
    private readonly AppSettings _settings;
    public MyScopedService(IOptionsSnapshot<AppSettings> options)
    {
        _settings = options.Value; // Fresh per scope
    }
}

// IOptionsMonitor<T>: Singleton, reads current value (reloads on change)
public class MyMonitoredService
{
    private readonly IOptionsMonitor<AppSettings> _monitor;
    public MyMonitoredService(IOptionsMonitor<AppSettings> monitor)
    {
        _monitor = monitor;
    }

    public void ShowSettings()
    {
        AppSettings current = _monitor.CurrentValue;
        Console.WriteLine($"Current version: {current.Version}");
    }
}

Environment Variables

Override settings with environment variables using a colon delimiter (or double underscore on Linux).

export AppSettings__ApplicationName="MyProductionApp"
export AppSettings__MaxItems=200
string appName = configuration["AppSettings:ApplicationName"];
Console.WriteLine($"From env: {appName}");

Custom Configuration Providers

Create custom configuration sources for databases, secrets, or APIs.

public class DatabaseConfigurationSource : IConfigurationSource
{
    private readonly string _connectionString;

    public DatabaseConfigurationSource(string connectionString)
    {
        _connectionString = connectionString;
    }

    public IConfigurationProvider Build(IConfigurationBuilder builder) =>
        new DatabaseConfigurationProvider(_connectionString);
}

public class DatabaseConfigurationProvider : ConfigurationProvider
{
    private readonly string _connectionString;

    public DatabaseConfigurationProvider(string connectionString)
    {
        _connectionString = connectionString;
    }

    public override void Load()
    {
        // Load settings from database
        Data = new Dictionary<string, string?>
        {
            ["DatabaseSettings:Timeout"] = "30",
            ["DatabaseSettings:RetryCount"] = "3"
        };
    }
}

User Secrets

For development, use the Secret Manager tool to store sensitive data.

dotnet user-secrets init
dotnet user-secrets set "DbPassword" "dev-password-123"
builder.AddUserSecrets<Program>();

Common Mistakes

  1. Hardcoding configuration keys: Use strongly-typed options classes instead of magic string keys throughout the codebase.

  2. Not using reloadOnChange: When reloadOnChange: true, the configuration system watches for file changes and updates IOptionsSnapshot and IOptionsMonitor automatically.

  3. Storing secrets in appsettings.json: Use User Secrets in development and Azure Key Vault, HashiCorp Vault, or environment variables in production.

  4. Forgetting environment-specific files: Use appsettings.Development.json, appsettings.Staging.json, and appsettings.Production.json for environment-specific overrides.

  5. Mixing case in configuration keys: Configuration keys are case-insensitive by default in .NET, but environment variables on Linux are case-sensitive. Use consistent casing.

Practice Questions

  1. Create a configuration class for email settings (SMTP host, port, username, password) and bind it from appsettings.json.

  2. Implement a feature flag system using configuration that can toggle features on and off at runtime.

  3. Write a custom configuration provider that reads settings from a REST API endpoint.

  4. Challenge: Build a configuration validation system using DataAnnotations attributes on options classes.

FAQ

What is the difference between IOptions, IOptionsSnapshot, and IOptionsMonitor?

IOptions is singleton and reads once. IOptionsSnapshot is scoped and reads per request. IOptionsMonitor is singleton but updates when configuration changes.

How do I validate options at startup?

Use services.AddOptions().Bind(config).ValidateDataAnnotations() or implement IValidateOptions.

{{< faq "Can I use JSON arrays in configuration?" "Yes. Use GetSection to access arrays: configuration.GetSection("AllowedHosts").Get<string[]>()" >}}

{{< faq "How do I handle hierarchical configuration keys?" "Use a colon (:) as the separator: "Parent:Child:Property". On Linux, use a double underscore (__) for environment variables." >}}

Is configuration thread-safe?

Yes. IConfiguration is immutable once built. IOptionsMonitor provides atomic updates.

Mini Project: Feature Flag System

Build a simple feature flag system using configuration.

using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;

public class FeatureFlags
{
    public bool NewCheckoutFlow { get; set; }
    public bool DarkMode { get; set; }
    public bool BetaSearch { get; set; }
    public int MaxRetryCount { get; set; } = 3;
}

public class CheckoutService
{
    private readonly IOptionsMonitor<FeatureFlags> _flags;

    public CheckoutService(IOptionsMonitor<FeatureFlags> flags)
    {
        _flags = flags;
    }

    public void Checkout()
    {
        if (_flags.CurrentValue.NewCheckoutFlow)
        {
            Console.WriteLine("Using new checkout flow with improved UX");
        }
        else
        {
            Console.WriteLine("Using legacy checkout flow");
        }
    }
}

// appsettings.json
// {
//   "FeatureFlags": {
//     "NewCheckoutFlow": true,
//     "DarkMode": false,
//     "BetaSearch": true
//   }
// }

var builder = new ConfigurationBuilder()
    .AddJsonFile("appsettings.json")
    .Build();

var services = new ServiceCollection();
services.Configure<FeatureFlags>(builder.GetSection("FeatureFlags"));
services.AddTransient<CheckoutService>();

var provider = services.BuildServiceProvider();
var checkout = provider.GetRequiredService<CheckoutService>();
checkout.Checkout();

Output:

Using new checkout flow with improved UX

The .NET configuration system is a powerful, flexible foundation for managing application settings. Combined with the options pattern, your C# applications can adapt to different environments without code changes or redeployments.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro