모델: 응답 데이터 서식화

등록일시: 2016-08-19 08:00,  수정일시: 2016-10-21 04:57
조회수: 7,016
이 문서는 ASP.NET Core MVC 기술을 널리 알리고자 하는 개인적인 취지로 제공되는 번역문서입니다. 이 문서에 대한 모든 저작권은 마이크로소프트에 있으며 요청이 있을 경우 언제라도 게시가 중단될 수 있습니다. 번역 내용에 오역이 존재할 수 있고 주석은 번역자 개인의 의견일 뿐이며 마이크로소프트는 이에 관한 어떠한 보장도 하지 않습니다. 번역이 완료된 이후에도 대상 제품 및 기술이 개선되거나 변경됨에 따라 원문의 내용도 변경되거나 보완되었을 수 있으므로 주의하시기 바랍니다.
본문에서는 콘텐츠 협상, Accept 헤더, 포맷터 구성 방법 등 클라이언트로 반환되는 응답의 서식을 구성하는 방법에 관한 다양한 정보들을 살펴봅니다.

ASP.NET Core MVC는 일정한 서식으로 응답 데이터를 서식화하거나 클라이언트의 요청 사양에 따라 응답 데이터를 서식화하는 기능을 기본으로 제공해줍니다.

GitHub에서 샘플 코드 확인 및 다운로드 받기

서식 전용 액션 결과

JsonResultContentResult 같은 일부 액션 결과(Action Result) 형식은 특정 서식에 특화되어 있습니다. 이런 액션 결과를 반환하는 액션은 항상 특정한 방식으로 서식화된 데이터를 반환하게 됩니다. 가령 JsonResult 형식을 반환하면 클라이언트의 설정과는 무관하게 항상 JSON으로 서식화된 데이터가 반환됩니다. 마찬가지로 ContentResult 형식을 반환하면 평문으로 서식화된 데이터가 (즉 단순한 문자열이) 반환됩니다.

노트

반드시 액션이 특정 형식을 반환해야만 하는 것은 아닙니다. MVC는 모든 개체 형식의 값을 반환할 수 있습니다. 만약 특정 액션이 IActionResult 인터페이스를 구현한 개체를 반환하고 해당 컨트롤러가 Controller 클래스를 상속받는다면, 개발자는 다양한 선택에 따라 각종 헬퍼 메서드의 도움을 받을 수 있습니다. 반면 IActionResult 인터페이스를 구현하지 않은 개체를 반환하는 액션의 결과는 적절한 IOutputFormatter 인터페이스의 구현을 통해서 직렬화됩니다.

컨트롤러에서 특정 서식의 데이터를 반환하려면 우선 해당 컨트롤러가 Controller 기반 클래스를 상속받아야 하고, JSON을 반환하려면 Json 내장 헬퍼 메서드를, 평문을 반환하려면 Content 내장 헬퍼 메서드를 사용하면 됩니다. 그리고 액션 메서드는 특정 형식을 (이를테면 JsonResult 형식을) 반환하거나 IActionResult 인터페이스를 반환해야 합니다.

다음 코드는 JSON으로 서식화된 데이터를 반환합니다:

// GET: api/authors
[HttpGet]
public JsonResult Get()
{
    return Json(_authorRepository.List());
}

이 액션의 예제 응답은 다음과 같습니다:

네트워크 요청 목록과 응답 헤더(Response Headers) 영역 양쪽 모두에서 확인할 수 있는 응답의 콘텐츠 유형이 application/json이라는 점에 유의하시기 바랍니다. 더불어 요청 헤더(Request Headers) 영역의 Accept 헤더에 지정된, 브라우저에 의해서 (이 경우 Microsoft Edge) 제공되는 옵션들의 목록도 주의해서 살펴보시기 바랍니다. 이번 절에서는 이 정보를 무시하고 있지만, 잠시 뒤에는 이 목록을 준수하는 방법에 관해서도 살펴봅니다.

역주

Internet Explorer로 이 예제를 테스트해보면 JSON 콘텐츠가 화면에 출력되는 대신, 열거나 다운로드 받을지 여부를 물어보는 프롬프트가 나타납니다. 이는 Internet Explorer 자체의 특징입니다.

평문으로 서식화된 데이터를 반환하려면 ContentResult 형식과 Content 헬퍼 메서드를 사용합니다:

// GET api/authors/about
[HttpGet("About")]
public ContentResult About()
{
    return Content("An API listing authors of docs.asp.net.");
}

이 액션의 예제 응답은 다음과 같습니다:

