Tipos de retorno de ação do controlador na API Web do ASP.NET Core

Observação

Esta não é a versão mais recente deste artigo. Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Importante

Essas informações relacionam-se ao produto de pré-lançamento, que poderá ser substancialmente modificado antes do lançamento comercial. A Microsoft não oferece nenhuma garantia, explícita ou implícita, quanto às informações fornecidas aqui.

Para informações sobre a versão vigente, confira a Versão do .NET 8 deste artigo.

Exibir ou baixar código de exemplo (como baixar)

O ASP.NET Core oferece as seguintes opções para os tipos de retorno da ação do controlador da API Web:

Este artigo explica quando é mais adequado usar cada tipo de retorno.

Tipo específico

A ação mais básica retorna um tipo de dados complexo ou primitivo (por exemplo, string ou um objeto personalizado). Considere a seguinte ação, que retorna uma coleção de objetos Product personalizados:

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

Sem condições conhecidas para se proteger, retornar um tipo específico pode ser suficiente. A ação anterior não aceita parâmetros, assim, validação de restrições de parâmetro não é necessária.

Quando são possíveis vários tipos de retorno, é comum combinar um tipo de retorno ActionResult com o tipo de retorno primitivo ou complexo. IActionResult ou ActionResult<T> é necessário para acomodar esse tipo de ação. Vários exemplos de vários tipos de retorno são fornecidos neste artigo.

Retornar IEnumerable<T> ou IAsyncEnumerable<T>

Confira Retornar IEnumerable<T> ou IAsyncEnumerable<T> para obter considerações de desempenho.

O ASP.NET Core armazena em buffer o resultado de ações que retornam IEnumerable<T> antes de escrevê-las na resposta. Considere declarar o tipo de retorno da assinatura de ação como IAsyncEnumerable<T> para garantir a iteração assíncrona. Por fim, o modo de iteração é baseado no tipo concreto subjacente que está sendo retornado e o formatador selecionado afeta a forma como o resultado é processado:

  • Ao usar o formatador System.Text.Json, o MVC depende do suporte que o System.Text.Json adicionou para transmitir o resultado.
  • Ao usar Newtonsoft.Json ou com formatadores XML-based, o resultado é armazenado em buffer.

Considere a ação a seguir, que retorna registros de produtos com preço de venda como 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;
        }
    }
}

O IAsyncEnumerable<Product> equivalente à ação anterior é:

[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;
        }
    }
}

Tipo IActionResult

O tipo de retorno IActionResult é apropriado quando vários tipos de retorno ActionResult são possíveis em uma ação. Os tipos ActionResult representam vários códigos de status HTTP. Qualquer classe não abstrata derivada de ActionResult se qualifica como um tipo de retorno válido. Alguns tipos de retorno comuns nessa categoria são BadRequestResult (400), NotFoundResult (404) e OkObjectResult (200). Como alternativa, os métodos de conveniência na classe ControllerBase podem ser usados para retornar tipos de ActionResult de uma ação. Por exemplo, return BadRequest(); é uma forma abreviada de return new BadRequestResult();.

Como há vários tipos de retorno e caminhos de retorno nesse tipo de ação, é necessário usar o atributo [ProducesResponseType] de forma liberal. Esse atributo produz detalhes de resposta mais descritivos para páginas de ajuda da API Web geradas por ferramentas como Swagger. [ProducesResponseType] indica os tipos conhecidos e os códigos de status HTTP a serem retornados pela ação.

Ação síncrona

Considere a seguinte ação síncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto representado por id não existe no armazenamento de dados subjacente. O método de conveniência NotFound é invocado como abreviação para return new NotFoundResult();.
  • Um código de status 200 é retornado com o objeto Product quando o produto existe. O método de conveniência Ok é invocado como abreviação para return new OkObjectResult(product);.

Ação assíncrona

Considere a seguinte ação assíncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • Um código de status 400 é retornado quando a descrição do produto contém "XYZ Widget". O método de conveniência BadRequest é invocado como abreviação para return new BadRequestResult();.

  • O método de conveniência CreatedAtAction gera um código de status 201 quando um produto é criado. O código a seguir é uma alternativa para chamar CreatedAtAction:

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

    No caminho de código anterior, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Por exemplo, o modelo a seguir indica que as solicitações devem incluir as propriedades Name e Description. A falha em fornecer Name e Description na solicitação causa falha na validação do modelo.

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; }
}

