Tipos de valor devuelto de acción del controlador de la API web de ASP.NET Core

Nota:

Esta no es la versión más reciente de este artículo. Para la versión actual, consulte la versión .NET 8 de este artículo.

Importante

Esta información hace referencia a un producto en versión preliminar, el cual puede sufrir importantes modificaciones antes de que se publique la versión comercial. Microsoft no proporciona ninguna garantía, expresa o implícita, con respecto a la información proporcionada aquí.

Para la versión actual, consulte la versión .NET 8 de este artículo.

Vea o descargue el código de ejemplo (cómo descargarlo)

ASP.NET Core proporciona las siguientes opciones para los tipos de valor devueltos de acción del controlador de API web:

En este artículo se explica cuándo resulta más adecuado usar cada tipo de valor devuelto.

Tipo específico

La acción más básica devuelve un tipo de dato primitivo o complejo, por ejemplo, string o un objeto personalizado. Consideremos la siguiente acción, que devuelve una colección de objetos Product personalizados:

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

Sin condiciones conocidas de las que haya que protegerse, bastaría con que se devolviera un tipo específico. La acción anterior no acepta parámetros, por lo que no se necesita ninguna validación de restricciones de parámetros.

Cuando son posibles varios tipos de valor devuelto, es habitual mezclar un tipo de valor devuelto ActionResult con el tipo de valor devuelto primitivo o complejo. Se necesitará IActionResult o ActionResult<T>> para dar cabida a este tipo de acción. En este artículo se proporcionan varios ejemplos de varios tipos de valor devuelto.

Devolver IEnumerable<T> o IAsyncEnumerable<T>

Consulte Devolver IEnumerable<T> o IAsyncEnumerable<T> para conocer las consideraciones sobre el rendimiento.

ASP.NET Core almacena en búfer el resultado de las acciones que devuelven IEnumerable<T> antes de escribirlas en la respuesta. Considere la posibilidad de declarar el tipo de valor devuelto de la signatura de la acción como IAsyncEnumerable<T> para garantizar la iteración asincrónica. En última instancia, el modo de iteración se basa en el tipo concreto subyacente que se devuelve y el formateador seleccionado afecta a cómo se procesa el resultado:

  • Al usar el formateador de System.Text.Json, MVC confía en el soporte que System.Text.Json agregó para transmitir el resultado.
  • Al usar Newtonsoft.Json o con formateadores de XML-based el resultado se almacena en la memoria intermedia.

Considere la siguiente acción, que devuelve los registros de producto con precio de venta 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;
        }
    }
}

El IAsyncEnumerable<Product> equivalente de la acción anterior es:

