builder.Services 등록 메서드 상세 설명
builder.Services는 DI(의존성 주입) 컨테이너입니다. 여기에 “이런 기능/클래스를 쓰겠다”고 등록해 두면, 앱이 실행될 때 필요한 곳에 ASP.NET Core가 알아서 만들어 넣어 줍니다. 아래 메서드들은 모두 이 컨테이너에 무언가를 등록하는 역할입니다.
1. AddCors
csharp
builder.Services.AddCors(options => options.AddPolicy("FrontPage", policy =>
policy.WithOrigins(frontPageOrigins)
.WithMethods("GET", "POST")
.WithHeaders("Content-Type", "X-Admin-Key")));
역할: CORS 정책을 정의합니다. 정의만 할 뿐, 실제 적용은 아래 미들웨어가 합니다.
csharp
app.UseCors("FrontPage"); // 정의한 정책을 요청 파이프라인에 적용
| 메서드 | 의미 |
|---|---|
AddPolicy("FrontPage", ...) | 이름이 있는 정책을 등록 |
WithOrigins(...) | 허용할 오리진 목록 |
WithMethods("GET","POST") | 허용할 HTTP 메서드 (PUT, DELETE는 차단됨) |
WithHeaders(...) | 허용할 요청 헤더 |
참고로 AllowAnyOrigin(), AllowAnyMethod(), AllowAnyHeader()도 있지만 모두 허용이라 보안상 권장되지 않고, 이 코드는 필요한 것만 여는 방식을 썼습니다. 또한 AllowCredentials()를 쓰지 않았기 때문에 쿠키 같은 자격 증명 포함 요청은 허용되지 않습니다 (헤더 키 방식이라 필요 없음).
2. AddControllers
csharp
builder.Services.AddControllers();
역할: [ApiController] 클래스(컨트롤러)를 사용하는 데 필요한 기능을 등록합니다.
포함되는 것들:
- 컨트롤러 탐색 (어셈블리에서
ControllerBase를 상속한 클래스 찾기) - 모델 바인딩 (요청 JSON, 쿼리스트링을 C# 객체로 변환)
- JSON 직렬화/역직렬화 (
System.Text.Json) - 모델 검증 (
[Required]등 어트리뷰트 검사)
AddControllers() 후 app.MapControllers()를 호출해야 컨트롤러가 실제 엔드포인트로 연결됩니다. 등록(Add)과 사용(Map/Use)이 짝을 이루는 구조입니다.
참고:
AddControllersWithViews()는 Razor 뷰까지 포함한 MVC용이고,AddControllers()는 View 없이 API 전용입니다.
3. AddHttpClient
csharp
builder.Services.AddHttpClient<AiService>(c => c.Timeout = TimeSpan.FromSeconds(40));
역할: 외부 HTTP API를 호출하기 위한 HttpClient를 팩토리(IHttpClientFactory) 방식으로 등록합니다. 이 코드는 타입 지정 클라이언트(typed client) 패턴입니다.
AiService는 이렇게 HttpClient를 생성자로 받습니다.
csharp
public class AiService
{
private readonly HttpClient _http;
public AiService(HttpClient http) => _http = http; // 40초 타임아웃이 설정된 HttpClient가 주입됨
}
왜 new HttpClient()를 직접 쓰지 않나?
| 직접 생성 | AddHttpClient 사용 |
|---|---|
| 요청마다 만들면 소켓 고갈(socket exhaustion) 위험 | 내부 연결 풀을 팩토리가 관리 |
| 싱글톤으로 오래 쓰면 DNS 변경을 반영 못 함 | 핸들러를 주기적으로 교체해 DNS 갱신 반영 |
| 설정이 코드 곳곳에 흩어짐 | 등록 시점에 한 번 설정 (타임아웃, 기본 주소, 헤더) |
Timeout을 40초로 둔 것은 AI API 응답이 오래 걸릴 수 있어서로 보입니다 (기본값은 100초). 필요하면 c.BaseAddress, c.DefaultRequestHeaders 등도 여기서 설정할 수 있고, Polly 같은 재시도 정책도 이어서 붙일 수 있습니다.
4. AddSingleton / AddScoped (그리고 AddTransient)
csharp
builder.Services.AddSingleton<IsoCatalog>();
builder.Services.AddScoped<CountryRepository>();
builder.Services.AddScoped<ApprovalService>();
역할: 클래스를 DI 컨테이너에 등록하되, 인스턴스의 수명(lifetime) 을 지정합니다.
| 수명 | 인스턴스 생성 | 적합한 용도 |
|---|---|---|
| Singleton | 앱 전체에서 딱 1개 공유 | 불변 데이터, 캐시, 설정 (예: IsoCatalog) |
| Scoped | HTTP 요청 1건당 1개 (요청 안에서는 공유) | DB 컨텍스트, 리포지토리, 서비스 (예: CountryRepository) |
| Transient | 주입받을 때마다 새로 생성 | 가볍고 상태 없는 유틸리티 |
이 프로젝트의 선택이 합리적인 이유:
IsoCatalog: ISO 국가 코드 같은 변하지 않는 목록이라 한 번 만들어 공유하면 효율적입니다.CountryRepository,ApprovalService: 요청 단위로 상태/DB 연결을 다루므로 Scoped가 일반적입니다.
주의할 점 (수명 불일치): Singleton이 Scoped를 주입받으면 안 됩니다. 예를 들어 IsoCatalog(Singleton)가 생성자에서 CountryRepository(Scoped)를 받으면, 요청이 끝나도 Scoped 객체가 살아남는 문제가 생겨 개발 환경에서는 예외가 납니다. 수명이 긴 쪽이 짧은 쪽에 의존하지 않도록 하세요.
참고로 AddHttpClient<AiService>로 등록된 AiService는 기본적으로 Transient입니다.
5. AddEndpointsApiExplorer
csharp
builder.Services.AddEndpointsApiExplorer();
역할: 앱에 어떤 엔드포인트(경로, 메서드, 파라미터, 응답 타입)가 있는지 메타데이터를 수집할 수 있게 해 줍니다.
이 자체는 화면에 아무것도 보여주지 않고, 뒤의 Swagger가 “문서를 만들 재료”를 가져가는 통로입니다. 특히 Minimal API(app.MapGet(...)) 엔드포인트를 Swagger에 보이게 하려면 필수이고, 컨트롤러만 쓰더라도 관례적으로 함께 등록합니다.
6. AddSwaggerGen
csharp
builder.Services.AddSwaggerGen();
역할: 위에서 수집한 엔드포인트 정보로 OpenAPI(Swagger) 명세 문서(JSON)를 생성하는 기능을 등록합니다. (Swashbuckle.AspNetCore 패키지)
등록만으로는 부족하고, 미들웨어로 노출해야 합니다. 이 프로젝트는 개발 환경에서만 켭니다.
csharp
if (app.Environment.IsDevelopment())
{
app.UseSwagger(); // /swagger/v1/swagger.json 문서 제공
app.UseSwaggerUI(); // /swagger 에서 브라우저용 테스트 화면 제공
}
옵션으로 문서 제목, X-Admin-Key 헤더 입력 버튼 등을 추가할 수도 있습니다.
csharp
builder.Services.AddSwaggerGen(c =>
{
c.AddSecurityDefinition("AdminKey", new OpenApiSecurityScheme
{
Type = SecuritySchemeType.ApiKey,
In = ParameterLocation.Header,
Name = "X-Admin-Key"
});
});
이 프로젝트처럼 /api 전체가 API 키를 요구한다면, 위처럼 설정해야 Swagger UI에서 “Authorize” 버튼으로 키를 넣고 테스트할 수 있습니다. 설정하지 않으면 Swagger UI에서 호출 시 모두 401이 납니다.
전체 구조 한눈에 보기
ASP.NET Core는 “등록(Services) → 빌드 → 사용(Middleware)” 3단계입니다.
builder.Services.AddXxx() ← 1. 기능/클래스를 DI에 "등록" (재료 준비)
var app = builder.Build(); ← 2. 컨테이너 확정
app.UseXxx() / MapXxx() ← 3. 요청 파이프라인에 "적용" (실제 사용)
등록 (builder.Services) | 적용 (app) |
|---|---|
AddCors | UseCors("FrontPage") |
AddControllers | MapControllers() |
AddEndpointsApiExplorer + AddSwaggerGen | UseSwagger() + UseSwaggerUI() |
AddSingleton/Scoped/HttpClient | 컨트롤러 등의 생성자 주입으로 사용 |
Add...만 하고 Use.../Map...을 빠뜨리면 아무 일도 일어나지 않으니, 기능이 동작하지 않을 때는 이 짝이 맞는지부터 확인해 보세요.