ASP.NET Core 웹 API의 컨트롤러 작업 반환 형식

참고 항목

이 문서의 최신 버전은 아닙니다. 현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

Important

이 정보는 상업적으로 출시되기 전에 실질적으로 수정될 수 있는 시험판 제품과 관련이 있습니다. Microsoft는 여기에 제공된 정보에 대해 어떠한 명시적, 또는 묵시적인 보증을 하지 않습니다.

현재 릴리스는 이 문서의 .NET 8 버전을 참조 하세요.

샘플 코드 보기 및 다운로드(다운로드 방법)

ASP.NET Core에서는 웹 API 컨트롤러 작업 반환 형식에 다음 옵션을 제공합니다.

이 문서에서는 각 반환 형식을 사용하는 것이 가장 적합한 경우를 설명합니다.

특정 형식

가장 기본적인 작업은 기본 또는 복합 데이터 형식을 반환합니다(예: string 또는 사용자 지정 개체). 다음 작업을 사용하여 사용자 지정 Product 개체의 컬렉션을 반환합니다.

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

보호할 알려진 조건 없이 특정 형식을 반환하는 것으로 충분합니다. 매개 변수 제약 조건 유효성 검사가 필요하지 않으므로 위의 작업은 매개 변수를 허용하지 않습니다.

여러 반환 형식이 가능한 경우 기본 또는 복합적인 반환 형식과 ActionResult 반환 형식을 혼합하는 것이 일반적입니다. 이 종류의 동작을 수용하기 위해 IActionResult 또는 ActionResult<T> 중 하나가 필요합니다. 이 문서에서는 여러 반환 형식의 몇 가지 샘플을 제공합니다.

IEnumerable<T> 또는 IAsyncEnumerable<T> 반환

반환 IEnumerable<T> 또는 IAsyncEnumerable<T> 성능 고려 사항을 참조하세요.

ASP.NET Core는 응답에 쓰기 전에 IEnumerable<T>을 반환하는 작업의 결과를 버퍼링합니다. 비동기 반복을 보장하기 위해 작업 서명의 반환 형식을 IAsyncEnumerable<T>로 선언합니다. 궁극적으로 반복 모드는 반환되는 기본 구체적인 형식을 기반으로 하며 선택한 포맷터는 결과가 처리되는 방식에 영향을 줍니다.

  • System.Text.Json 포맷터를 사용하는 경우 MVC는 결과를 스트리밍하기 위해 추가된 System.Text.Json 지원을 사용합니다.
  • Newtonsoft.Json를 사용하거나 XML-based 포맷터와 함께 사용하면 결과가 버퍼링됩니다.

다음과 같은 판매 가격이 책정된 제품 레코드를 IEnumerable<Product>로 반환하는 작업을 살펴보겠습니다.

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

다음은 위 작업의 IAsyncEnumerable<Product>에 해당합니다.

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

IActionResult 형식

한 작업에 여러 ActionResult 반환 형식을 사용할 수 있는 경우 IActionResult 반환 형식이 적절합니다. ActionResult 형식은 다양한 HTTP 상태 코드를 나타냅니다. ActionResult에서 파생되는 비추상 클래스는 유효한 반환 형식입니다. 이 범주에서 일반적인 반환 형식은 BadRequestResult(400), NotFoundResult(404) 및 OkObjectResult(200)입니다. 또는 ControllerBase 클래스의 편의 메서드를 사용하여 작업에서 ActionResult 형식을 반환할 수 있습니다. 예를 들어 return BadRequest();return new BadRequestResult();의 약식 형태입니다.

이 작업 유형에는 여러 반환 형식 및 경로가 있기 때문에 [ProducesResponseType] 특성을 자유롭게 사용할 필요가 있습니다. 이 특성은 Swagger와 같은 도구에서 생성한 웹 API 도움말 페이지에 대한 설명이 포함된 응답 세부 정보를 생성합니다. [ProducesResponseType]은 작업에서 반환한 알려진 형식 및 HTTP 상태 코드을 나타냅니다.

동기화 작업

두 가지 가능한 반환 형식이 포함된 다음과 같은 동기 작업을 사용합니다.

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

위 작업에서:

  • id에서 표시하는 제품이 내부 데이터 저장소에 없는 경우 404 상태 코드가 반환됩니다. NotFound 편의 메서드는 return new NotFoundResult();의 축약형으로 호출됩니다.
  • 제품이 존재하는 경우 Product 개체와 함께 200 상태 코드가 반환됩니다. Ok 편의 메서드는 return new OkObjectResult(product);의 축약형으로 호출됩니다.

