Hardcoded string key'lerle konfigürasyon okumak, runtime hatalarına ve güç test edilebilirliğe zemin hazırlar. .NET'in yapılandırma sistemi, hem esneklik hem de tip güvenliği sunuyor.

Yapılandırma Kaynakları ve Öncelik Sırası

.NET, birden fazla kaynaktan yapılandırma okur. Sonraki kaynak bir öncekini geçersiz kılar:

// WebApplication.CreateBuilder varsayılan kaynak sırası (öncelik azalıyor):
// 1. Command-line args       → dotnet run --Logging:LogLevel:Default=Debug
// 2. Environment variables   → ASPNETCORE_URLS=http://+:8080
// 3. User secrets            → Sadece Development ortamında
// 4. appsettings.{env}.json  → appsettings.Production.json
// 5. appsettings.json        → Temel ayarlar

// appsettings.json
{
    "App": {
        "Name": "MyApp",
        "MaxRetry": 3,
        "Features": {
            "NewDashboard": false,
            "BetaApi": false
        }
    },
    "Limits": {
        "MaxUploadMb": 10,
        "RequestsPerMinute": 100
    }
}

// appsettings.Production.json — sadece farkları yaz
{
    "App": {
        "Features": {
            "NewDashboard": true
        }
    },
    "Limits": {
        "RequestsPerMinute": 500
    }
}

IConfiguration: Ham Okuma

// String key ile okuma — güvensiz, typo riski var
public class MyService(IConfiguration config)
{
    public void DoWork()
    {
        var name    = config["App:Name"];           // null dönebilir
        var maxRetry = config.GetValue<int>("App:MaxRetry", defaultValue: 3);
        var section  = config.GetSection("Limits");
        var maxUpload = section.GetValue<int>("MaxUploadMb");
    }
}

Strongly Typed Config: Options Pattern

// Seçenek sınıfları
public class AppOptions
{
    public const string SectionName = "App";

    public string Name       { get; init; } = "";
    public int    MaxRetry   { get; init; } = 3;
    public FeatureOptions Features { get; init; } = new();
}

public class FeatureOptions
{
    public bool NewDashboard { get; init; }
    public bool BetaApi      { get; init; }
}

public class LimitOptions
{
    public const string SectionName = "Limits";

    [Range(1, 100)]
    public int MaxUploadMb { get; init; } = 10;

    [Range(1, 10000)]
    public int RequestsPerMinute { get; init; } = 100;
}

// Program.cs — kayıt
builder.Services.AddOptions<AppOptions>()
    .BindConfiguration(AppOptions.SectionName)
    .ValidateDataAnnotations()       // [Range], [Required] vb. kontrol
    .ValidateOnStart();              // Uygulama başlarken doğrula

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

IOptions, IOptionsSnapshot ve IOptionsMonitor Farkları

// IOptions<T> — Singleton, uygulama başında bir kez okunur, değişiklik algılamaz
public class ReportService(IOptions<AppOptions> options)
{
    private readonly AppOptions _opts = options.Value; // Değişmez

    public string GetAppName() => _opts.Name;
}

// IOptionsSnapshot<T> — Scoped, her request'te taze değer (reloadable)
// Sadece Scoped/Transient servislerde kullanılabilir
public class ThrottleMiddleware(IOptionsSnapshot<LimitOptions> options) : IMiddleware
{
    public async Task InvokeAsync(HttpContext ctx, RequestDelegate next)
    {
        var limit = options.Value.RequestsPerMinute; // Request'e göre taze
        // Rate limit kontrolü...
        await next(ctx);
    }
}

// IOptionsMonitor<T> — Singleton uyumlu, değişiklik eventi fırlatır
public class FeatureFlagService(IOptionsMonitor<AppOptions> monitor,
                                 ILogger<FeatureFlagService> logger)
{
    private AppOptions _current = monitor.CurrentValue;

    // Singleton'da constructor'da kayıt
    public FeatureFlagService(IOptionsMonitor<AppOptions> monitor,
                               ILogger<FeatureFlagService> logger)
    {
        _current = monitor.CurrentValue;

        // Dosya değiştiğinde otomatik güncellenir
        monitor.OnChange(newValue =>
        {
            logger.LogInformation("Feature flags güncellendi");
            _current = newValue;
        });
    }

    public bool IsEnabled(string feature) => feature switch
    {
        "NewDashboard" => _current.Features.NewDashboard,
        "BetaApi"      => _current.Features.BetaApi,
        _               => false
    };
}

User Secrets: Development'ta Hassas Veri

// Terminal
// dotnet user-secrets init
// dotnet user-secrets set "Email:Password" "gizli-sifre"
// dotnet user-secrets set "ConnectionStrings:Default" "Server=..."

// Kod tarafında değişiklik yok — IConfiguration normal gibi okur
// User secrets sadece Development'ta yüklenir, appsettings'i override eder

Custom Validation

// DataAnnotations yetersizse IValidateOptions kullanın
public class LimitOptionsValidator : IValidateOptions<LimitOptions>
{
    public ValidateOptionsResult Validate(string? name, LimitOptions options)
    {
        var errors = new List<string>();

        if (options.MaxUploadMb is < 1 or > 100)
            errors.Add("MaxUploadMb 1 ile 100 arasında olmalı");

        if (options.RequestsPerMinute < options.MaxUploadMb)
            errors.Add("RequestsPerMinute, MaxUploadMb'den büyük olmalı");

        return errors.Any()
            ? ValidateOptionsResult.Fail(errors)
            : ValidateOptionsResult.Success;
    }
}

// Program.cs
builder.Services.AddSingleton<IValidateOptions<LimitOptions>, LimitOptionsValidator>();
builder.Services.AddOptions<LimitOptions>()
    .BindConfiguration("Limits")
    .ValidateOnStart();

Test'te Yapılandırma

// Test'te IOptions mock'lamak yerine Options.Create kullanın
var opts = Options.Create(new AppOptions
{
    Name = "TestApp",
    MaxRetry = 1,
    Features = new FeatureOptions { NewDashboard = true }
});

var sut = new ReportService(opts);

// Entegrasyon testinde WebApplicationFactory ile override
var factory = new WebApplicationFactory<Program>()
    .WithWebHostBuilder(builder =>
    {
        builder.ConfigureAppConfiguration((ctx, config) =>
        {
            config.AddInMemoryCollection(new Dictionary<string, string?>
            {
                ["Limits:RequestsPerMinute"] = "1000",
                ["App:Features:BetaApi"]     = "true"
            });
        });
    });

Yapılandırma yönetiminde altın kural: ham string key okumak yerine strongly typed options kullanın, ValidateOnStart() ile hataları erken yakalayın ve hassas bilgileri user secrets veya environment variable'larda tutun.