[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

El tipo de valor devuelto IActionResult resulta adecuado cuando existen varios tipos de valor devuelto ActionResult posibles en una acción. Los tipos ActionResult representan varios códigos de estado HTTP. Cualquier clase no abstracta derivada de ActionResult se considera un tipo de valor devuelto válido. Algunos tipos de valor devueltos comunes en esta categoría son BadRequestResult (400), NotFoundResult (404) y OkObjectResult (200). Como alternativa, se pueden usar métodos de conveniencia en la clase ControllerBase para devolver tipos ActionResult de una acción. Por ejemplo, return BadRequest(); es una forma abreviada de return new BadRequestResult();.

Dado que hay varios tipos de valor devuelto y rutas de acceso en este tipo de acción, es necesario el uso libre del atributo [ProducesResponseType]. Este atributo genera detalles de respuesta más pormenorizados relativos a las páginas de ayuda de API web generadas por herramientas como Swagger. [ProducesResponseType] indica los tipos conocidos y los códigos de estado HTTP que la acción va a devolver.

Acción sincrónica

Veamos la siguiente acción sincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto representado por id no existe en el almacén de datos subyacente. El método de conveniencia NotFound se invoca como una abreviatura para return new NotFoundResult();.
  • Se devuelve un código de estado 200 con el objeto Product cuando existe el producto. El método de conveniencia Ok se invoca como una abreviatura para return new OkObjectResult(product);.

Acción asincrónica

Veamos la siguiente acción asincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 400 cuando la descripción del producto contiene "XYZ Widget". El método de conveniencia BadRequest se invoca como una abreviatura para return new BadRequestResult();.

  • Al crear un producto, el método de conveniencia CreatedAtAction genera un código de estado 201. El código siguiente es una alternativa a llamar a CreatedAtAction:

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

    En la ruta de acceso de código anterior, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Por ejemplo, el siguiente modelo indica que las solicitudes deben incluir las propiedades Name y Description. Si no se proporcionan Name y Description en la solicitud, se producirá un error en la validación de los modelos.

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

Si se aplica el atributo [ApiController], los errores de validación de los modelos generarán un código de estado 400. Para obtener más información, consulte Respuestas HTTP 400 automáticas.

ActionResult frente a IActionResult

La sección siguiente compara ActionResult con IActionResult

ActionResult<T> tipo

ASP.NET Core incluye el tipo de valor devuelto ActionResult<T> para las acciones del controlador de la API web. Permite devolver un tipo derivado de ActionResult o devolver un tipo específico. ActionResult<T> reporta las siguientes ventajas con frente al tipo IActionResult:

  • La propiedad del atributo [ProducesResponseType]Type se puede excluir. Por ejemplo, [ProducesResponseType(200, Type = typeof(Product))] se simplifica a [ProducesResponseType(200)]. En su lugar, el tipo de valor devuelto esperado de la acción se infiere de T en ActionResult<T>.
  • Los operadores de conversión implícitos admiten la conversión tanto de T como de ActionResult en ActionResult<T>. T se convierte en ObjectResult, lo que significa que return new ObjectResult(T); se ha simplificado para return T;.

C# no admite operadores de conversión implícitos en las interfaces. Por consiguiente, la conversión de la interfaz a un tipo concreto es necesaria para usar ActionResult<T>. Por ejemplo, el uso de IEnumerable en el siguiente ejemplo no funciona:

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

Una opción para corregir el código anterior es devolver _repository.GetProducts().ToList();.

La mayoría de las acciones tiene un tipo de valor devuelto específico. Se pueden producir condiciones inesperadas que durante la ejecución de una acción, en cuyo caso no se devuelve el tipo específico. Por ejemplo, puede suceder que el parámetro de entrada de una acción genere un error de validación de modelos. En tal caso, lo normal es que se devuelva el tipo ActionResult correspondiente en lugar del tipo específico.

Acción sincrónica

Veamos una acción sincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto no existe en la base de datos.
  • Se devuelve un código de estado 200 con el objeto Product correspondiente cuando existe el producto.

Acción asincrónica

Veamos una acción asincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • El entorno de ejecución de ASP.NET Core devuelve un código de estado 400 (BadRequest) en los casos siguientes:
    • El atributo [ApiController] se ha aplicado y se produce un error de validación de los modelos.
    • La descripción del producto contiene "Widget XYZ".
  • Al crear un producto, el método CreatedAtAction genera un código de estado 201. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Tipo HttpResults

Además de los tipos de resultados incorporados específicos de MVC (IActionResult y ActionResult<T>), ASP.NET Core incluye los tipos HttpResults que pueden usarse tanto en API mínimas como en API Web.

A diferencia de los tipos de resultado específicos de MVC, HttpResults:

  • Es una implementación de resultados que se procesa mediante una llamada a IResult.ExecuteAsync.

  • No aprovecha los Formateadores configurados. No aprovechar los formateadores configurados significa que:

    • Algunas características como Content negotiation no están disponibles.
    • La implementación de HttpResults decide el Content-Type producido.

HttpResults puede ser útil al compartir código entre las API mínimas y la API web.

Tipo IResult

El espacio de nombres Microsoft.AspNetCore.Http.HttpResults contiene clases que implementan la interfaz IResult. La interfaz IResult define un contrato que representa el resultado de un punto de conexión HTTP. La clase estática Results se usa para crear distintos objetos IResult que representan diferentes tipos de respuestas.

La tabla Resultados integrados muestra los asistentes de resultados habituales.

Observe el código siguiente:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto no existe en la base de datos.
  • Se devuelve un código de estado 200 con el objeto Product correspondiente cuando el producto existe, generado por Results.Ok<T>().

Observe el código siguiente:

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

En la acción anterior:

  • Se devuelve un código de estado 400 cuando:
    • El atributo [ApiController] se ha aplicado y se produce un error de validación de los modelos.
    • La descripción del producto contiene "Widget XYZ".
  • Al crear un producto, el método Results.Create genera un código de estado 201. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Tipo Results<TResult1, TResultN>

La clase estática TypedResults devuelve la implementación concreta IResult que permite usar IResult como tipo de valor devuelto. El uso de la implementación concreta IResult ofrece la siguiente ventaja sobre el tipo IResult:

  • Todos los atributos [ProducesResponseType] pueden excluirse, ya que la implementación de HttpResult contribuye automáticamente a los metadatos del punto de conexión.

Cuando se necesitan varios tipos de valor devuelto IResult, se prefiere el tipo de valor devuelto Results<TResult1, TResultN> al tipo de valor devuelto IResult. Es preferible devolver Results<TResult1, TResultN> porque los tipos de unión genéricos conservan automáticamente los metadatos del punto de conexión.

Los tipos de unión Results<TResult1, TResultN> implementan operadores de conversión implícitos para que el compilador pueda convertir automáticamente los tipos especificados en los argumentos genéricos a una instancia del tipo de unión. Esto tiene la ventaja adicional de proporcionar la comprobación en tiempo de compilación de que un controlador de ruta solo devuelve realmente los resultados que declara. Si se intenta devolver un tipo que no se declara como uno de los argumentos genéricos de Results<>, se producirá un error de compilación.

Observe el código siguiente:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto no existe en la base de datos.
  • Se devuelve un código de estado 200 con el objeto Product correspondiente cuando el producto existe, generado 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);
}

