.NET 10 최소 API의 데이터 유효성 검사
[[academy-video-youtube({"vid": "sW_AcN-nD0Y", "start_time": "0", "title": ".NET 10 Minimal APIs에서 데이터 검증", "creator": "Tim Corey", "length": "~10m"})]]
미니멀 API는 컨트롤러 기반의 ASP.NET Core에 대한 슬림한 대안이었지만, 오랜 시간 동안 눈에 띄는 간격이 있었습니다: 들어오는 데이터를 검증하기 위한 내장 지원이 없었습니다. 이는 각 핸들러 내부에서 수동으로 검사하거나, 서드 파티 라이브러리에 의존해야 했습니다. .NET 10은 추가 패키지 없이 쿼리 문자열, 헤더 및 요청 본문에 대한 일급 데이터 주석 검증을 통해 그 간격을 메웁니다.
Tim Corey's 설명을 바탕으로 한 이 .NET 10 Minimal APIs에 대한 심층 분석은 검증 서비스 등록, 모델 클래스 주석 작성 및 설정을 망가뜨릴 수 있는 일반 접근자 한정자 함정을 피하는 방법을 설명합니다.
설정: 간단한 Minimal API
[0:44 - 1:35] 데모는 .NET 10에서 실행되는 간소한 ASP.NET Core Minimal API 프로젝트로 시작합니다. 표면적 영역은 의도적으로 작습니다: 서로 다른 모델을 받는 두 개의 POST 엔드포인트.
app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));app.MapPost("/person", (Person person) => Results.Ok(person));
app.MapPost("/login", (LoginModel login) => Results.Ok(login));Person 모델은 기본적인 식별 필드를 포함합니다. LoginModel는 자격 증명을 처리합니다: 이메일 주소, 비밀번호, 그리고 비밀번호 확인 필드를 포함합니다. 둘 다 JSON 본문으로 보냅니다. 이 시점에서는 입력 검사 자체가 전혀 없습니다; API는 빈 문자열과 형식이 잘못된 이메일 주소를 포함하여 전달받은 모든 것을 허용합니다.
.NET 10에 포함된 현대적 OpenAPI UI인 Scalar를 사용하여 브라우저에서 테스트 요청을 직접 실행할 수 있어서, 검증이 연결되기 전과 후에 API가 정확히 반환하는 것을 쉽게 확인할 수 있습니다.
검증 서비스 등록
[2:36 - 3:06] 어떠한 검증 속성도 효과를 발휘하기 전에, 서비스 수준에서 이를 선택해야 합니다. 서비스 등록 블록에서 단일 호출을 통해 모든 미니멀 API 엔드포인트 전반에서 집행을 활성화합니다:
builder.Services.AddValidation();builder.Services.AddValidation();그 한 줄이 전체 구성 단계입니다. 추가해야 할 미들웨어도 없고 연결할 파이프라인 단계도 없습니다. 서비스가 등록되면 프레임워크가 자동으로 이를 관리합니다. 이 호출을 건너뛰면, 데이터 주석 속성이 모델에 존재하지만 결코 평가되지 않으며, 요청은 포함된 내용을 막론하고 통과시킵니다.
이는 첫 번째 디버깅 단계로 기억할 가치가 있습니다: 검증이 조용히 아무것도 하지 않는다면, AddValidation()가 일반적으로 누락된 부분입니다.
클래스 모델에 검증 추가
[3:00 - 3:55] 서비스가 등록된 상태에서, 클래스 기반 모델에 검증을 추가하는 것은 데이터 주석 속성을 사용하여 속성을 장식하는 문제입니다:
public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}public class Person
{
[Required]
public string FirstName { get; set; }
[Required]
public string LastName { get; set; }
}파일의 상단에 System.ComponentModel.DataAnnotations using 지시문을 추가한 후, FirstName 및 LastName를 [Required]로 표시하면 이들이 검증되도록 보장합니다. Scalar를 통해 빈 본문으로 POST 요청을 보내면 400 Bad Request와 구조화된 오류 응답이 반환됩니다:
{
"errors": {
"FirstName": ["The FirstName field is required."],
"LastName": ["The LastName field is required."]
}
}맞춤형 오류 처리도 필터 속성도 없습니다. 프레임워크는 응답을 자동으로 생성하고, 체크는 핸들러 본문이 실행되기 전에 실행되므로 엔드포인트 로직 내에서 null을 방어할 필요가 없습니다.
레코드에 대한 검증 적용
[4:29 - 5:30] 동일한 속성은 C# 레코드에 대해 작동하지만, 레코드 속성이 주로 기본 생성자에서 정의되는 데 비해 별도의 멤버 선언으로 정의되기 때문에 구문이 약간 다릅니다.
public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);public record LoginModel(
[Required] [EmailAddress] string Email,
[Required] string Password,
[Required] string ConfirmPassword
);생성자 매개변수의 속성들은 생성된 속성에 적용되므로 [Required] 및 [EmailAddress]는 클래스에서와 정확히 같은 방식으로 동작합니다. 잘못된 이메일, 즉 "notanemail"를 포함한 요청을 보내면 이제 400가 반환되어 Email 필드가 유효하지 않음을 식별합니다.
[Compare] 속성은 또한 ConfirmPassword이 Password와 일치하는 것을 강제할 수 있습니다. 레코드에서는 속성 대상에 대한 명시적 자극이 필요한데, 컴파일러가 매개변수 자체가 아닌 생성된 멤버를 의미한다는 것을 알아야 하기 때문입니다:
[property: Compare(nameof(Password))]
string ConfirmPassword[property: Compare(nameof(Password))]
string ConfirmPassword[property:] 타겟은 컴파일러에게 속성을 매개변수가 아니라 생성된 멤버에 첨부하도록 지시합니다. 없다면, [Compare]는 컴파일되지만 검사 시에 실행되지 않습니다. 이러한 맥락에서 레코드와 클래스 작업의 가장 까다로운 부분입니다: 클래스 속성은 속성을 자연스럽게 받아들이지만, 레코드 매개변수는 멤버 수준에서 작동하는 속성에 대해 명시적 대상을 필요로 합니다.
공개 접근 한정자 요구 사항
[7:51 - 9:00] 일반적인 함정은 쉽게 놓치기 쉬우며 오류 메시지를 생성하지 않습니다. 검증 시스템은 런타임에 모델 타입을 검사하기 위해 리플렉션을 사용합니다; 리플렉션이 타입의 멤버를 찾기 위해서는, 해당 타입 자체가 public로 표시되어야 합니다.
// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }// Validation will NOT run; the class is internal by default
class Person { ... }
// Validation runs correctly
public class Person { ... }레코드도 동일한 규칙이 적용됩니다. 만약 모델이 액세스 한정자 없이 선언된다면, C#에서는 기본적으로 internal로 간주하고, 서비스는 이를 완전히 건너뜁니다. 엔드포인트는 여전히 요청을 받고, 핸들러는 여전히 실행되며, 오류는 반환되지 않습니다; 검사는 단지 조용히 아무 행위도 하지 않습니다.
이는 ASP.NET Core의 동작으로, Minimal APIs에 특화되지 않습니다, 그러나 이러한 프로젝트는 컴팩트하는 경향이 있어 개발자들이 종종 Program.cs와 같은 파일에 모델을 인라인으로 또는 정의할 때 가시성을 생각하지 않고 모델을 정의하기 때문에 여기서 더 자주 나타납니다.
프로젝트를 감시하는 빠른 방법: 엔드포인트 핸들러에 전달되는 모든 모델 타입은 명시적으로 public로 표시해야 합니다. 그렇지 않으면 어떤 속성도 프레임워크를 작동시키지 않습니다.
검증할 수 있는 것
내장된 데이터 주석 속성은 추가 작업 없이 가장 일반적인 시나리오를 다룹니다:
[Required]는 null 또는 빈 값을 거부합니다[EmailAddress]는 이메일 문자열의 형식을 검증합니다[Compare]는 두 속성이 일치하는지 확인하며, 비밀번호 확인에 유용합니다[Range]는 숫자 또는 날짜 경계를 강제합니다[StringLength]는 선택적으로 최소 값을 포함하여 문자열 길이를 제한합니다[RegularExpression]는 사용자 정의 패턴에 대해 검증합니다
이들은 모두 쿼리 문자열 매개변수, 요청 헤더 및 JSON 본문에 걸쳐 작동합니다. 동일한 모델 클래스나 레코드는 어떤 속성도 수정하지 않고 다른 소스에서 바인딩될 수 있습니다.
결론
[7:46 - 끝] AddValidation()이 등록되고 모델이 장식되면, Minimal APIs는 클래스 및 레코드 전반에 걸쳐 자동으로 입력 제약을 강제합니다. .NET 10에서 이를 작동시키려면, 간단히 builder.Services.AddValidation()을 한 번 호출하고, 표준 데이터 주석 속성으로 모델 속성을 장식하고, 모든 모델 타입이 public로 표시되도록 합니다.
레코드의 [property:] 타겟과 public 한정자 요구 사항만이 실제로 주의해야 할 사항입니다. 이는 쉽게 간과되고 컴파일 오류 대신에 조용한 실패를 일으키며, 입력 검사가 아무것도 하지 않는 것처럼 보일 때마다 이를 체크리스트에 포함하세요.
전체 소스 코드 설명을 보려면, Tim Corey의 유튜브 동영상을 시청하십시오.