비동기 작업

두 가지 가능한 반환 형식이 포함된 다음과 같은 비동기 작업을 사용합니다.

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(CreateAsync_IActionResult), new { id = product.Id }, product);
}

위 작업에서:

  • 제품 설명에 "XYZ 위젯"이 포함되어 있는 경우 400 상태 코드가 반환됩니다. BadRequest 편의 메서드는 return new BadRequestResult();의 축약형으로 호출됩니다.

  • 제품이 만들어지면 CreatedAtAction 편의 메서드가 201 상태 코드를 생성합니다. 다음 코드는 CreatedAtAction을 호출하는 대안입니다.

    return new CreatedAtActionResult(nameof(CreateAsync), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    앞의 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

예를 들어 다음 모델은 요청이 NameDescription 속성을 포함해야 함을 나타냅니다. 요청에서 NameDescription을 제공하지 못하면 모델 유효성 검사에 실패합니다.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

[ApiController] 특성이 적용되면 모델 유효성 검사 오류로 인해 400 상태 코드가 생성됩니다. 자세한 정보는 자동 HTTP 400 응답을 참조하세요.

ActionResult 및 IActionResult

다음 섹션에서는 ActionResultIActionResult를 비교합니다.

ActionResult<T> 형식

ASP.NET Core에는 웹 API 컨트롤러 작업에 대한 ActionResult<T> 반환 형식이 포함되어 있습니다. 이를 통해 ActionResult에서 파생된 형식을 반환하거나 특정 형식을 반환할 수 있습니다. ActionResult<T>IActionResult 형식을 통해 다음과 같은 혜택을 제공합니다.

  • [ProducesResponseType] 특성의 Type 속성을 제외할 수 있습니다. 예를 들어 [ProducesResponseType(200, Type = typeof(Product))][ProducesResponseType(200)]으로 단순화됩니다. 작업의 예상 반환 형식은 ActionResult<T>T에서 유추됩니다.
  • 암시적 캐스트 연산자TActionResult 모두를 ActionResult<T>로 변환하도록 지원합니다. TObjectResult로 변환합니다. 즉, return new ObjectResult(T);return T;로 간소화됩니다.

C#은 인터페이스에서 암시적 캐스트 연산자를 지원하지 않습니다. 따라서 인터페이스를 구체적인 형식으로 전환하려면 ActionResult<T>를 사용해야 합니다. 예를 들어 다음 예제에서 IEnumerable을 사용하면 작동하지 않습니다.

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

이전 코드를 수정하는 한 가지 옵션으로 _repository.GetProducts().ToList();를 반환해 볼 수 있습니다.

대부분의 작업에는 특정 반환 형식이 있습니다. 작업을 실행하는 동안 예기치 않은 조건이 발생할 수 있습니다. 이 경우에 특정 형식이 반환되지 않습니다. 예를 들어 작업의 입력 매개 변수는 모델 유효성 검사에 실패할 수 있습니다. 이러한 경우에 일반적으로 특정 형식이 아닌 적절한 ActionResult 형식을 반환합니다.

동기화 작업

두 가지 가능한 반환 형식이 포함된 동기 작업을 사용합니다.

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

위 작업에서:

  • 제품이 데이터베이스에 존재하지 않는 경우 404 상태 코드가 반환됩니다.
  • 제품이 존재하는 경우 해당 Product 개체와 함께 200 상태 코드가 반환됩니다.

비동기 작업

두 가지 가능한 반환 형식이 포함된 비동기 작업을 사용합니다.

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(CreateAsync_ActionResultOfT), new { id = product.Id }, product);
}

위 작업에서:

  • 400 상태 코드(BadRequest)는 다음과 같은 경우 ASP.NET Core 런타임에서 반환됩니다.
    • [ApiController] 특성이 적용되었고 모델 유효성 검사에 실패합니다.
    • 제품 설명에 “XYZ 위젯”이 포함됩니다.
  • 제품이 만들어지면 CreatedAtAction 메서드가 201 상태 코드를 생성합니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

HttpResults 형식

MVC별 기본 제공 결과 형식(IActionResultActionResult<T>) 외에도 ASP.NET Core 최소 API와 Web API 모두에서 사용할 수 있는 HttpResults 형식을 포함합니다.