Se o atributo [ApiController] for aplicado, os erros de validação do modelo resultarão em um código de status 400. Para obter mais informações, veja Respostas automáticas HTTP 400.

ActionResult vs IActionResult

A seção a seguir compara ActionResult ao IActionResult

Tipo ActionResult<T>

O ASP.NET Core inclui o tipo de retorno ActionResult<T> para ações do controlador da API Web. Permite retornar um tipo derivado de ActionResult ou retornar um tipo específico. ActionResult<T> oferece os seguintes benefícios em relação ao tipo IActionResult:

  • A propriedade [ProducesResponseType] do atributo Type pode ser excluída. Por exemplo, [ProducesResponseType(200, Type = typeof(Product))] é simplificado para [ProducesResponseType(200)]. O tipo de retorno esperado da ação é inferido do T em ActionResult<T>.
  • Operadores de conversão implícita são compatíveis com a conversão de T e ActionResult em ActionResult<T>. T converte em ObjectResult, o que significa que return new ObjectResult(T); é simplificado para return T;.

C# não dá suporte a operadores de conversão implícita em interfaces. Consequentemente, a conversão da interface para um tipo concreto é necessário para usar ActionResult<T>. Por exemplo, o uso de IEnumerable no exemplo a seguir não funciona:

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

Uma opção para corrigir o código anterior é retornar a _repository.GetProducts().ToList();.

A maioria das ações tem um tipo de retorno específico. Condições inesperadas podem ocorrer durante a execução da ação, caso em que o tipo específico não é retornado. Por exemplo, o parâmetro de entrada de uma ação pode falhar na validação do modelo. Nesse caso, é comum retornar o tipo ActionResult adequado, em vez do tipo específico.

Ação síncrona

Considere uma ação síncrona em que há dois tipos de retorno possíveis:

[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;
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto não existe no banco de dados.
  • Um código de status 200 é retornado com o objeto Product correspondente quando o produto existe.

Ação assíncrona

Considere uma ação assíncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • O runtime do ASP.NET Core retorna um código de status 400 (BadRequest) quando:
    • O atributo [ApiController] foi aplicado e o modelo de validação falhou.
    • A descrição do produto contém "Widget XYZ".
  • O método CreatedAtAction gera um código de status 201 quando um produto é criado. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Tipo HttpResults

Além dos tipos de resultados internos específicos do MVC (IActionResult e ActionResult<T>), o ASP.NET Core inclui os tipos HttpResults que podem ser usados em APIs mínimas e na API Web.

Diferente dos tipos de resultado específicos do MVC, o HttpResults:

  • São uma implementação de resultados que é processada por uma chamada para IResult.ExecuteAsync.

  • Não aproveita os Formatadores configurados. Não aproveitar os formatadores configurados significa:

    • Alguns recursos como Content negotiation não estão disponíveis.
    • O Content-Type produzido é decidido pela implementação HttpResults.

O HttpResults pode ser útil ao compartilhar código entre APIs mínimas e API Web.

Tipo IResult

O namespace Microsoft.AspNetCore.Http.HttpResults contém classes que implementam a interface IResult. A interface IResult define um contrato que representa o resultado de um ponto de extremidade HTTP. A classe estática Results é usada para criar objetos IResult variados que representam diferentes tipos de respostas.

A tabela resultados internos mostra os auxiliares de resultados comuns.

Considere o seguinte código:

[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);
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto não existe no banco de dados.
  • Um código de status 200 é retornado com o objeto Product correspondente quando o produto existe, gerado por Results.Ok<T>().

Considere o seguinte código:

[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);
}

Na ação anterior:

  • Um código de status 400 é retornado quando:
    • O atributo [ApiController] foi aplicado e o modelo de validação falhou.
    • A descrição do produto contém "Widget XYZ".
  • O método Results.Create gera um código de status 201 quando um produto é criado. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Resulta no tipo <TResult1, TResultN>

A classe estática TypedResults retorna a implementação concreta IResult que permite usar IResult como tipo de retorno. O uso da implementação concreta IResult oferece o seguinte benefício sobre o tipo IResult:

  • Todos os atributos [ProducesResponseType] podem ser excluídos, pois a implementação HttpResult contribui automaticamente para os metadados do ponto de extremidade.