이번에는 반환된 Content-Typetext/plain이라는 것을 확인할 수 있습니다. 참고로 다음과 같이 간단히 문자열 응답 형식을 사용해도 이와 동일한 결과를 얻을 수 있습니다:

// GET api/authors/version
[HttpGet("version")]
public string Version()
{
    return "Version 1.0.0";
}

다양한 반환 형식이나 옵션을 갖고 있는 특수한 액션의 경우 (가령, 수행된 작업의 결과에 따라 각기 다른 HTTP 상태 코드가 반환되는 경우 등), 반환 형식으로 IActionResult를 사용하는 것이 좋습니다.

콘텐츠 협상

콘텐츠 협상(Content Negotiation)은 클라이언트가 Accept 헤더를 설정한 경우에만 발생합니다. ASP.NET Core MVC의 기본 서식은 JSON입니다. 콘텐츠 협상은 ObjectResult 클래스에 의해서 구현되며, 헬퍼 메서드에서 반환되는 상태 코드별 액션 결과들도 콘텐츠 협상 기능을 기본으로 지원합니다 (모두 ObjectResult 클래스를 기반으로 하고 있습니다). 모델 형식을 (직접 데이터 전송 형식으로 정의한 클래스를) 반환할 수도 있는데, 그러면 프레임워크가 반환되는 모델 형식을 자동으로 ObjectResult로 감싸줍니다.

가령, 다음 액션 메서드는 Ok 헬퍼 메서드와 NotFound 헬퍼 메서드를 사용하고 있습니다:

// GET: api/authors/search?namelike=th
[HttpGet("Search")]
public IActionResult Search(string namelike)
{
    var result = _authorRepository.GetByNameSubstring(namelike);
    if (!result.Any())
    {
        return NotFound(namelike);
    }
    return Ok(result);
}

기본적으로 서버에서 반환할 수 있는 다른 특정 서식이 요청된 경우가 아니라면 JSON으로 서식화된 응답이 반환됩니다. 그러나 Fiddler 같은 도구를 이용하면 다른 서식이 지정된 Accept 헤더가 설정된 요청을 생성할 수 있습니다. 이 경우, 서버에 요청된 서식으로 응답을 생성할 수 있는 포맷터가 구성되어 있다면, 클라이언트가 선호하는 서식으로 결과가 반환됩니다.

이 스크린샷을 살펴보면 Fiddler의 Composer 탭을 이용해서 Accept 헤더를 application/xml로 설정한 요청을 생성하고 있습니다. 그러나 ASP.NET Core MVC는 기본적으로 JSON만 지원하기 때문에, 다른 서식이 지정되더라도 결과는 여전히 JSON으로 서식화되어 반환됩니다. 추가적인 포맷터를 추가하는 방법은 다음 절에서 살펴봅니다.

컨트롤러 액션에서 POCOs(Plain Old CLR Objects)를 반환할 수도 있으며, 이 경우 자동으로 ASP.NET Core MVC가 반환되는 개체를 감싸는 ObjectResult 개체를 생성해줍니다. 그리고 클라이언트는 서식화되어 직렬화된 개체를 받게 됩니다 (기본 서식은 JSON으로 XML이나 다른 서식을 사용하도록 구성할 수도 있습니다). 만약 개체가 null로 반환되면, 프레임워크가 대신 204 No Content 응답을 반환해줍니다.

다음 코드는 개체 형식을 반환합니다:

// GET api/authors/ardalis
[HttpGet("{alias}")]
public Author Get(string alias)
{
    return _authorRepository.GetByAlias(alias);
}

이 예제의 경우, 올바른 저자의 별칭(Alias)이 지정된 요청은 저자의 데이터와 함께 200 OK 응답을 받게 됩니다. 반면, 유효하지 않은 별칭이 지정된 경우에는 204 No Content 응답을 받게 됩니다. 이번 예제에 대한 XML로 서식화된 응답과 JSON으로 서식화된 응답의 스크린샷은 잠시 후에 확인할 수 있습니다.

콘텐츠 협상 절차

