콘텐츠로 이동

IConfiguration & IOptions 패턴 심화

ASP.NET Core의 구성(Configuration) 시스템은 appsettings.json, 환경 변수, 커맨드라인 인수 등을 계층적으로 병합합니다. IOptions<T> 패턴으로 강타입 설정 객체를 주입하고, IOptionsMonitor<T>로 런타임 변경을 감지할 수 있습니다.


appsettings.json
↓ (덮어씀)
appsettings.{Environment}.json
User Secrets (개발 환경)
환경 변수
커맨드라인 인수 (최고 우선순위)
// Program.cs — 커스텀 소스 추가
builder.Configuration
.AddJsonFile("custom.json", optional: true, reloadOnChange: true)
.AddEnvironmentVariables(prefix: "MYAPP_")
.AddCommandLine(args);

appsettings.json
{
"Email": {
"SmtpHost": "smtp.example.com",
"Port": 587,
"UseTls": true
}
}
public class EmailOptions
{
public const string SectionName = "Email";
public string SmtpHost { get; set; } = "";
public int Port { get; set; } = 25;
public bool UseTls { get; set; }
}
// 등록
builder.Services.Configure<EmailOptions>(
builder.Configuration.GetSection(EmailOptions.SectionName));
// 주입
public class EmailService(IOptions<EmailOptions> options)
{
private readonly EmailOptions _opts = options.Value; // 싱글톤으로 캐시됨
}

// IOptions<T> — Singleton, 앱 시작 시점 값 고정
public class Service1(IOptions<MyOptions> opts)
{
void Use() => _ = opts.Value; // 항상 동일
}
// IOptionsSnapshot<T> — Scoped, 요청마다 갱신
public class Service2(IOptionsSnapshot<MyOptions> opts)
{
void Use() => _ = opts.Value; // 요청 시점 값
}
// IOptionsMonitor<T> — Singleton, 파일 변경 즉시 반영
public class Service3(IOptionsMonitor<MyOptions> monitor)
{
void Use() => _ = monitor.CurrentValue; // 최신 값
// 변경 이벤트 구독
IDisposable? _sub;
void Subscribe() =>
_sub = monitor.OnChange(opts => Reload(opts));
}
IOptionsIOptionsSnapshotIOptionsMonitor
수명SingletonScopedSingleton
변경 반영요청 단위즉시
Singleton에서 사용

using System.ComponentModel.DataAnnotations;
public class DatabaseOptions
{
[Required]
public string ConnectionString { get; set; } = "";
[Range(1, 100)]
public int MaxPoolSize { get; set; } = 10;
[Url]
public string? HealthCheckUrl { get; set; }
}
// 등록 + 유효성 검사
builder.Services
.AddOptions<DatabaseOptions>()
.BindConfiguration("Database")
.ValidateDataAnnotations()
.ValidateOnStart(); // 시작 시점에 즉시 검증
// 커스텀 유효성 검사
builder.Services
.AddOptions<DatabaseOptions>()
.BindConfiguration("Database")
.Validate(opts =>
{
return opts.MaxPoolSize <= 50 || opts.ConnectionString.Contains("pooling=true");
}, "풀 크기가 50 초과면 연결 문자열에 pooling=true 필요");

// 같은 타입을 다른 설정으로 여러 개 등록
builder.Services.Configure<SmtpOptions>("Primary",
builder.Configuration.GetSection("Email:Primary"));
builder.Services.Configure<SmtpOptions>("Backup",
builder.Configuration.GetSection("Email:Backup"));
// Named Options 주입
public class EmailRouter(IOptionsMonitor<SmtpOptions> monitor)
{
private SmtpOptions Primary => monitor.Get("Primary");
private SmtpOptions Backup => monitor.Get("Backup");
}

// 데이터베이스에서 설정 로드
public class DbConfigSource(string connectionString) : IConfigurationSource
{
public IConfigurationProvider Build(IConfigurationBuilder builder)
=> new DbConfigProvider(connectionString);
}
public class DbConfigProvider(string connectionString)
: ConfigurationProvider
{
public override void Load()
{
using var conn = new SqlConnection(connectionString);
var rows = conn.Query<(string Key, string Value)>(
"SELECT [Key], [Value] FROM AppConfig");
Data = rows.ToDictionary(r => r.Key, r => r.Value);
}
}
// 등록
builder.Configuration.Add(new DbConfigSource(connectionString));

Terminal window
# 계층형 키는 __ (더블 언더스코어)로 구분
export Email__SmtpHost=smtp.gmail.com
export Email__Port=587
# 배열
export Urls__0=https://localhost:5001
export Urls__1=https://localhost:5002

구성 소스 우선순위를 이해하면 환경별 설정 오버라이드가 자연스럽습니다. 런타임 변경이 필요하면 IOptionsMonitor, 요청 범위 변경이면 IOptionsSnapshot, 고정 설정이면 IOptions를 선택하세요. ValidateOnStart()로 잘못된 설정이 있으면 앱 시작 즉시 오류를 발견하도록 하세요.