ASP.NET Core DI 내부 구조 — 서비스 컨테이너 심화
ASP.NET Core는 Microsoft.Extensions.DependencyInjection을 내장 IoC 컨테이너로 사용합니다. 단순한 등록과 해결(resolve)을 넘어 내부 동작을 이해하면 수명 충돌, 성능 문제, 고급 패턴을 올바르게 적용할 수 있습니다.
1. 서비스 수명(Lifetime)
섹션 제목: “1. 서비스 수명(Lifetime)”builder.Services.AddSingleton<ICache, MemoryCache>(); // 앱 생명주기 동안 1개builder.Services.AddScoped<IDbContext, AppDbContext>(); // 요청당 1개builder.Services.AddTransient<IEmailSender, SmtpSender>(); // 요청마다 새 인스턴스수명 충돌 (Captive Dependency)
섹션 제목: “수명 충돌 (Captive Dependency)”// 위험: Singleton이 Scoped를 주입받으면 Scoped가 Singleton처럼 동작public class MySingleton(IScopedService scoped) { } // 잘못된 패턴
// 개발 환경에서 ValidateScopes 활성화로 감지builder.Host.UseDefaultServiceProvider(opt =>{ opt.ValidateScopes = builder.Environment.IsDevelopment(); opt.ValidateOnBuild = true;});2. 팩토리 등록
섹션 제목: “2. 팩토리 등록”// 람다 팩토리builder.Services.AddSingleton<IConnection>(sp =>{ var config = sp.GetRequiredService<IConfiguration>(); return new SqlConnection(config["ConnectionString"]);});
// ImplementationFactory로 조건 분기builder.Services.AddScoped<IPaymentGateway>(sp =>{ var env = sp.GetRequiredService<IHostEnvironment>(); return env.IsProduction() ? new StripeGateway(sp.GetRequiredService<StripeOptions>()) : new MockGateway();});3. Keyed Services (.NET 8+)
섹션 제목: “3. Keyed Services (.NET 8+)”// 등록builder.Services.AddKeyedSingleton<IStorage, S3Storage>("cloud");builder.Services.AddKeyedSingleton<IStorage, LocalStorage>("local");
// 해결public class FileService( [FromKeyedServices("cloud")] IStorage cloudStorage, [FromKeyedServices("local")] IStorage localStorage){ }
// 직접 해결var cloud = sp.GetRequiredKeyedService<IStorage>("cloud");4. IServiceScopeFactory — Singleton에서 Scoped 사용
섹션 제목: “4. IServiceScopeFactory — Singleton에서 Scoped 사용”public class BackgroundWorker(IServiceScopeFactory scopeFactory){ public async Task ProcessAsync() { // 새 스코프 생성 → Scoped 서비스 안전하게 사용 await using var scope = scopeFactory.CreateAsyncScope(); var db = scope.ServiceProvider.GetRequiredService<AppDbContext>(); await db.SaveChangesAsync(); }}5. 오픈 제네릭 등록
섹션 제목: “5. 오픈 제네릭 등록”// IRepository<T> 를 한 번에 등록builder.Services.AddScoped(typeof(IRepository<>), typeof(EfRepository<>));
// 사용public class ProductService(IRepository<Product> repo) { }public class OrderService(IRepository<Order> repo) { }6. IOptions 패턴
섹션 제목: “6. IOptions 패턴”// appsettings.json의 "Email" 섹션을 EmailOptions에 바인딩builder.Services.Configure<EmailOptions>( builder.Configuration.GetSection("Email"));
// 주입public class EmailService(IOptions<EmailOptions> options){ private readonly EmailOptions _opts = options.Value;}
// 런타임 변경 감지public class DynamicService(IOptionsMonitor<AppOptions> monitor){ public void OnChange() => monitor.OnChange(opts => Reload(opts));}7. 성능 최적화
섹션 제목: “7. 성능 최적화”// GetRequiredService vs GetService// GetService는 null 반환 → null 체크 필요// GetRequiredService는 없으면 예외 → 안전
// 빌드 시 유효성 검사builder.Host.UseDefaultServiceProvider(opt => opt.ValidateOnBuild = true); // 등록 오류를 시작 시점에 발견
// ActivatorUtilities — 일부 매개변수만 DI로 해결var obj = ActivatorUtilities.CreateInstance<MyService>(sp, "extra-param");8. 서비스 데코레이터 패턴
섹션 제목: “8. 서비스 데코레이터 패턴”// 기존 등록을 래핑builder.Services.AddSingleton<IOrderService, OrderService>();builder.Services.Decorate<IOrderService, LoggingOrderService>();
// Scrutor 라이브러리 사용 시:// services.Decorate<IOrderService>((inner, sp) =>// new LoggingOrderService(inner, sp.GetRequiredService<ILogger>()));ASP.NET Core DI는 단순 등록을 넘어 Keyed Services, 오픈 제네릭, IServiceScopeFactory 등 다양한 고급 패턴을 지원합니다. 수명 충돌은 ValidateScopes와 ValidateOnBuild로 개발 단계에서 조기 발견하고, Singleton에서 Scoped 서비스가 필요하면 반드시 IServiceScopeFactory를 사용하세요.