모델: 모델 바인딩

등록일시: 2016-08-11 08:00,  수정일시: 2016-11-11 14:36
조회수: 8,692
이 문서는 ASP.NET Core MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 HTTP 요청으로부터 가져온 데이터를 액션 메서드의 매개변수에 매핑시켜주는 ASP.NET Core MVC의 기능인 모델 바인딩(Model Binding)에 관해서 살펴봅니다.

모델 바인딩 소개

ASP.NET Core MVC의 모델 바인딩(Model Binding)은 HTTP 요청에서 가져온 데이터를 액션 메서드의 매개변수에 매핑시켜주는 기능입니다. 이때 매핑의 대상이 되는 매개변수는 문자열이나 정수, 또는 실수 같은 간단한 형식일 수도 있고, 혹은 클래스 같은 복합 형식일 수도 있습니다. 이는 MVC의 매우 유용한 기능 중 하나인데, 전달받은 데이터를 대응하는 매개변수에 매핑하는 작업은 데이터의 크기나 복잡도와 무관하게 매우 빈번하게 반복되는 시나리오이기 때문입니다. MVC는 이 문제를 바인딩이라는 기능으로 추상화함으로써 해결했으며, 그 결과 개발자는 거의 차이점이 없는 동일한 코드의 여러 버전을 매번 재작성해서 응용 프로그램의 곳곳에 남겨둘 필요가 없어졌습니다. 게다가 문자열을 적절한 형식으로 변환하는 코드를 직접 작성하는 일은 지루할 뿐만 아니라 오류를 만들어내기 쉬운 작업이기도 합니다.

모델 바인딩의 동작 방식

MVC는 HTTP 요청을 전달받으면 특정 컨트롤러의 특정 액션 메서드로 요청을 라우트합니다. 이때 MVC는 라우트 데이터에 지정된 정보를 기준으로 실행할 액션 메서드를 결정하며, HTTP 요청에서 가져온 값들을 액션 메서드의 매개변수에 바인딩합니다. 예를 들어서, 다음과 같은 URL을 살펴보겠습니다:

http://contoso.com/movies/edit/2

라우트 템플릿이 {controller=Home}/{action=Index}/{id?}와 같이 구성되어 있는 경우, 이 movies/edit/2 라는 URL은 Movies 컨트롤러의 Edit 액션 메서드로 라우트되며, id라는 이름의 선택적 매개변수를 함께 받습니다. 여기에 대응하는 액션 메서드의 코드는 아마도 다음과 같을 것입니다:

1
public IActionResult Edit(int? id)

노트

URL 라우트 문자열은 대소문자를 구분하지 않습니다.

MVC가 요청 데이터를 액션 매개변수에 바인드할 때 적용하는 기준은 이름입니다. 다시 말해서, 매개변수 자체의 이름 및 매개변수가 갖고 있는 쓰기 가능한 public 속성들의 이름을 이용해서 각각의 매개변수에 매핑할 값들을 검색합니다. 가령 이번 예제에서 유일한 액션 매개변수의 이름은 id이며, MVC는 라우트 값 중에서 찾아낸 동일한 이름의 값으로 이 매개변수 값을 바인드합니다. MVC는 라우트 값 외에도 요청의 다양한 영역으로부터 가져온 데이터들을 이용해서 바인딩을 수행하는데, 이 작업에는 정해진 순서가 존재합니다. 다음은 모델 바인딩이 검색을 수행하는 순서대로 정리한 데이터 원본들의 목록입니다:

  1. 폼 값: POST 메서드를 통해서 HTTP 요청에 담겨서 전송된 폼 값들. (jQuery를 이용한 POST 요청 등을 포함합니다.)
  2. 라우트 값: 라우팅(Routing)에 의해서 제공되는 라우트 값들의 모음.
  3. 쿼리 문자열: URI의 쿼리 문자열 부분.

노트

폼 값, 라우트 데이터, 쿼리 문자열은 모두 이름-값 쌍의 형태를 갖고 있습니다.

모델 바인딩은 이 순서에 따라 id라는 이름을 갖고 있는 키를 검색하기 시작합니다. 그러나 폼 값 중에는 id라는 이름의 키가 존재하지 않기 때문에, 다시 라우트 값에서 해당 키를 찾기 시작합니다. 이번 예제에서는 바로 이 단계에서 일치하는 키가 발견됩니다. 그 결과 바인딩이 수행되고 값은 정수 2로 변환됩니다. 만약 동일한 요청이 Edit(string id) 같은 액션 메서드로 라우트된다면 값은 문자열 "2"로 변환될 것입니다.