콘텐츠 협상은 요청에 Accept 헤더가 존재하는 경우에만 수행됩니다. 요청에 Accept 헤더가 포함되어 있는 경우, 프레임워크는 우선순위에 따라 Accept 헤더에 지정되어 있는 미디어 유형들을 열거하고, Accept 헤더에 지정된 서식들 중 한 서식으로 응답을 생성할 수 있는 포맷터를 찾으려고 시도합니다. 그러나 프레임워크가 클라이언트의 요청을 만족하는 포맷터를 찾지 못한다면, 그 대신 응답을 생성할 수 있는 첫 번째 포맷터를 찾으려고 시도합니다 (개발자가 406 Not Acceptable을 반환하도록 MvcOptions의 옵션을 구성하지 않았다면). 만약 요청에 XML이 지정됐지만, XML 포맷터가 구성되어 있지 않다면 JSON 포맷터가 사용됩니다. 일반적으로 요청된 서식을 제공할 수 있는 구성된 포맷터가 존재하지 않으면, 해당 개체를 서식화할 수 있는 첫 번째 포맷터가 사용됩니다. 반면 헤더가 지정되지 않았다면 반환되는 개체를 처리할 수 있는 첫 번째 포맷터를 사용해서 응답을 직렬화합니다. 이 경우, 아무런 협상도 수행되지 않으며 서버가 사용될 서식을 일방적으로 결정합니다.

노트

만약 Accept 헤더에 /가 포함되어 있을 경우, MvcOptionsRespectBrowserAcceptHeader를 true로 설정하지 않으면 헤더가 무시됩니다.

브라우저와 콘텐츠 협상

일반적인 API 클라이언트들과는 달리, 웹 브라우저는 와일드카드를 비롯한 다양한 서식들이 지정된 Accept 헤더를 제공하는 경우가 대부분입니다. 기본적으로 프레임워크는 요청이 브라우저로부터 전달된 것을 감지하더라도, Accept 헤더를 무시하고 대신 응용 프로그램에 구성된 기본 서식으로 (별도로 구성하지 않는 한 JSON 서식으로) 콘텐츠를 반환합니다. 이 방식이 다양한 브라우저들을 이용해서 API를 사용할 때 보다 일관적인 경험을 제공해줍니다.

만약 응용 프로그램에서 브라우저의 Accept 헤더 설정을 반영하도록 구현하고자 한다면, Startup.cs 파일의 ConfigureServices 메서드에서 MVC 구성의 일부로 RespectBrowserAcceptHeader 속성을 true로 설정하여 구성이 가능합니다.