En la acción anterior:

  • Se devuelve un código de estado 400 cuando:
    • El atributo [ApiController] se ha aplicado y se produce un error de validación de los modelos.
    • La descripción del producto contiene "Widget XYZ".
  • Al crear un producto, el método TypedResults.Created genera un código de estado 201. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Recursos adicionales

Vea o descargue el código de ejemplo (cómo descargarlo)

ASP.NET Core proporciona las siguientes opciones para los tipos de valor devueltos de acción del controlador de API web:

En este artículo se explica cuándo resulta más adecuado usar cada tipo de valor devuelto.

Tipo específico

La acción más básica devuelve un tipo de dato primitivo o complejo, por ejemplo, string o un objeto personalizado. Consideremos la siguiente acción, que devuelve una colección de objetos Product personalizados:

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

Sin condiciones conocidas de las que haya que protegerse, bastaría con que se devolviera un tipo específico. La acción anterior no acepta parámetros, por lo que no se necesita ninguna validación de restricciones de parámetros.

Cuando son posibles varios tipos de valor devuelto, es habitual mezclar un tipo de valor devuelto ActionResult con el tipo de valor devuelto primitivo o complejo. Se necesitará IActionResult o ActionResult<T>> para dar cabida a este tipo de acción. En este artículo se proporcionan varios ejemplos de varios tipos de valor devuelto.

Devolver IEnumerable<T> o IAsyncEnumerable<T>

Consulte Devolver IEnumerable<T> o IAsyncEnumerable<T> para conocer las consideraciones sobre el rendimiento.

ASP.NET Core almacena en búfer el resultado de las acciones que devuelven IEnumerable<T> antes de escribirlas en la respuesta. Considere la posibilidad de declarar el tipo de valor devuelto de la signatura de la acción como IAsyncEnumerable<T> para garantizar la iteración asincrónica. En última instancia, el modo de iteración se basa en el tipo concreto subyacente que se devuelve y el formateador seleccionado afecta a cómo se procesa el resultado:

  • Al usar el formateador de System.Text.Json, MVC confía en el soporte que System.Text.Json agregó para transmitir el resultado.
  • Al usar Newtonsoft.Json o con formateadores de XML-based el resultado se almacena en la memoria intermedia.

Considere la siguiente acción, que devuelve los registros de producto con precio de venta 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;
        }
    }
}

El IAsyncEnumerable<Product> equivalente de la acción anterior es:

[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

El tipo de valor devuelto IActionResult resulta adecuado cuando existen varios tipos de valor devuelto ActionResult posibles en una acción. Los tipos ActionResult representan varios códigos de estado HTTP. Cualquier clase no abstracta derivada de ActionResult se considera un tipo de valor devuelto válido. Algunos tipos de valor devueltos comunes en esta categoría son BadRequestResult (400), NotFoundResult (404) y OkObjectResult (200). Como alternativa, se pueden usar métodos de conveniencia en la clase ControllerBase para devolver tipos ActionResult de una acción. Por ejemplo, return BadRequest(); es una forma abreviada de return new BadRequestResult();.

Dado que hay varios tipos de valor devuelto y rutas de acceso en este tipo de acción, es necesario el uso libre del atributo [ProducesResponseType]. Este atributo genera detalles de respuesta más pormenorizados relativos a las páginas de ayuda de API web generadas por herramientas como Swagger. [ProducesResponseType] indica los tipos conocidos y los códigos de estado HTTP que la acción va a devolver.

Acción sincrónica

Veamos la siguiente acción sincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto representado por id no existe en el almacén de datos subyacente. El método de conveniencia NotFound se invoca como una abreviatura para return new NotFoundResult();.
  • Se devuelve un código de estado 200 con el objeto Product cuando existe el producto. El método de conveniencia Ok se invoca como una abreviatura para return new OkObjectResult(product);.

Acción asincrónica

Veamos la siguiente acción asincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 400 cuando la descripción del producto contiene "XYZ Widget". El método de conveniencia BadRequest se invoca como una abreviatura para return new BadRequestResult();.

  • Al crear un producto, el método de conveniencia CreatedAtAction genera un código de estado 201. El código siguiente es una alternativa a llamar a CreatedAtAction:

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

    En la ruta de acceso de código anterior, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Por ejemplo, el siguiente modelo indica que las solicitudes deben incluir las propiedades Name y Description. Si no se proporcionan Name y Description en la solicitud, se producirá un error en la validación de los modelos.

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

Si se aplica el atributo [ApiController], los errores de validación de los modelos generarán un código de estado 400. Para obtener más información, consulte Respuestas HTTP 400 automáticas.

ActionResult frente a IActionResult

La sección siguiente compara ActionResult con IActionResult

ActionResult<T> tipo

ASP.NET Core incluye el tipo de valor devuelto ActionResult<T> para las acciones del controlador de la API web. Permite devolver un tipo derivado de ActionResult o devolver un tipo específico. ActionResult<T> reporta las siguientes ventajas con frente al tipo IActionResult:

  • La propiedad del atributo [ProducesResponseType]Type se puede excluir. Por ejemplo, [ProducesResponseType(200, Type = typeof(Product))] se simplifica a [ProducesResponseType(200)]. En su lugar, el tipo de valor devuelto esperado de la acción se infiere de T en ActionResult<T>.
  • Los operadores de conversión implícitos admiten la conversión tanto de T como de ActionResult en ActionResult<T>. T se convierte en ObjectResult, lo que significa que return new ObjectResult(T); se ha simplificado para return T;.

C# no admite operadores de conversión implícitos en las interfaces. Por consiguiente, la conversión de la interfaz a un tipo concreto es necesaria para usar ActionResult<T>. Por ejemplo, el uso de IEnumerable en el siguiente ejemplo no funciona:

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

Una opción para corregir el código anterior es devolver _repository.GetProducts().ToList();.

La mayoría de las acciones tiene un tipo de valor devuelto específico. Se pueden producir condiciones inesperadas que durante la ejecución de una acción, en cuyo caso no se devuelve el tipo específico. Por ejemplo, puede suceder que el parámetro de entrada de una acción genere un error de validación de modelos. En tal caso, lo normal es que se devuelva el tipo ActionResult correspondiente en lugar del tipo específico.

Acción sincrónica

Veamos una acción sincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto no existe en la base de datos.
  • Se devuelve un código de estado 200 con el objeto Product correspondiente cuando existe el producto.

Acción asincrónica

Veamos una acción asincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • El entorno de ejecución de ASP.NET Core devuelve un código de estado 400 (BadRequest) en los casos siguientes:
    • El atributo [ApiController] se ha aplicado y se produce un error de validación de los modelos.
    • La descripción del producto contiene "Widget XYZ".
  • Al crear un producto, el método CreatedAtAction genera un código de estado 201. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Tipo HttpResults

Además de los tipos de resultados incorporados específicos de MVC (IActionResult y ActionResult<T>), ASP.NET Core incluye los tipos HttpResults que pueden usarse tanto en API mínimas como en API Web.

A diferencia de los tipos de resultado específicos de MVC, HttpResults:

  • Es una implementación de resultados que se procesa mediante una llamada a IResult.ExecuteAsync.

  • No aprovecha los Formateadores configurados. No aprovechar los formateadores configurados significa que:

    • Algunas características como Content negotiation no están disponibles.
    • La implementación de HttpResults decide el Content-Type producido.

HttpResults puede ser útil al compartir código entre las API mínimas y la API web.

Tipo IResult

El espacio de nombres Microsoft.AspNetCore.Http.HttpResults contiene clases que implementan la interfaz IResult. La interfaz IResult define un contrato que representa el resultado de un punto de conexión HTTP. La clase estática Results se usa para crear distintos objetos IResult que representan diferentes tipos de respuestas.

La tabla Resultados integrados muestra los asistentes de resultados habituales.

Observe el código siguiente:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto no existe en la base de datos.
  • Se devuelve un código de estado 200 con el objeto Product correspondiente cuando el producto existe, generado por Results.Ok<T>().

Observe el código siguiente:

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

En la acción anterior:

  • Se devuelve un código de estado 400 cuando:
    • El atributo [ApiController] se ha aplicado y se produce un error de validación de los modelos.
    • La descripción del producto contiene "Widget XYZ".
  • Al crear un producto, el método Results.Create genera un código de estado 201. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Tipo Results<TResult1, TResultN>

La clase estática TypedResults devuelve la implementación concreta IResult que permite usar IResult como tipo de valor devuelto. El uso de la implementación concreta IResult ofrece la siguiente ventaja sobre el tipo IResult:

  • Todos los atributos [ProducesResponseType] pueden excluirse, ya que la implementación de HttpResult contribuye automáticamente a los metadatos del punto de conexión.

Cuando se necesitan varios tipos de valor devuelto IResult, se prefiere el tipo de valor devuelto Results<TResult1, TResultN> al tipo de valor devuelto IResult. Es preferible devolver Results<TResult1, TResultN> porque los tipos de unión genéricos conservan automáticamente los metadatos del punto de conexión.

Los tipos de unión Results<TResult1, TResultN> implementan operadores de conversión implícitos para que el compilador pueda convertir automáticamente los tipos especificados en los argumentos genéricos a una instancia del tipo de unión. Esto tiene la ventaja adicional de proporcionar la comprobación en tiempo de compilación de que un controlador de ruta solo devuelve realmente los resultados que declara. Si se intenta devolver un tipo que no se declara como uno de los argumentos genéricos de Results<>, se producirá un error de compilación.

Observe el código siguiente:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto no existe en la base de datos.
  • Se devuelve un código de estado 200 con el objeto Product correspondiente cuando el producto existe, generado 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);
}

