IConfiguration & IOptions 패턴 심화
ASP.NET Core의 구성(Configuration) 시스템은 appsettings.json, 환경 변수, 커맨드라인 인수 등을 계층적으로 병합합니다. IOptions<T> 패턴으로 강타입 설정 객체를 주입하고, IOptionsMonitor<T>로 런타임 변경을 감지할 수 있습니다.
1. 구성 소스 우선순위
섹션 제목: “1. 구성 소스 우선순위”appsettings.json ↓ (덮어씀)appsettings.{Environment}.json ↓User Secrets (개발 환경) ↓환경 변수 ↓커맨드라인 인수 (최고 우선순위)// Program.cs — 커스텀 소스 추가builder.Configuration .AddJsonFile("custom.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables(prefix: "MYAPP_") .AddCommandLine(args);2. IOptions — 기본 설정 주입
섹션 제목: “2. IOptions — 기본 설정 주입”{ "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; // 싱글톤으로 캐시됨}3. IOptions 세 가지 비교
섹션 제목: “3. IOptions 세 가지 비교”// 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));}IOptions | IOptionsSnapshot | IOptionsMonitor | |
|---|---|---|---|
| 수명 | Singleton | Scoped | Singleton |
| 변경 반영 | ✗ | 요청 단위 | 즉시 |
| Singleton에서 사용 | ✓ | ✗ | ✓ |
4. 유효성 검사
섹션 제목: “4. 유효성 검사”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 필요");5. Named Options
섹션 제목: “5. Named Options”// 같은 타입을 다른 설정으로 여러 개 등록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");}6. 커스텀 구성 소스
섹션 제목: “6. 커스텀 구성 소스”// 데이터베이스에서 설정 로드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));7. 환경 변수 이름 규칙
섹션 제목: “7. 환경 변수 이름 규칙”# 계층형 키는 __ (더블 언더스코어)로 구분export Email__SmtpHost=smtp.gmail.comexport Email__Port=587
# 배열export Urls__0=https://localhost:5001export Urls__1=https://localhost:5002구성 소스 우선순위를 이해하면 환경별 설정 오버라이드가 자연스럽습니다. 런타임 변경이 필요하면 IOptionsMonitor, 요청 범위 변경이면 IOptionsSnapshot, 고정 설정이면 IOptions를 선택하세요. ValidateOnStart()로 잘못된 설정이 있으면 앱 시작 즉시 오류를 발견하도록 하세요.