Quando vários tipos de retorno IResult são necessários, o retorno Results<TResult1, TResultN> é preferencial em vez de retornar IResult. O retorno de Results<TResult1, TResultN> é preferencial porque os tipos de união genéricos retêm automaticamente os metadados do ponto de extremidade.

Os tipos de união Results<TResult1, TResultN> implementam operadores de conversão implícita para que o compilador pode converter automaticamente os tipos especificados nos argumentos genéricos em uma instância do tipo de união. Essa funcionalidade tem o benefício adicional de verificar durante o tempo de compilação se um manipulador de rotas retorna apenas os resultados que ele declara retornar. Tentar retornar um tipo que não é declarado como um dos argumentos genéricos para Results<> resulta em um erro de compilação.

Considere o seguinte código:

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

Na ação anterior:

  • Um código de status 404 é retornado quando o produto não existe no banco de dados.
  • Um código de status 200 é retornado com o objeto Product correspondente quando o produto existe, gerado por TypedResults.Ok<T>.
[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);
}

Na ação anterior:

  • Um código de status 400 é retornado quando:
    • O atributo [ApiController] foi aplicado e o modelo de validação falhou.
    • A descrição do produto contém "Widget XYZ".
  • O método TypedResults.Created gera um código de status 201 quando um produto é criado. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Recursos adicionais

Exibir ou baixar código de exemplo (como baixar)

O ASP.NET Core oferece as seguintes opções para os tipos de retorno da ação do controlador da API Web:

Este artigo explica quando é mais adequado usar cada tipo de retorno.

Tipo específico

A ação mais básica retorna um tipo de dados complexo ou primitivo (por exemplo, string ou um objeto personalizado). Considere a seguinte ação, que retorna uma coleção de objetos Product personalizados:

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

Sem condições conhecidas para se proteger, retornar um tipo específico pode ser suficiente. A ação anterior não aceita parâmetros, assim, validação de restrições de parâmetro não é necessária.

Quando são possíveis vários tipos de retorno, é comum combinar um tipo de retorno ActionResult com o tipo de retorno primitivo ou complexo. IActionResult ou ActionResult<T> é necessário para acomodar esse tipo de ação. Vários exemplos de vários tipos de retorno são fornecidos neste artigo.

Retornar IEnumerable<T> ou IAsyncEnumerable<T>

Confira Retornar IEnumerable<T> ou IAsyncEnumerable<T> para obter considerações de desempenho.

O ASP.NET Core armazena em buffer o resultado de ações que retornam IEnumerable<T> antes de escrevê-las na resposta. Considere declarar o tipo de retorno da assinatura de ação como IAsyncEnumerable<T> para garantir a iteração assíncrona. Por fim, o modo de iteração é baseado no tipo concreto subjacente que está sendo retornado e o formatador selecionado afeta a forma como o resultado é processado:

  • Ao usar o formatador System.Text.Json, o MVC depende do suporte que o System.Text.Json adicionou para transmitir o resultado.
  • Ao usar Newtonsoft.Json ou com formatadores XML-based, o resultado é armazenado em buffer.

Considere a ação a seguir, que retorna registros de produtos com preço de venda como 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;
        }
    }
}

O IAsyncEnumerable<Product> equivalente à ação anterior é:

[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;
        }
    }
}

Tipo IActionResult

O tipo de retorno IActionResult é apropriado quando vários tipos de retorno ActionResult são possíveis em uma ação. Os tipos ActionResult representam vários códigos de status HTTP. Qualquer classe não abstrata derivada de ActionResult se qualifica como um tipo de retorno válido. Alguns tipos de retorno comuns nessa categoria são BadRequestResult (400), NotFoundResult (404) e OkObjectResult (200). Como alternativa, os métodos de conveniência na classe ControllerBase podem ser usados para retornar tipos de ActionResult de uma ação. Por exemplo, return BadRequest(); é uma forma abreviada de return new BadRequestResult();.

Como há vários tipos de retorno e caminhos de retorno nesse tipo de ação, é necessário usar o atributo [ProducesResponseType] de forma liberal. Esse atributo produz detalhes de resposta mais descritivos para páginas de ajuda da API Web geradas por ferramentas como Swagger. [ProducesResponseType] indica os tipos conhecidos e os códigos de status HTTP a serem retornados pela ação.