지금까지는 단순 형식만 살펴봤습니다. MVC에서 단순 형식이란 모든 .NET 기본 형식과 문자열 형식 변환기가 존재하는 형식을 말합니다. 그러나 액션 메서드의 매개변수가 Movie 형식 같은 클래스인 경우에는 그 속성으로 단순 형식과 복합 형식이 모두 존재할 수 있으며, 이런 경우에도 MVC의 모델 바인딩은 잘 동작합니다. 모델 바인딩은 매핑할 값을 찾기 위해서 리플렉션과 재귀를 이용해서 복합 형식의 속성들을 하나씩 탐색합니다. 먼저 모델 바인딩은 속성에 값을 바인드하기 위해서 parameter_name.property_name 패턴의 키를 검색합니다. 만약 일치하는 패턴의 키를 가진 값을 찾지 못했다면 다시 속성 이름만 사용해서 바인드를 시도합니다. 속성이 Collection 형식 계열의 형식인 경우에는 parameter_name[index]  또는 그냥 [index] 형태와 일치하는 키의 값을 검색합니다. 모델 바인딩은 Dictionary 형식의 키가 단순 형식인 경우에도 이와 비슷하게 parameter_name[key]  또는 그냥 [key] 를 검색합니다. 여기서 지원되는 키들은 HTML의 필드 이름이나 동일한 모델 형식을 대상으로 태그 헬퍼가 생성하는 필드 이름들과 같습니다. 결과적으로 이로 인해서 값의 라운드 트립이 가능해지며, 생성 및 수정 작업 시 바운드 된 데이터가 유효성 검사를 통과하지 못하는 경우에도 편리하게 사용자가 입력한 값들이 폼 필드에 그대로 남아있게 됩니다.

바인딩이 가능하려면 클래스에 public 기본 생성자가 존재해야만 하며, 바인딩 될 멤버는 반드시 쓰기 가능한 public 속성이어야 합니다. 모델 바인딩이 수행될 때, public 기본 생성자를 통해서만 클래스의 인스턴스가 생성되며, 그 이후에 속성들이 설정됩니다.

특정 매개변수가 바인딩되면, 모델 바인딩은 해당 이름에 대한 값 검색을 중지하고 다음 매개변수를 바인드하기 위한 과정으로 넘어갑니다. 그러나 바인딩에 실패하더라도 MVC는 오류를 던지지 않습니다. 모델 상태 오류는 ModelState.IsValid 속성을 검사해서 조회가 가능합니다.

노트

컨트롤러의 ModelState 속성에 담긴 각 항목들은 Errors 속성을 갖고 있는 ModelStateEntry 개체입니다. 그러나 이 컬렉션을 직접 조회해야만 하는 경우는 거의 없습니다. 대신 ModelState.IsValid 속성을 사용하시기 바랍니다.

그 밖에도 모델 바인딩을 수행할 때 MVC가 반드시 감안해야 할 몇 가지 특수한 데이터 형식들이 존재합니다:

  • IFormFile, IEnumerable<IFormFile>: HTTP 요청의 일부로 전송된 하나 이상의 업로드된 파일들.
  • CancelationToken: 비동기 컨트롤러의 작업을 취소하는 용도로 사용됩니다.

이런 형식들도 액션 매개변수 또는 클래스 형식의 속성에 바인딩 될 수 있습니다.

모델 바인딩이 완료되면 유효성 검사가 실행됩니다. 기본으로 제공되는 모델 바인딩은 거의 대부분의 개발 시나리오에서 훌륭하게 동작합니다. 그러나 특별한 요구 사항이 존재할 경우에는 내장 동작을 사용자 지정해서 확장할 수도 있습니다.

어트리뷰트로 모델 바인딩 동작 사용자 지정하기