MVC별 결과 형식과는 다른 HttpResults:

  • IResult.ExecuteAsync 호출로 처리되는 결과 구현입니다.

  • 구성된 포맷터를 활용하지 않습니다. 구성된 포맷터를 활용하지 않는 것은 다음을 의미합니다.

    • Content negotiation과 같은 일부 기능은 사용할 수 없습니다.
    • 생성된 Content-TypeHttpResults 구현에 의해 결정됩니다.

HttpResults는 최소 API와 Web API 간에 코드를 공유할 때 유용할 수 있습니다.

IResult 형식

Microsoft.AspNetCore.Http.HttpResults 네임스페이스에는 IResult 인터페이스를 구현하는 클래스가 포함되어 있습니다. IResult 인터페이스는 HTTP 엔드포인트의 결과를 나타내는 계약을 정의합니다. 정적 Results 클래스는 다양한 형식의 응답을 나타내는 다양한 IResult 개체를 만드는 데 사용됩니다.

기본 제공 결과 테이블에는 일반적인 결과 도우미가 표시됩니다.

다음 코드를 생각해 봅시다.

[HttpGet("{id}")]
[ProducesResponseType<Product>(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

위 작업에서:

  • 제품이 데이터베이스에 존재하지 않는 경우 404 상태 코드가 반환됩니다.
  • Results.Ok<T>()에서 생성한 제품이 있을 때 해당 Product 개체와 함께 200 상태 코드가 반환됩니다.

다음 코드를 생각해 봅시다.

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType<Product>(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return Results.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

위 작업에서:

  • 다음과 같은 경우 400 상태 코드가 반환됩니다.
    • [ApiController] 특성이 적용되었고 모델 유효성 검사에 실패합니다.
    • 제품 설명에 “XYZ 위젯”이 포함됩니다.
  • 제품이 만들어지면 Results.Create 메서드가 201 상태 코드를 생성합니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

결과<TResult1, TResultN> 형식

정적 TypedResults 클래스는 IResult를 반환 형식으로 사용할 수 있는 구체적인 IResult 구현을 반환합니다. 구체적인 IResult 구현을 사용하면 IResult 형식에 비해 다음과 같은 이점이 제공됩니다.

  • 구현이 엔드포인트 메타데이터에 [ProducesResponseType]HttpResult 자동으로 기여하므로 모든 특성을 제외할 수 있습니다.

여러 IResult 반환 형식이 필요한 경우 IResult를 반환하는 것보다 Results<TResult1, TResultN>을 반환하는 것이 좋습니다. 제네릭 공용 구조체 형식은 엔드포인트 메타데이터를 자동으로 유지하므로 Results<TResult1, TResultN>을 반환하는 것이 좋습니다.

Results<TResult1, TResultN> 공용 구조체 암시적 캐스트 연산자를 구현하므로 컴파일러는 제네릭 인수에 지정된 형식을 공용 구조체 형식의 인스턴스로 자동 변환할 수 있습니다. 이렇게 하면 경로 처리기가 실제로 선언한 결과만 반환한다는 컴파일 시간 검사를 제공하는 이점이 추가됩니다. 제네릭 인수 중 하나로 선언되지 않은 형식을 반환하려고 시도하면 Results<> 컴파일 오류가 발생합니다.

다음 코드를 생각해 봅시다.

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

위 작업에서:

  • 제품이 데이터베이스에 존재하지 않는 경우 404 상태 코드가 반환됩니다.
  • TypedResults.Ok<T>에서 생성한 제품이 있을 때 해당 Product 개체와 함께 200 상태 코드가 반환됩니다.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(CreateAsync), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

위 작업에서:

  • 다음과 같은 경우 400 상태 코드가 반환됩니다.
    • [ApiController] 특성이 적용되었고 모델 유효성 검사에 실패합니다.
    • 제품 설명에 “XYZ 위젯”이 포함됩니다.
  • 제품이 만들어지면 TypedResults.Created 메서드가 201 상태 코드를 생성합니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

추가 리소스

샘플 코드 보기 및 다운로드(다운로드 방법)

ASP.NET Core에서는 웹 API 컨트롤러 작업 반환 형식에 다음 옵션을 제공합니다.

이 문서에서는 각 반환 형식을 사용하는 것이 가장 적합한 경우를 설명합니다.

특정 형식

가장 기본적인 작업은 기본 또는 복합 데이터 형식을 반환합니다(예: string 또는 사용자 지정 개체). 다음 작업을 사용하여 사용자 지정 Product 개체의 컬렉션을 반환합니다.

[HttpGet]
public Task<List<Product>> Get() =>
    _productContext.Products.OrderBy(p => p.Name).ToListAsync();

보호할 알려진 조건 없이 특정 형식을 반환하는 것으로 충분합니다. 매개 변수 제약 조건 유효성 검사가 필요하지 않으므로 위의 작업은 매개 변수를 허용하지 않습니다.

여러 반환 형식이 가능한 경우 기본 또는 복합적인 반환 형식과 ActionResult 반환 형식을 혼합하는 것이 일반적입니다. 이 종류의 동작을 수용하기 위해 IActionResult 또는 ActionResult<T> 중 하나가 필요합니다. 이 문서에서는 여러 반환 형식의 몇 가지 샘플을 제공합니다.

IEnumerable<T> 또는 IAsyncEnumerable<T> 반환

반환 IEnumerable<T> 또는 IAsyncEnumerable<T> 성능 고려 사항을 참조하세요.

ASP.NET Core는 응답에 쓰기 전에 IEnumerable<T>을 반환하는 작업의 결과를 버퍼링합니다. 비동기 반복을 보장하기 위해 작업 서명의 반환 형식을 IAsyncEnumerable<T>로 선언합니다. 궁극적으로 반복 모드는 반환되는 기본 구체적인 형식을 기반으로 하며 선택한 포맷터는 결과가 처리되는 방식에 영향을 줍니다.

  • System.Text.Json 포맷터를 사용하는 경우 MVC는 결과를 스트리밍하기 위해 추가된 System.Text.Json 지원을 사용합니다.
  • Newtonsoft.Json를 사용하거나 XML-based 포맷터와 함께 사용하면 결과가 버퍼링됩니다.

다음과 같은 판매 가격이 책정된 제품 레코드를 IEnumerable<Product>로 반환하는 작업을 살펴보겠습니다.

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _productContext.Products.OrderBy(p => p.Name).ToList();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

다음은 위 작업의 IAsyncEnumerable<Product>에 해당합니다.

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _productContext.Products.OrderBy(p => p.Name).AsAsyncEnumerable();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

IActionResult 형식

한 작업에 여러 ActionResult 반환 형식을 사용할 수 있는 경우 IActionResult 반환 형식이 적절합니다. ActionResult 형식은 다양한 HTTP 상태 코드를 나타냅니다. ActionResult에서 파생되는 비추상 클래스는 유효한 반환 형식입니다. 이 범주에서 일반적인 반환 형식은 BadRequestResult(400), NotFoundResult(404) 및 OkObjectResult(200)입니다. 또는 ControllerBase 클래스의 편의 메서드를 사용하여 작업에서 ActionResult 형식을 반환할 수 있습니다. 예를 들어 return BadRequest();return new BadRequestResult();의 약식 형태입니다.

이 작업 유형에는 여러 반환 형식 및 경로가 있기 때문에 [ProducesResponseType] 특성을 자유롭게 사용할 필요가 있습니다. 이 특성은 Swagger와 같은 도구에서 생성한 웹 API 도움말 페이지에 대한 설명이 포함된 응답 세부 정보를 생성합니다. [ProducesResponseType]은 작업에서 반환한 알려진 형식 및 HTTP 상태 코드을 나타냅니다.

동기화 작업

두 가지 가능한 반환 형식이 포함된 다음과 같은 동기 작업을 사용합니다.

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById_IActionResult(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : Ok(product);
}

위 작업에서:

  • id에서 표시하는 제품이 내부 데이터 저장소에 없는 경우 404 상태 코드가 반환됩니다. NotFound 편의 메서드는 return new NotFoundResult();의 축약형으로 호출됩니다.
  • 제품이 존재하는 경우 Product 개체와 함께 200 상태 코드가 반환됩니다. Ok 편의 메서드는 return new OkObjectResult(product);의 축약형으로 호출됩니다.

비동기 작업

두 가지 가능한 반환 형식이 포함된 다음과 같은 비동기 작업을 사용합니다.

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync_IActionResult(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(GetById_IActionResult), new { id = product.Id }, product);
}

위 작업에서:

  • 제품 설명에 "XYZ 위젯"이 포함되어 있는 경우 400 상태 코드가 반환됩니다. BadRequest 편의 메서드는 return new BadRequestResult();의 축약형으로 호출됩니다.

  • 제품이 만들어지면 CreatedAtAction 편의 메서드가 201 상태 코드를 생성합니다. 다음 코드는 CreatedAtAction을 호출하는 대안입니다.

    return new CreatedAtActionResult(nameof(GetById), 
                                    "Products", 
                                    new { id = product.Id }, 
                                    product);
    

    앞의 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

예를 들어 다음 모델은 요청이 NameDescription 속성을 포함해야 함을 나타냅니다. 요청에서 NameDescription을 제공하지 못하면 모델 유효성 검사에 실패합니다.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; } = string.Empty;

    [Required]
    public string Description { get; set; } = string.Empty;

    public bool IsOnSale { get; set; }
}