Ação síncrona

Considere a seguinte ação síncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto representado por id não existe no armazenamento de dados subjacente. O método de conveniência NotFound é invocado como abreviação para return new NotFoundResult();.
  • Um código de status 200 é retornado com o objeto Product quando o produto existe. O método de conveniência Ok é invocado como abreviação para return new OkObjectResult(product);.

Ação assíncrona

Considere a seguinte ação assíncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • Um código de status 400 é retornado quando a descrição do produto contém "XYZ Widget". O método de conveniência BadRequest é invocado como abreviação para return new BadRequestResult();.

  • O método de conveniência CreatedAtAction gera um código de status 201 quando um produto é criado. O código a seguir é uma alternativa para chamar CreatedAtAction:

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

    No caminho de código anterior, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Por exemplo, o modelo a seguir indica que as solicitações devem incluir as propriedades Name e Description. A falha em fornecer Name e Description na solicitação causa falha na validação do modelo.

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; }
}

Se o atributo [ApiController] for aplicado, os erros de validação do modelo resultarão em um código de status 400. Para obter mais informações, veja Respostas automáticas HTTP 400.

ActionResult vs IActionResult

A seção a seguir compara ActionResult ao IActionResult

Tipo ActionResult<T>

O ASP.NET Core inclui o tipo de retorno ActionResult<T> para ações do controlador da API Web. Permite retornar um tipo derivado de ActionResult ou retornar um tipo específico. ActionResult<T> oferece os seguintes benefícios em relação ao tipo IActionResult:

  • A propriedade [ProducesResponseType] do atributo Type pode ser excluída. Por exemplo, [ProducesResponseType(200, Type = typeof(Product))] é simplificado para [ProducesResponseType(200)]. O tipo de retorno esperado da ação é inferido do T em ActionResult<T>.
  • Operadores de conversão implícita são compatíveis com a conversão de T e ActionResult em ActionResult<T>. T converte em ObjectResult, o que significa que return new ObjectResult(T); é simplificado para return T;.

C# não dá suporte a operadores de conversão implícita em interfaces. Consequentemente, a conversão da interface para um tipo concreto é necessário para usar ActionResult<T>. Por exemplo, o uso de IEnumerable no exemplo a seguir não funciona:

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

Uma opção para corrigir o código anterior é retornar a _repository.GetProducts().ToList();.

A maioria das ações tem um tipo de retorno específico. Condições inesperadas podem ocorrer durante a execução da ação, caso em que o tipo específico não é retornado. Por exemplo, o parâmetro de entrada de uma ação pode falhar na validação do modelo. Nesse caso, é comum retornar o tipo ActionResult adequado, em vez do tipo específico.

Ação síncrona

Considere uma ação síncrona em que há dois tipos de retorno possíveis:

[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;
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto não existe no banco de dados.
  • Um código de status 200 é retornado com o objeto Product correspondente quando o produto existe.

Ação assíncrona

Considere uma ação assíncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • O runtime do ASP.NET Core retorna um código de status 400 (BadRequest) quando:
    • O atributo [ApiController] foi aplicado e o modelo de validação falhou.
    • A descrição do produto contém "Widget XYZ".
  • O método CreatedAtAction gera um código de status 201 quando um produto é criado. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Tipo HttpResults

Além dos tipos de resultados internos específicos do MVC (IActionResult e ActionResult<T>), o ASP.NET Core inclui os tipos HttpResults que podem ser usados em APIs mínimas e na API Web.

Diferente dos tipos de resultado específicos do MVC, o HttpResults:

  • São uma implementação de resultados que é processada por uma chamada para IResult.ExecuteAsync.

  • Não aproveita os Formatadores configurados. Não aproveitar os formatadores configurados significa:

    • Alguns recursos como Content negotiation não estão disponíveis.
    • O Content-Type produzido é decidido pela implementação HttpResults.

O HttpResults pode ser útil ao compartilhar código entre APIs mínimas e API Web.

Tipo IResult

O namespace Microsoft.AspNetCore.Http.HttpResults contém classes que implementam a interface IResult. A interface IResult define um contrato que representa o resultado de um ponto de extremidade HTTP. A classe estática Results é usada para criar objetos IResult variados que representam diferentes tipos de respostas.

A tabela resultados internos mostra os auxiliares de resultados comuns.

Considere o seguinte código:

[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);
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto não existe no banco de dados.
  • Um código de status 200 é retornado com o objeto Product correspondente quando o produto existe, gerado por Results.Ok<T>().

Considere o seguinte código:

[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);
}