MVC는 다양한 원본들에 대한 모델 바인딩의 기본 동작을 지정할 수 있는 몇 가지 어트리뷰트들을 제공해줍니다. 가령, [BindRequired] 어트리뷰트나 [BindNever] 어트리뷰트를 적용해서 특정 속성의 바인딩을 필수로 지정하거나, 반대로 바인딩이 수행되지 않도록 지정할 수도 있습니다. 또는 기본 데이터 원본을 재정의하거나 모델 바인더의 데이터 원본을 지정할 수도 있습니다. 다음은 제공되는 모델 바인딩 어트리뷰트들의 목록입니다:

  • [BindRequired]: 이 어트리뷰트는 바인딩이 발생하지 않으면 모델 상태 오류를 추가합니다.
  • [BindNever]: 모델 바인더에게 해당 매개변수를 바인드하지 않도록 지시합니다.
  • [FromHeader], [FromQuery], [FromRoute], [FromForm]: 이 어트리뷰트들을 사용해서 값을 가져올 특정 바인딩 원본을 명시적으로 지정합니다.
  • [FromServices]: 이 어트리뷰트는 의존성 주입을 이용해서 서비스로부터 가져온 값으로 매개변수를 바인딩합니다.
  • [FromBody]: 구성된 포맷터를 이용해서 요청 본문으로부터 가져온 값으로 데이터를 바인딩합니다. 이때 요청에 지정된 콘텐츠 유형(Content-Type)에 따라 포맷터가 선택됩니다.
  • [ModelBinder]: 기본 모델 바인더, 바인딩 원본 및 이름을 재정의하기 위해서 사용됩니다.

이 어트리뷰트들은 모델 바인딩의 기본 동작을 재정의해야 할 필요가 있을 때 매우 유용한 도구입니다.

요청 본문의 서식화된 데이터 바인딩하기

요청 데이터는 JSON, XML, 그리고 그 밖의 다른 다양한 서식으로 전달될 수 있습니다. [FromBody] 어트리뷰트를 적용해서 요청 본문으로부터 가져온 데이터로 매개변수를 바인딩하도록 지시할 경우, MVC는 요청의 콘텐츠 유형(Content-Type)을 기준으로 구성된 포맷터의 모음을 이용해서 요청의 데이터를 처리합니다. MVC는 JSON 데이터를 처리하기 위한 JsonInputFormatter 클래스를 기본으로 제공해주며, XML이나 다른 사용자 지정 서식을 처리하기 위한 별도의 포맷터들을 추가할 수도 있습니다.

노트

액션 메서드마다 단 하나의 매개변수에만 [FromBody] 어트리뷰트를 지정할 수 있습니다. ASP.NET Core MVC 런타임은 요청 스트림을 읽어들이는 역할을 포맷터에 위임합니다. 일단 특정 매개변수를 위해서 요청 스트림이 읽혀지고 나면, 일반적으로 [FromBody] 어트리뷰트가 지정된 다른 매개변수를 바인딩하기 위해서 다시 요청 스트림을 읽는 것은 불가능합니다.

노트

기본 포맷터는 JsonInputFormatterJson.NET을 기반으로 하고 있습니다.

명시적으로 포맷터에 대한 어트리뷰트가 적용되지 않았다면, ASP.NET은 Content-Type 헤더와 매개변수의 형식을 기반으로 입력 포맷터를 선택합니다. 만약, XML이나 기타 다른 서식을 선호한다면 반드시 Startup.cs 파일에 그에 대한 구성을 추가해줘야 합니다. 그리고 그 전에 먼저 NuGet을 이용해서 Microsoft.AspNetCore.Mvc.Formatters.Xml 패키지 등에 대한 참조를 가져와야 합니다. 관련 구동 코드는 아마도 다음과 비슷할 것입니다:

1
2
3
4
5
public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
       .AddXmlSerializerFormatters();
}

프로젝트의 루트에 위치한 Startup.cs 파일에는 ASP.NET 응용 프로그램의 서비스들을 구성하기 위한 용도로 사용되는 services라는 인자를 매개변수로 받는 ConfigureServices 메서드가 존재합니다. 이번 예제 코드에서는 XML 포맷터를 MVC가 응용 프로그램에 제공하게 될 서비스로 추가하고 있습니다. 이 AddMvc 메서드에 전달되는 options 인자를 이용하면 응용 프로그램 구동 시, 필터나 포맷터, 그리고 다른 시스템 옵션들을 추가하거나 관리할 수 있습니다. 그런 다음, 컨트롤러 클래스나 액션 메서드에 Consumes 어트리뷰트를 적용해서 작업하고자 하는 서식을 지정합니다.