[ApiController] 특성이 적용되면 모델 유효성 검사 오류로 인해 400 상태 코드가 생성됩니다. 자세한 정보는 자동 HTTP 400 응답을 참조하세요.

ActionResult 및 IActionResult

다음 섹션에서는 ActionResultIActionResult를 비교합니다.

ActionResult<T> 형식

ASP.NET Core에는 웹 API 컨트롤러 작업에 대한 ActionResult<T> 반환 형식이 포함되어 있습니다. 이를 통해 ActionResult에서 파생된 형식을 반환하거나 특정 형식을 반환할 수 있습니다. ActionResult<T>IActionResult 형식을 통해 다음과 같은 혜택을 제공합니다.

  • [ProducesResponseType] 특성의 Type 속성을 제외할 수 있습니다. 예를 들어 [ProducesResponseType(200, Type = typeof(Product))][ProducesResponseType(200)]으로 단순화됩니다. 작업의 예상 반환 형식은 ActionResult<T>T에서 유추됩니다.
  • 암시적 캐스트 연산자TActionResult 모두를 ActionResult<T>로 변환하도록 지원합니다. TObjectResult로 변환합니다. 즉, return new ObjectResult(T);return T;로 간소화됩니다.

C#은 인터페이스에서 암시적 캐스트 연산자를 지원하지 않습니다. 따라서 인터페이스를 구체적인 형식으로 전환하려면 ActionResult<T>를 사용해야 합니다. 예를 들어 다음 예제에서 IEnumerable을 사용하면 작동하지 않습니다.

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