Na ação anterior:

  • Um código de status 400 é retornado quando:
    • O atributo [ApiController] foi aplicado e o modelo de validação falhou.
    • A descrição do produto contém "Widget XYZ".
  • O método Results.Create gera um código de status 201 quando um produto é criado. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Resulta no tipo <TResult1, TResultN>

A classe estática TypedResults retorna a implementação concreta IResult que permite usar IResult como tipo de retorno. O uso da implementação concreta IResult oferece o seguinte benefício sobre o tipo IResult:

  • Todos os atributos [ProducesResponseType] podem ser excluídos, pois a implementação HttpResult contribui automaticamente para os metadados do ponto de extremidade.

Quando vários tipos de retorno IResult são necessários, o retorno Results<TResult1, TResultN> é preferencial em vez de retornar IResult. O retorno de Results<TResult1, TResultN> é preferencial porque os tipos de união genéricos retêm automaticamente os metadados do ponto de extremidade.

Os tipos de união Results<TResult1, TResultN> implementam operadores de conversão implícita para que o compilador pode converter automaticamente os tipos especificados nos argumentos genéricos em uma instância do tipo de união. Essa funcionalidade tem o benefício adicional de verificar durante o tempo de compilação se um manipulador de rotas retorna apenas os resultados que ele declara retornar. Tentar retornar um tipo que não é declarado como um dos argumentos genéricos para Results<> resulta em um erro de compilação.

Considere o seguinte código:

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

Na ação anterior:

  • Um código de status 404 é retornado quando o produto não existe no banco de dados.
  • Um código de status 200 é retornado com o objeto Product correspondente quando o produto existe, gerado por TypedResults.Ok<T>.
[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);
}

Na ação anterior:

  • Um código de status 400 é retornado quando:
    • O atributo [ApiController] foi aplicado e o modelo de validação falhou.
    • A descrição do produto contém "Widget XYZ".
  • O método TypedResults.Create gera um código de status 201 quando um produto é criado. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Recursos adicionais

Exibir ou baixar código de exemplo (como baixar)

O ASP.NET Core oferece as seguintes opções para os tipos de retorno da ação do controlador da API web:

Este documento explica quando é mais adequado usar cada tipo de retorno.

Tipo específico

A ação mais simples retorna um tipo de dados complexo ou primitivo (por exemplo, string ou um tipo de objeto personalizado). Considere a seguinte ação, que retorna uma coleção de objetos Product personalizados:

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

Sem condições conhecidas contra as quais se proteger durante a execução da ação, retornar um tipo específico pode ser suficiente. A ação anterior não aceita parâmetros, assim, validação de restrições de parâmetro não é necessária.

Quando são possíveis vários tipos de retorno, é comum combinar um tipo de retorno ActionResult com o tipo de retorno primitivo ou complexo. IActionResult ou ActionResult<T> é necessário para acomodar esse tipo de ação. Vários exemplos de vários tipos de retorno são fornecidos neste documento.

Retornar IEnumerable<T> ou IAsyncEnumerable<T>

O ASP.NET Core armazena em buffer o resultado de ações que retornam IEnumerable<T> antes de escrevê-las na resposta. Considere declarar o tipo de retorno da assinatura de ação como IAsyncEnumerable<T> para garantir a iteração assíncrona. Por fim, o modo de iteração é baseado no tipo concreto subjacente que está sendo retornado. O MVC armazena automaticamente em buffer qualquer tipo concreto que implemente IAsyncEnumerable<T>.

Considere a ação a seguir, que retorna registros de produtos com preço de venda como IEnumerable<Product>:

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

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

O IAsyncEnumerable<Product> equivalente à ação anterior é:

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

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

Tipo IActionResult