services.AddMvc(options =>
{
    options.RespectBrowserAcceptHeader = true; // false by default
}

포맷터 구성하기

만약 응용 프로그램에서 기본 서식인 JSON 외에도 추가적인 서식들을 지원해야 한다면, 해당 서식들을 project.json 파일에 추가적인 의존성으로 추가하고 MVC가 이를 지원하도록 구성해야 합니다. 포맷터에는 입력 포맷터와 출력 포맷터가 별도로 존재합니다. 입력 포맷터는 모델 바인딩(Model Binding)에 의해서 사용되는 반면, 출력 포맷터는 응답을 서식화할 때 사용됩니다. 그리고 사용자 지정 포맷터를 구성할 수도 있습니다.

XML 서식 지원 추가하기

XML 서식화 기능을 추가하려면, 먼저 "Microsoft.AspNetCore.Mvc.Formatters.Xml" 패키지를 project.json 파일의 의존성 목록에 추가해야 합니다.

그런 다음, Startup.cs 파일에서 MVC의 구성에 XmlSerializerFormatters를 추가합니다:

public void ConfigureServices(IServiceCollection services)
{
    services.AddMvc()
        .AddXmlSerializerFormatters();

    services.AddScoped<IAuthorRepository, AuthorRepository>();
}

또는, 출력 포맷터만 별도로 추가할 수도 있습니다:

services.AddMvc(options =>
{
    options.OutputFormatters.Add(new XmlSerializerOutputFormatter());
});

이 두 가지 접근방식은 System.Xml.Serialization.XmlSerializer를 이용해서 결과를 직렬화합니다. 만약 원한다면, 관련 포멧터를 추가해서 대신 System.Runtime.Serialization.DataContractSerializer를 사용할 수도 있습니다:

services.AddMvc(options =>
{
    options.OutputFormatters.Add(new XmlDataContractSerializerOutputFormatter());
});

XML 서식화 관련 기능을 추가하고 나면, 다음 Fiddler 예제 데모에서 볼 수 있는 것처럼 컨트롤러 메서드가 요청의 Accept 헤더에 따라 적절한 서식으로 결과를 반환하게 됩니다.

이 스크린샷의 Inspectors 탭을 살펴보면 Raw GET 요청에 Accept: application/xml 헤더가 설정되어 있는 것을 확인할 수 있습니다. 그리고 응답 패인을 살펴보면 Content-Type: application/xml 헤더와 XML로 직렬화된 Author 개체를 확인할 수 있습니다.

이번에는 Composer 탭에서 요청을 수정해서 Accept 헤더 값을 application/json으로 설정합니다. 그런 다음, 요청을 실행하면 응답이 JSON으로 서식화되어 반환될 것입니다:

이 스크린샷을 살펴보면, Accept: application/json으로 설정된 요청의 헤더와, 동일하게 설정된 응답의 Content-Type을 확인할 수 있습니다. Author 개체는 JSON으로 서식화되어 응답의 본문에 담겨서 반환됩니다.

특정 서식 강제하기

특정 액션의 응답 서식을 제한하고 싶다면 [Produces] 필터를 적용하면 됩니다. [Produces] 필터는 지정된 액션의 (또는 컨트롤러의) 응답 서식을 지정합니다. 다른 대부분의 필터처럼 이 필터도 액션이나 컨트롤러, 또는 전역 범위에 적용할 수 있습니다.

[Produces("application/json")]
public class AuthorsController

이렇게 [Produces] 필터를 지정하면, 응용 프로그램에 다른 포맷터들이 구성되어 있고 클라이언트로부터 사용 가능한 다양한 서식들이 설정된 Accept 헤더가 전달되더라도, AuthorsController에 존재하는 모든 액션들은 무조건 JSON으로 서식화된 응답을 반환하게 됩니다. 필터를 전역 범위에 적용하는 방법을 비롯한, 필터에 대한 보다 자세한 정보들은 필터(Filters)를 참고하시기 바랍니다.

특수 사례 포맷터

일부 특수한 사례들은 기본 포맷터를 통해서 구현됩니다. 가령 반환 형식이 string일 경우, 기본적으로 text/plain 서식으로 서식화되는데 (Accept 헤더를 통해서 text/html 이 요청된 경우), 이 동작은 TextOutputFormatter를 제거해서 중지시킬 수 있습니다. 다음 코드에서 볼 수 있는 것처럼 포맷터는 Startup.cs 파일의 Configure 메서드에서 제거할 수 있습니다. 또한, 모델 개체를 반환하는 액션의 경우에는 null이 반환되면 자동으로 204 No Content 응답이 반환되는데, 이 동작은 HttpNoContentOutputFormatter를 제거해서 중지시킬 수 있습니다. 다음은 TextOutputFormatterHttpNoContentOutputFormatter를 제거하는 코드입니다.

services.AddMvc(options =>
{
    options.OutputFormatters.RemoveType<TextOutputFormatter>();
    options.OutputFormatters.RemoveType<HttpNoContentOutputFormatter>();
});

이렇게 TextOutputFormatter가 제거되면 string 반환 형식은 406 Not Acceptable을 반환하게 됩니다. 다만 TextOutputFormatter는 제거됐으나, XML 포맷터가 구성되어 있다면 XML 포맷터를 통해서 string 반환 형식이 서식화된다는 점에 주의하시기 바랍니다.

그리고 HttpNoContentOutputFormatter가 제거되고 나면 null 개체는 구성된 포맷터를 이용해서 서식화 됩니다. 예를 들어서, JSON 포맷터는 간단히 본문이 null인 응답을 반환하는 반면, XML 포맷터는 xsi:nil="true" 어트리뷰트가 설정된 빈 XML 요소를 반환합니다.

URL로 응답 서식 매핑하기

클라이언트에서 쿼리 문자열이나 경로의 일부분, 또는 .xml 및 .json 등의 서식별 파일 확장자 같은 URL의 일부분을 이용해서 특정 서식을 요청할 수도 있습니다. 이때, 다음과 같이 요청 경로를 이용한 매핑이 API에 적용된 라우트에 설정되어 있어야 합니다:

역주

갑자기 API라는 단어가 등장하는 이유는 단지 본문의 예제가 Web API로 만들어져 있기 때문입니다. 액션 메서드라는 단어로 치환해도 무방하므로 문맥 파악에 혼동 없으시기 바랍니다.

[FormatFilter]
public class ProductsController
{
    [Route("[controller]/[action]/{id}.{format?}")]
    public Product GetById(int id)

이와 같이 라우트를 설정하면 선택적인 파일 확장자를 이용해서 특정 서식을 요청할 수 있습니다. 클래스에 적용된 [FormatFilter] 어트리뷰트는 RouteData에 서식 값이 존재하는지 확인하고 응답이 생성될 때 응답 서식을 적절한 포맷터와 매핑시켜 줍니다.

URL 응답 서식 매핑 예제
라우트 포맷터
/products/GetById/5 기본 출력 포맷터
/products/GetById/5.json JSON 포맷터 (구성된 경우)
/products/GetById/5.xml XML 포맷터 (구성된 경우)