이전 코드를 수정하는 한 가지 옵션으로 _repository.GetProducts().ToList();를 반환해 볼 수 있습니다.

대부분의 작업에는 특정 반환 형식이 있습니다. 작업을 실행하는 동안 예기치 않은 조건이 발생할 수 있습니다. 이 경우에 특정 형식이 반환되지 않습니다. 예를 들어 작업의 입력 매개 변수는 모델 유효성 검사에 실패할 수 있습니다. 이러한 경우에 일반적으로 특정 형식이 아닌 적절한 ActionResult 형식을 반환합니다.

동기화 작업

두 가지 가능한 반환 형식이 포함된 동기 작업을 사용합니다.

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById_ActionResultOfT(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? NotFound() : product;
}

위 작업에서:

  • 제품이 데이터베이스에 존재하지 않는 경우 404 상태 코드가 반환됩니다.
  • 제품이 존재하는 경우 해당 Product 개체와 함께 200 상태 코드가 반환됩니다.

비동기 작업

두 가지 가능한 반환 형식이 포함된 비동기 작업을 사용합니다.

[HttpPost()]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync_ActionResultOfT(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    return CreatedAtAction(nameof(GetById_ActionResultOfT), new { id = product.Id }, product);
}

위 작업에서:

  • 400 상태 코드(BadRequest)는 다음과 같은 경우 ASP.NET Core 런타임에서 반환됩니다.
    • [ApiController] 특성이 적용되었고 모델 유효성 검사에 실패합니다.
    • 제품 설명에 “XYZ 위젯”이 포함됩니다.
  • 제품이 만들어지면 CreatedAtAction 메서드가 201 상태 코드를 생성합니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

HttpResults 형식

MVC별 기본 제공 결과 형식(IActionResultActionResult<T>) 외에도 ASP.NET Core 최소 API와 Web API 모두에서 사용할 수 있는 HttpResults 형식을 포함합니다.

MVC별 결과 형식과는 다른 HttpResults:

  • IResult.ExecuteAsync 호출로 처리되는 결과 구현입니다.

  • 구성된 포맷터를 활용하지 않습니다. 구성된 포맷터를 활용하지 않는 것은 다음을 의미합니다.

    • Content negotiation과 같은 일부 기능은 사용할 수 없습니다.
    • 생성된 Content-TypeHttpResults 구현에 의해 결정됩니다.