O tipo de retorno IActionResult é apropriado quando vários tipos de retorno ActionResult são possíveis em uma ação. Os tipos ActionResult representam vários códigos de status HTTP. Qualquer classe não abstrata derivada de ActionResult se qualifica como um tipo de retorno válido. Alguns tipos de retorno comuns nessa categoria são BadRequestResult (400), NotFoundResult (404) e OkObjectResult (200). Como alternativa, os métodos de conveniência na classe ControllerBase podem ser usados para retornar tipos de ActionResult de uma ação. Por exemplo, return BadRequest(); é uma forma abreviada de return new BadRequestResult();.

Como há vários tipos de retorno e caminhos de retorno nesse tipo de ação, é necessário usar o atributo [ProducesResponseType] de forma liberal. Esse atributo produz detalhes de resposta mais descritivos para páginas de ajuda da API Web geradas por ferramentas como Swagger. [ProducesResponseType] indica os tipos conhecidos e os códigos de status HTTP a serem retornados pela ação.

Ação síncrona

Considere a seguinte ação síncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto representado por id não existe no armazenamento de dados subjacente. O método de conveniência NotFound é invocado como abreviação para return new NotFoundResult();.
  • Um código de status 200 é retornado com o objeto Product quando o produto existe. O método de conveniência Ok é invocado como abreviação para return new OkObjectResult(product);.

Ação assíncrona

Considere a seguinte ação assíncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • Um código de status 400 é retornado quando a descrição do produto contém "XYZ Widget". O método de conveniência BadRequest é invocado como abreviação para return new BadRequestResult();.
  • O método de conveniência CreatedAtAction gera um código de status 201 quando um produto é criado. Uma alternativa à chamada CreatedAtAction é return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Por exemplo, o modelo a seguir indica que as solicitações devem incluir as propriedades Name e Description. A falha em fornecer Name e Description na solicitação causa falha na validação do modelo.

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; }
}

Se o atributo [ApiController] for aplicado, os erros de validação do modelo resultarão em um código de status 400. Para obter mais informações, veja Respostas automáticas HTTP 400.

ActionResult vs IActionResult

A seção a seguir compara ActionResult ao IActionResult

Tipo ActionResult<T>

O ASP.NET Core inclui o tipo de retorno ActionResult<T> para ações do controlador da API Web. Permite que você retorne um tipo derivado de ActionResult ou retorne um tipo específico. ActionResult<T> oferece os seguintes benefícios em relação ao tipo IActionResult:

  • A propriedade [ProducesResponseType] do atributo Type pode ser excluída. Por exemplo, [ProducesResponseType(200, Type = typeof(Product))] é simplificado para [ProducesResponseType(200)]. O tipo de retorno esperado da ação é inferido do T em ActionResult<T>.
  • Operadores de conversão implícita são compatíveis com a conversão de T e ActionResult em ActionResult<T>. T converte em ObjectResult, o que significa que return new ObjectResult(T); é simplificado para return T;.

C# não dá suporte a operadores de conversão implícita em interfaces. Consequentemente, a conversão da interface para um tipo concreto é necessário para usar ActionResult<T>. Por exemplo, o uso de IEnumerable no exemplo a seguir não funciona:

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

Uma opção para corrigir o código anterior é retornar a _repository.GetProducts().ToList();.

A maioria das ações tem um tipo de retorno específico. Condições inesperadas podem ocorrer durante a execução da ação, caso em que o tipo específico não é retornado. Por exemplo, o parâmetro de entrada de uma ação pode falhar na validação do modelo. Nesse caso, é comum retornar o tipo ActionResult adequado, em vez do tipo específico.

Ação síncrona

Considere uma ação síncrona em que há dois tipos de retorno possíveis:

[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;
}

Na ação anterior:

  • Um código de status 404 é retornado quando o produto não existe no banco de dados.
  • Um código de status 200 é retornado com o objeto Product correspondente quando o produto existe.

Ação assíncrona

Considere uma ação assíncrona em que há dois tipos de retorno possíveis:

[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);
}

Na ação anterior:

  • O runtime do ASP.NET Core retorna um código de status 400 (BadRequest) quando:
    • O atributo [ApiController] foi aplicado e o modelo de validação falhou.
    • A descrição do produto contém "Widget XYZ".
  • O método CreatedAtAction gera um código de status 201 quando um produto é criado. Neste caminho de código, o objeto Product é fornecido no corpo da resposta. Um cabeçalho de resposta Location que contém a URL do produto recém-criado é fornecido.

Recursos adicionais