En la acción anterior:

  • Se devuelve un código de estado 400 cuando:
    • El atributo [ApiController] se ha aplicado y se produce un error de validación de los modelos.
    • La descripción del producto contiene "Widget XYZ".
  • Al crear un producto, el método TypedResults.Create genera un código de estado 201. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Recursos adicionales

Vea o descargue el código de ejemplo (cómo descargarlo)

ASP.NET Core ofrece las siguientes opciones relativas a los tipos de valor devuelto de acción del controlador de la API web:

En este documento se explica cuándo resulta más adecuado usar cada tipo de valor devuelto.

Tipo específico

La acción más sencilla devuelve un tipo de datos primitivo o complejo (por ejemplo, string o un tipo de objeto personalizado). Consideremos la siguiente acción, que devuelve una colección de objetos Product personalizados:

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

Sin condiciones conocidas de las que haya que protegerse durante la ejecución de la acción, bastaría con que se devolviera un tipo específico. La acción anterior no acepta parámetros, por lo que no se necesita ninguna validación de restricciones de parámetros.

Cuando son posibles varios tipos de valor devuelto, es habitual mezclar un tipo de valor devuelto ActionResult con el tipo de valor devuelto primitivo o complejo. Se necesitará IActionResult o ActionResult<T>> para dar cabida a este tipo de acción. En este documento se proporcionan varios ejemplos de varios tipos de valor devuelto.

Devolver IEnumerable<T> o IAsyncEnumerable<T>

ASP.NET Core almacena en búfer el resultado de las acciones que devuelven IEnumerable<T> antes de escribirlas en la respuesta. Considere la posibilidad de declarar el tipo de valor devuelto de la signatura de la acción como IAsyncEnumerable<T> para garantizar la iteración asincrónica. En última instancia, el modo de iteración se basa en el tipo concreto subyacente que se va a devolver. MVC almacena en búfer automáticamente cualquier tipo concreto que implemente IAsyncEnumerable<T>.

Considere la siguiente acción, que devuelve los registros de producto con precio de venta como IEnumerable<Product>:

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

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

El IAsyncEnumerable<Product> equivalente de la acción anterior es:

[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

El tipo de valor devuelto IActionResult resulta adecuado cuando existen varios tipos de valor devuelto ActionResult posibles en una acción. Los tipos ActionResult representan varios códigos de estado HTTP. Cualquier clase no abstracta derivada de ActionResult se considera un tipo de valor devuelto válido. Algunos tipos de valor devueltos comunes en esta categoría son BadRequestResult (400), NotFoundResult (404) y OkObjectResult (200). Como alternativa, se pueden usar métodos de conveniencia en la clase ControllerBase para devolver tipos ActionResult de una acción. Por ejemplo, return BadRequest(); es una forma abreviada de return new BadRequestResult();.

Dado que hay varios tipos de valor devuelto y rutas de acceso en este tipo de acción, es necesario el uso libre del atributo [ProducesResponseType]. Este atributo genera detalles de respuesta más pormenorizados relativos a las páginas de ayuda de API web generadas por herramientas como Swagger. [ProducesResponseType] indica los tipos conocidos y los códigos de estado HTTP que la acción va a devolver.

Acción sincrónica

Veamos la siguiente acción sincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto representado por id no existe en el almacén de datos subyacente. El método de conveniencia NotFound se invoca como una abreviatura para return new NotFoundResult();.
  • Se devuelve un código de estado 200 con el objeto Product cuando existe el producto. El método de conveniencia Ok se invoca como una abreviatura para return new OkObjectResult(product);.

Acción asincrónica

Veamos la siguiente acción asincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 400 cuando la descripción del producto contiene "XYZ Widget". El método de conveniencia BadRequest se invoca como una abreviatura para return new BadRequestResult();.
  • Al crear un producto, el método de conveniencia CreatedAtAction genera un código de estado 201. Una alternativa a la llamada a CreatedAtAction es return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Por ejemplo, el siguiente modelo indica que las solicitudes deben incluir las propiedades Name y Description. Si no se proporcionan Name y Description en la solicitud, se producirá un error en la validación de los modelos.

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

Si se aplica el atributo [ApiController], los errores de validación de los modelos generarán un código de estado 400. Para obtener más información, consulte Respuestas HTTP 400 automáticas.

ActionResult frente a IActionResult

La sección siguiente compara ActionResult con IActionResult

ActionResult<T> tipo

ASP.NET Core incluye el tipo de valor devuelto ActionResult<T> para las acciones del controlador de la API web. Permite devolver un tipo que se deriva de ActionResult o bien un tipo específico. ActionResult<T> reporta las siguientes ventajas con frente al tipo IActionResult:

  • La propiedad del atributo [ProducesResponseType]Type se puede excluir. Por ejemplo, [ProducesResponseType(200, Type = typeof(Product))] se simplifica a [ProducesResponseType(200)]. En su lugar, el tipo de valor devuelto esperado de la acción se infiere desde T en ActionResult<T>.
  • Los operadores de conversión implícitos admiten la conversión tanto de T como de ActionResult en ActionResult<T>. T se convierte en ObjectResult, lo que significa que return new ObjectResult(T); se ha simplificado para return T;.

C# no admite operadores de conversión implícitos en las interfaces. Por consiguiente, la conversión de la interfaz a un tipo concreto es necesaria para usar ActionResult<T>. Por ejemplo, el uso de IEnumerable en el siguiente ejemplo no funciona:

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

Una opción para corregir el código anterior es devolver _repository.GetProducts().ToList();.

La mayoría de las acciones tiene un tipo de valor devuelto específico. Se pueden producir condiciones inesperadas que durante la ejecución de una acción, en cuyo caso no se devuelve el tipo específico. Por ejemplo, puede suceder que el parámetro de entrada de una acción genere un error de validación de modelos. En tal caso, lo normal es que se devuelva el tipo ActionResult correspondiente en lugar del tipo específico.

Acción sincrónica

Veamos una acción sincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • Se devuelve un código de estado 404 cuando el producto no existe en la base de datos.
  • Se devuelve un código de estado 200 con el objeto Product correspondiente cuando existe el producto.

Acción asincrónica

Veamos una acción asincrónica, en la que pueden darse dos tipos de valor devuelto posibles:

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

En la acción anterior:

  • El entorno de ejecución de ASP.NET Core devuelve un código de estado 400 (BadRequest) en los casos siguientes:
    • El atributo [ApiController] se ha aplicado y se produce un error de validación de los modelos.
    • La descripción del producto contiene "Widget XYZ".
  • Al crear un producto, el método CreatedAtAction genera un código de estado 201. En esta ruta de acceso de código, el objeto Product se proporciona en el cuerpo de la respuesta. Se proporciona un encabezado de respuesta Location que contiene la dirección URL del producto recién creada.

Recursos adicionales