HttpResults는 최소 API와 Web API 간에 코드를 공유할 때 유용할 수 있습니다.

IResult 형식

Microsoft.AspNetCore.Http.HttpResults 네임스페이스에는 IResult 인터페이스를 구현하는 클래스가 포함되어 있습니다. IResult 인터페이스는 HTTP 엔드포인트의 결과를 나타내는 계약을 정의합니다. 정적 Results 클래스는 다양한 형식의 응답을 나타내는 다양한 IResult 개체를 만드는 데 사용됩니다.

기본 제공 결과 테이블에는 일반적인 결과 도우미가 표시됩니다.

다음 코드를 생각해 봅시다.

[HttpGet("{id}")]
[ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IResult GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? Results.NotFound() : Results.Ok(product);
}

위 작업에서:

  • 제품이 데이터베이스에 존재하지 않는 경우 404 상태 코드가 반환됩니다.
  • Results.Ok<T>()에서 생성한 제품이 있을 때 해당 Product 개체와 함께 200 상태 코드가 반환됩니다.

다음 코드를 생각해 봅시다.

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return Results.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return Results.Created(location, product);
}

위 작업에서:

  • 다음과 같은 경우 400 상태 코드가 반환됩니다.
    • [ApiController] 특성이 적용되었고 모델 유효성 검사에 실패합니다.
    • 제품 설명에 “XYZ 위젯”이 포함됩니다.
  • 제품이 만들어지면 Results.Create 메서드가 201 상태 코드를 생성합니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

결과<TResult1, TResultN> 형식

정적 TypedResults 클래스는 IResult를 반환 형식으로 사용할 수 있는 구체적인 IResult 구현을 반환합니다. 구체적인 IResult 구현을 사용하면 IResult 형식에 비해 다음과 같은 이점이 제공됩니다.

  • 구현이 엔드포인트 메타데이터에 [ProducesResponseType]HttpResult 자동으로 기여하므로 모든 특성을 제외할 수 있습니다.

여러 IResult 반환 형식이 필요한 경우 IResult를 반환하는 것보다 Results<TResult1, TResultN>을 반환하는 것이 좋습니다. 제네릭 공용 구조체 형식은 엔드포인트 메타데이터를 자동으로 유지하므로 Results<TResult1, TResultN>을 반환하는 것이 좋습니다.

Results<TResult1, TResultN> 공용 구조체 암시적 캐스트 연산자를 구현하므로 컴파일러는 제네릭 인수에 지정된 형식을 공용 구조체 형식의 인스턴스로 자동 변환할 수 있습니다. 이렇게 하면 경로 처리기가 실제로 선언한 결과만 반환한다는 컴파일 시간 검사를 제공하는 이점이 추가됩니다. 제네릭 인수 중 하나로 선언되지 않은 형식을 반환하려고 시도하면 Results<> 컴파일 오류가 발생합니다.

다음 코드를 생각해 봅시다.

[HttpGet("{id}")]
public Results<NotFound, Ok<Product>> GetById(int id)
{
    var product = _productContext.Products.Find(id);
    return product == null ? TypedResults.NotFound() : TypedResults.Ok(product);
}

위 작업에서:

  • 제품이 데이터베이스에 존재하지 않는 경우 404 상태 코드가 반환됩니다.
  • TypedResults.Ok<T>에서 생성한 제품이 있을 때 해당 Product 개체와 함께 200 상태 코드가 반환됩니다.
[HttpPost]
public async Task<Results<BadRequest, Created<Product>>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return TypedResults.BadRequest();
    }

    _productContext.Products.Add(product);
    await _productContext.SaveChangesAsync();

    var location = Url.Action(nameof(GetById), new { id = product.Id }) ?? $"/{product.Id}";
    return TypedResults.Created(location, product);
}

위 작업에서:

  • 다음과 같은 경우 400 상태 코드가 반환됩니다.
    • [ApiController] 특성이 적용되었고 모델 유효성 검사에 실패합니다.
    • 제품 설명에 “XYZ 위젯”이 포함됩니다.
  • 제품이 만들어지면 TypedResults.Create 메서드가 201 상태 코드를 생성합니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

추가 리소스

샘플 코드 보기 및 다운로드(다운로드 방법)

