# Dotnet Csharp Configuration

> Configures Options pattern, user secrets, and feature flags. IOptions<T>, FeatureManagement.

- Skill: `rudironsoni/dotnet-csharp-configuration` (Agent Skill, multi-file: 2 files)
- Install (CLI): `npx skillmds@latest add rudironsoni/dotnet-csharp-configuration`
- Raw SKILL.md: https://api.skillmd.com/api/skills/rudironsoni/dotnet-csharp-configuration/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools, AI & ML
- License: MIT
- Author: rudironsoni (https://skillmd.com/u/rudironsoni)
- Updated: 2026-09-10
- Page: https://skillmd.com/skills/rudironsoni/dotnet-csharp-configuration

---


# dotnet-csharp-configuration

Configuration patterns for .NET applications using Microsoft.Extensions.Configuration and Microsoft.Extensions.Options.
Covers the Options pattern (`IOptions<T>`, `IOptionsMonitor<T>`, `IOptionsSnapshot<T>`), validation, user secrets,
environment-based configuration, and feature flags with `Microsoft.FeatureManagement`.

## Scope

- Options pattern (IOptions<T>, IOptionsMonitor<T>, IOptionsSnapshot<T>)
- Options validation and ValidateOnStart
- User secrets and environment-based configuration
- Feature flags with Microsoft.FeatureManagement
- Configuration source precedence

## Out of scope

- DI container mechanics and service lifetimes -- see [skill:dotnet-csharp-dependency-injection]
- EditorConfig and analyzer rule configuration -- see [skill:dotnet-editorconfig]
- Structured logging pipeline configuration -- see [skill:dotnet-structured-logging]

Cross-references: [skill:dotnet-csharp-dependency-injection] for service registration patterns,
[skill:dotnet-csharp-coding-standards] for naming conventions.

---

## Configuration Sources and Precedence

Default configuration sources in `WebApplication.CreateBuilder` (last wins):

1. `appsettings.json`
2. `appsettings.{Environment}.json`
3. User secrets (Development only)
4. Environment variables
5. Command-line arguments

````csharp

var builder = WebApplication.CreateBuilder(args);
// Sources above are loaded automatically. Add custom sources:
builder.Configuration.AddJsonFile("features.json", optional: true, reloadOnChange: true);

```csharp

---

## Options Pattern

Bind configuration sections to strongly typed classes and inject them via DI.

### Defining Options Classes

```csharp

public sealed class SmtpOptions
{
    public const string SectionName = "Smtp";

    public string Host { get; set; } = "";
    public int Port { get; set; } = 587;
    public string FromAddress { get; set; } = "";
    public bool UseSsl { get; set; } = true;
}

```text

> Options classes use `{ get; set; }` (not `init`) because the configuration binder and `PostConfigure` need to mutate
> properties. Use `[Required]` via data annotations for mandatory fields instead.

### Registration

```csharp

builder.Services
    .AddOptions<SmtpOptions>()
    .BindConfiguration(SmtpOptions.SectionName)
    .ValidateDataAnnotations()
    .ValidateOnStart();

```text

### `appsettings.json`

```json

{
  "Smtp": {
    "Host": "smtp.example.com",
    "Port": 587,
    "FromAddress": "noreply@example.com",
    "UseSsl": true
  }
}

```text

---

## Options Interfaces

| Interface             | Lifetime  | Reload Behavior                   | Use Case                        |
| --------------------- | --------- | --------------------------------- | ------------------------------- |
| `IOptions<T>`         | Singleton | Never reloads after startup       | Static config, most services    |
| `IOptionsSnapshot<T>` | Scoped    | Reloads per request/scope         | Per-request config in ASP.NET   |
| `IOptionsMonitor<T>`  | Singleton | Live reload + change notification | Singletons, background services |

### Injection Examples

```csharp

// Static -- most common, singleton-safe
public sealed class EmailService(IOptions<SmtpOptions> options)
{
    private readonly SmtpOptions _smtp = options.Value;

    public Task SendAsync(string to, string subject, string body,
        CancellationToken ct = default)
    {
        // Use _smtp.Host, _smtp.Port, etc.
        return Task.CompletedTask;
    }
}

// Live reload in singletons -- monitors config file changes
public sealed class FeatureService(IOptionsMonitor<FeatureOptions> monitor)
{
    public bool IsEnabled(string feature)
        => monitor.CurrentValue.EnabledFeatures.Contains(feature);
}

// Per-request in scoped services -- reads latest config each request
public sealed class PricingService(IOptionsSnapshot<PricingOptions> snapshot)
{
    public decimal GetMarkup() => snapshot.Value.MarkupPercent;
}

```text

### Change Notifications with `IOptionsMonitor<T>`

```csharp

public sealed class CacheService : IDisposable
{
    private readonly IDisposable? _changeListener;
    private CacheOptions _current;

    public CacheService(IOptionsMonitor<CacheOptions> monitor)
    {
        _current = monitor.CurrentValue;
        _changeListener = monitor.OnChange(updated =>
        {
            _current = updated;
            // React to config change -- flush cache, resize pool, etc.
        });
    }

    public void Dispose() => _changeListener?.Dispose();
}

```text

---

## Options Validation

### Data Annotations

```csharp

using System.ComponentModel.DataAnnotations;

public sealed class SmtpOptions
{
    public const string SectionName = "Smtp";

    [Required, MinLength(1)]
    public string Host { get; set; } = "";

    [Range(1, 65535)]
    public int Port { get; set; } = 587;

    [Required, EmailAddress]
    public string FromAddress { get; set; } = "";
}

builder.Services
    .AddOptions<SmtpOptions>()
    .BindConfiguration(SmtpOptions.SectionName)
    .ValidateDataAnnotations()
    .ValidateOnStart(); // Fail fast at startup, not on first use

```text

### `IValidateOptions<T>` (Complex Validation)

Use when validation logic requires cross-property checks or external dependencies.

```csharp

public sealed class SmtpOptionsValidator : IValidateOptions<SmtpOptions>
{
    public ValidateOptionsResult Validate(string? name, SmtpOptions options)
    {
        var failures = new List<string>();

        if (options.UseSsl && options.Port == 25)
        {
            failures.Add("Port 25 does not support SSL. Use 465 or 587.");
        }

        if (string.IsNullOrWhiteSpace(options.Host))
        {
            failures.Add("SMTP host is required.");
        }

        return failures.Count > 0
            ? ValidateOptionsResult.Fail(failures)
            : ValidateOptionsResult.Success;
    }
}

// Register the validator
builder.Services.AddSingleton<IValidateOptions<SmtpOptions>, SmtpOptionsValidator>();

```text

### `ValidateOnStart` (Fail Fast)

Always use `.ValidateOnStart()` to surface configuration errors at startup instead of at first resolution. Without it,
invalid config only throws when `IOptions<T>.Value` is first accessed.

---

## User Secrets (Development)

Store sensitive values outside source control during development.

```bash

# Initialize (once per project)
dotnet user-secrets init

# Set values
dotnet user-secrets set "Smtp:Host" "smtp.example.com"
dotnet user-secrets set "ConnectionStrings:Default" "Server=..."

# List all secrets
dotnet user-secrets list

# Clear all
dotnet user-secrets clear

```text

User secrets are stored in `~/.microsoft/usersecrets/<UserSecretsId>/secrets.json` and override `appsettings.json`
values in Development.

**Key rules:**

- Never use user secrets in production -- use environment variables, Azure Key Vault, or other vault providers
- User secrets are loaded automatically when `ASPNETCORE_ENVIRONMENT=Development`
- For non-web hosts, explicitly add: `builder.Configuration.AddUserSecrets<Program>()`

---

## Environment-Based Configuration

### Environment Variables

```csharp

// Hierarchical keys use __ (double underscore) as separator
// Environment variable: Smtp__Host=smtp.prod.com
// Maps to: configuration["Smtp:Host"]

```csharp

### Per-Environment Files

```text

appsettings.json                 # Base (all environments)
appsettings.Development.json     # Overrides for dev
appsettings.Staging.json         # Overrides for staging
appsettings.Production.json      # Overrides for prod

```json

```csharp

// Set environment via ASPNETCORE_ENVIRONMENT or DOTNET_ENVIRONMENT
// Defaults to "Production" if not set
var env = builder.Environment.EnvironmentName; // "Development", "Staging", "Production"

```csharp

### Conditional Service Registration

```csharp

if (builder.Environment.IsDevelopment())
{
    builder.Services.AddSingleton<IEmailSender, ConsoleEmailSender>();
}
else
{
    builder.Services.AddSingleton<IEmailSender, SmtpEmailSender>();
}

```text

---

## Feature Flags with Microsoft.FeatureManagement

`Microsoft.FeatureManagement.AspNetCore` provides structured feature flag support with filters, targeting, and gradual
rollout.

### Setup

```bash

dotnet add package Microsoft.FeatureManagement.AspNetCore

```bash

```csharp

builder.Services.AddFeatureManagement();

```csharp

### Configuration

```json

{
  "FeatureManagement": {
    "NewDashboard": true,
    "BetaSearch": {
      "EnabledFor": [
        {
          "Name": "Percentage",
          "Parameters": { "Value": 50 }
        }
      ]
    },
    "DarkMode": {
      "EnabledFor": [
        {
          "Name": "Targeting",
          "Parameters": {
            "Audience": {
              "Users": ["alice@example.com"],
              "Groups": [{ "Name": "Beta", "RolloutPercentage": 100 }],
              "DefaultRolloutPercentage": 0
            }
          }
        }
      ]
    }
  }
}

```text

### Usage in Code

```csharp

// Inject IFeatureManager
public sealed class DashboardController(IFeatureManager featureManager) : ControllerBase
{
    [HttpGet]
    public async Task<IActionResult> Get(CancellationToken ct = default)
    {
        if (await featureManager.IsEnabledAsync("NewDashboard"))
        {

## Detailed Examples

See [references/detailed-examples.md](references/detailed-examples.md) for complete code samples and advanced patterns.