ASP.NET Core에서는 웹 API 컨트롤러 작업 반환 형식에 다음 옵션을 제공합니다.

이 문서에서는 각 반환 형식을 사용하는 것이 가장 적합한 경우를 설명합니다.

특정 형식

가장 간단한 작업은 기본 또는 복합 데이터 형식을 반환합니다(예: string 또는 사용자 지정 개체 형식). 다음 작업을 사용하여 사용자 지정 Product 개체의 컬렉션을 반환합니다.

[HttpGet]
public List<Product> Get() =>
    _repository.GetProducts();

작업 실행 중에 보호할 알려진 조건 없이 특정 형식을 반환하는 것으로 충분합니다. 매개 변수 제약 조건 유효성 검사가 필요하지 않으므로 위의 작업은 매개 변수를 허용하지 않습니다.

여러 반환 형식이 가능한 경우 기본 또는 복합적인 반환 형식과 ActionResult 반환 형식을 혼합하는 것이 일반적입니다. 이 종류의 동작을 수용하기 위해 IActionResult 또는 ActionResult<T> 중 하나가 필요합니다. 이 문서에서는 여러 반환 형식의 몇 가지 샘플을 제공합니다.

IEnumerable<T> 또는 IAsyncEnumerable<T> 반환

ASP.NET Core는 응답에 쓰기 전에 IEnumerable<T>을 반환하는 작업의 결과를 버퍼링합니다. 비동기 반복을 보장하기 위해 작업 서명의 반환 형식을 IAsyncEnumerable<T>로 선언합니다. 궁극적으로 반복 모드는 반환되는 기본 구체적 형식을 기반으로 합니다. MVC는 IAsyncEnumerable<T>를 구현하는 모든 구체적 형식을 자동으로 버퍼링합니다.

다음과 같은 판매 가격이 책정된 제품 레코드를 IEnumerable<Product>로 반환하는 작업을 살펴보겠습니다.

[HttpGet("syncsale")]
public IEnumerable<Product> GetOnSaleProducts()
{
    var products = _repository.GetProducts();

    foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

다음은 위 작업의 IAsyncEnumerable<Product>에 해당합니다.

[HttpGet("asyncsale")]
public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
{
    var products = _repository.GetProductsAsync();

    await foreach (var product in products)
    {
        if (product.IsOnSale)
        {
            yield return product;
        }
    }
}

IActionResult 형식

한 작업에 여러 ActionResult 반환 형식을 사용할 수 있는 경우 IActionResult 반환 형식이 적절합니다. ActionResult 형식은 다양한 HTTP 상태 코드를 나타냅니다. ActionResult에서 파생되는 비추상 클래스는 유효한 반환 형식입니다. 이 범주에서 일반적인 반환 형식은 BadRequestResult(400), NotFoundResult(404) 및 OkObjectResult(200)입니다. 또는 ControllerBase 클래스의 편의 메서드를 사용하여 작업에서 ActionResult 형식을 반환할 수 있습니다. 예를 들어 return BadRequest();return new BadRequestResult();의 약식 형태입니다.

이 작업 유형에는 여러 반환 형식 및 경로가 있기 때문에 [ProducesResponseType] 특성을 자유롭게 사용할 필요가 있습니다. 이 특성은 Swagger와 같은 도구에서 생성한 웹 API 도움말 페이지에 대한 설명이 포함된 응답 세부 정보를 생성합니다. [ProducesResponseType]은 작업에서 반환한 알려진 형식 및 HTTP 상태 코드을 나타냅니다.

동기화 작업

두 가지 가능한 반환 형식이 포함된 다음과 같은 동기 작업을 사용합니다.

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK, Type = typeof(Product))]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public IActionResult GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return Ok(product);
}

위 작업에서:

  • id에서 표시하는 제품이 내부 데이터 저장소에 없는 경우 404 상태 코드가 반환됩니다. NotFound 편의 메서드는 return new NotFoundResult();의 축약형으로 호출됩니다.
  • 제품이 존재하는 경우 Product 개체와 함께 200 상태 코드가 반환됩니다. Ok 편의 메서드는 return new OkObjectResult(product);의 축약형으로 호출됩니다.

비동기 작업

두 가지 가능한 반환 형식이 포함된 다음과 같은 비동기 작업을 사용합니다.

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

위 작업에서:

  • 제품 설명에 "XYZ 위젯"이 포함되어 있는 경우 400 상태 코드가 반환됩니다. BadRequest 편의 메서드는 return new BadRequestResult();의 축약형으로 호출됩니다.
  • 제품이 만들어지면 CreatedAtAction 편의 메서드가 201 상태 코드를 생성합니다. CreatedAtAction을 호출하는 대신 return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);를 호출할 수 있습니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

예를 들어 다음 모델은 요청이 NameDescription 속성을 포함해야 함을 나타냅니다. 요청에서 NameDescription을 제공하지 못하면 모델 유효성 검사에 실패합니다.

public class Product
{
    public int Id { get; set; }

    [Required]
    public string Name { get; set; }

    [Required]
    public string Description { get; set; }

    public bool IsOnSale { get; set; }
}

[ApiController] 특성이 적용되면 모델 유효성 검사 오류로 인해 400 상태 코드가 생성됩니다. 자세한 정보는 자동 HTTP 400 응답을 참조하세요.

ActionResult 및 IActionResult

다음 섹션에서는 ActionResultIActionResult를 비교합니다.

ActionResult<T> 형식

ASP.NET Core에는 웹 API 컨트롤러 작업에 대한 ActionResult<T> 반환 형식이 포함되어 있습니다. 이를 통해 ActionResult에서 파생된 형식을 반환하거나 특정 형식을 반환할 수 있습니다. ActionResult<T>IActionResult 형식을 통해 다음과 같은 혜택을 제공합니다.

  • [ProducesResponseType] 특성의 Type 속성을 제외할 수 있습니다. 예를 들어 [ProducesResponseType(200, Type = typeof(Product))][ProducesResponseType(200)]으로 단순화됩니다. 작업의 예상 반환 형식은 대신 ActionResult<T>T에서 유추됩니다.
  • 암시적 캐스트 연산자TActionResult 모두를 ActionResult<T>로 변환하도록 지원합니다. TObjectResult로 변환합니다. 즉, return new ObjectResult(T);return T;로 간소화됩니다.

C#은 인터페이스에서 암시적 캐스트 연산자를 지원하지 않습니다. 따라서 인터페이스를 구체적인 형식으로 전환하려면 ActionResult<T>를 사용해야 합니다. 예를 들어 다음 예제에서 IEnumerable을 사용하면 작동하지 않습니다.

[HttpGet]
public ActionResult<IEnumerable<Product>> Get() =>
    _repository.GetProducts();

이전 코드를 수정하는 한 가지 옵션으로 _repository.GetProducts().ToList();를 반환해 볼 수 있습니다.

대부분의 작업에는 특정 반환 형식이 있습니다. 작업을 실행하는 동안 예기치 않은 조건이 발생할 수 있습니다. 이 경우에 특정 형식이 반환되지 않습니다. 예를 들어 작업의 입력 매개 변수는 모델 유효성 검사에 실패할 수 있습니다. 이러한 경우에 일반적으로 특정 형식이 아닌 적절한 ActionResult 형식을 반환합니다.

동기화 작업

두 가지 가능한 반환 형식이 포함된 동기 작업을 사용합니다.

[HttpGet("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status404NotFound)]
public ActionResult<Product> GetById(int id)
{
    if (!_repository.TryGetProduct(id, out var product))
    {
        return NotFound();
    }

    return product;
}

위 작업에서:

  • 제품이 데이터베이스에 존재하지 않는 경우 404 상태 코드가 반환됩니다.
  • 제품이 존재하는 경우 해당 Product 개체와 함께 200 상태 코드가 반환됩니다.

비동기 작업

두 가지 가능한 반환 형식이 포함된 비동기 작업을 사용합니다.

[HttpPost]
[Consumes(MediaTypeNames.Application.Json)]
[ProducesResponseType(StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<ActionResult<Product>> CreateAsync(Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

    return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
}

위 작업에서:

  • 400 상태 코드(BadRequest)는 다음과 같은 경우 ASP.NET Core 런타임에서 반환됩니다.
    • [ApiController] 특성이 적용되었고 모델 유효성 검사에 실패합니다.
    • 제품 설명에 “XYZ 위젯”이 포함됩니다.
  • 제품이 만들어지면 CreatedAtAction 메서드가 201 상태 코드를 생성합니다. 이 코드 경로에서는 Product 개체가 응답 본문에 제공됩니다. 새로 만든 제품의 URL을 포함하는 Location 응답 헤더가 제공됩니다.

추가 리소스