ASP.NET Core Web API 中控制器操作的返回类型Controller action return types in ASP.NET Core web API

作者:Scott AddieBy Scott Addie

查看或下载示例代码如何下载View or download sample code (how to download)

ASP.NET Core 提供以下 Web API 控制器操作返回类型选项:ASP.NET Core offers the following options for web API controller action return types:

本文档说明每种返回类型的最佳适用情况。This document explains when it's most appropriate to use each return type.

特定类型Specific type

最简单的操作返回基元或复杂数据类型(如 string 或自定义对象类型)。The simplest action returns a primitive or complex data type (for example, string or a custom object type). 请参考以下操作,该操作返回自定义 Product 对象的集合:Consider the following action, which returns a collection of custom Product objects:

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

在执行操作期间无需防范已知条件,返回特定类型即可满足要求。Without known conditions to safeguard against during action execution, returning a specific type could suffice. 上述操作不接受任何参数,因此不需要参数约束验证。The preceding action accepts no parameters, so parameter constraints validation isn't needed.

当有多个返回类型时,通常会将 ActionResult 返回类型与基元或复杂返回类型混合使用。When multiple return types are possible, it's common to mix an ActionResult return type with the primitive or complex return type. 需要IActionResultActionResult <T> 才能容纳此类操作。Either IActionResult or ActionResult<T> are necessary to accommodate this type of action. 本文档中提供了多个返回类型的多个示例。Several samples of multiple return types are provided in this document.

返回 IEnumerable <T> 或 IAsyncEnumerable<T>Return IEnumerable<T> or IAsyncEnumerable<T>

在 ASP.NET Core 2.2 及更低版本中,从操作返回 IEnumerable<T> 会导致序列化程序同步集合迭代。In ASP.NET Core 2.2 and earlier, returning IEnumerable<T> from an action results in synchronous collection iteration by the serializer. 因此会阻止调用,并且可能会导致线程池资源不足。The result is the blocking of calls and a potential for thread pool starvation. 为了说明这一点,假设 Entity Framework (EF) Core 用于满足 Web API 的数据访问需求。To illustrate, imagine that Entity Framework (EF) Core is being used for the web API's data access needs. 序列化期间,将同步枚举以下操作的返回类型:The following action's return type is synchronously enumerated during serialization:

public IEnumerable<Product> GetOnSaleProducts() =>
    _context.Products.Where(p => p.IsOnSale);

若要避免在 ASP.NET Core 2.2 及更低版本的数据库上同步枚举和阻止等待,请调用 ToListAsyncTo avoid synchronous enumeration and blocking waits on the database in ASP.NET Core 2.2 and earlier, invoke ToListAsync:

public async Task<IEnumerable<Product>> GetOnSaleProducts() =>
    await _context.Products.Where(p => p.IsOnSale).ToListAsync();

在 ASP.NET Core 3.0 及更高版本中,从操作返回 IAsyncEnumerable<T>In ASP.NET Core 3.0 and later, returning IAsyncEnumerable<T> from an action:

  • 不再会导致同步迭代。No longer results in synchronous iteration.
  • 变成和返回 IEnumerable<T> 一样高效。Becomes as efficient as returning IEnumerable<T>.

ASP.NET Core 3.0 和更高版本将缓冲以下操作的结果,然后将其提供给序列化程序:ASP.NET Core 3.0 and later buffers the result of the following action before providing it to the serializer:

public IEnumerable<Product> GetOnSaleProducts() =>
    _context.Products.Where(p => p.IsOnSale);

考虑将操作签名的返回类型声明为 IAsyncEnumerable<T> 以保证异步迭代。Consider declaring the action signature's return type as IAsyncEnumerable<T> to guarantee the asynchronous iteration. 最终,迭代模式基于要返回的基础具体类型。Ultimately, the iteration mode is based on the underlying concrete type being returned. MVC 自动对实现 IAsyncEnumerable<T> 的任何具体类型进行缓冲。MVC automatically buffers any concrete type that implements IAsyncEnumerable<T>.

请考虑以下操作,该操作将销售价格的产品记录返回为 IEnumerable<Product>Consider the following action, which returns sale-priced product records as 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> 等效项为:The IAsyncEnumerable<Product> equivalent of the preceding action is:

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

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

自 ASP.NET Core 3.0 起,前面两个操作均为非阻止性。Both of the preceding actions are non-blocking as of ASP.NET Core 3.0.

IActionResult 类型IActionResult type

当操作中可能有多个 ActionResult 返回类型时,适合使用 IActionResult 返回类型。The IActionResult return type is appropriate when multiple ActionResult return types are possible in an action. ActionResult 类型表示多种 HTTP 状态代码。The ActionResult types represent various HTTP status codes. 派生自 ActionResult 的任何非抽象类都限定为有效的返回类型。Any non-abstract class deriving from ActionResult qualifies as a valid return type. 此类别中的某些常见返回类型为 BadRequestResult (400)、NotFoundResult (404) 和 OkObjectResult (200)。Some common return types in this category are BadRequestResult (400), NotFoundResult (404), and OkObjectResult (200). 或者,可以使用 ControllerBase 类中的便利方法从操作返回 ActionResult 类型。Alternatively, convenience methods in the ControllerBase class can be used to return ActionResult types from an action. 例如,return BadRequest();return new BadRequestResult(); 的简写形式。For example, return BadRequest(); is a shorthand form of return new BadRequestResult();.

由于这种类型的操作具有多个返回类型和路径,因此需要大量使用 [ProducesResponseType] 特性。Because there are multiple return types and paths in this type of action, liberal use of the [ProducesResponseType] attribute is necessary. 此特性可针对 Swagger 等工具生成的 Web API 帮助页生成更多描述性响应详细信息。This attribute produces more descriptive response details for web API help pages generated by tools like Swagger. [ProducesResponseType] 指示操作将返回的已知类型和 HTTP 状态代码。[ProducesResponseType] indicates the known types and HTTP status codes to be returned by the action.

同步操作Synchronous action

请参考以下同步操作,其中有两种可能的返回类型:Consider the following synchronous action in which there are two possible return types:

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

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

    return Ok(product);
}

在上述操作中:In the preceding action:

  • id 代表的产品不在基础数据存储中时,则返回 404 状态代码。A 404 status code is returned when the product represented by id doesn't exist in the underlying data store. NotFound 便利方法作为 return new NotFoundResult(); 的简写调用。The NotFound convenience method is invoked as shorthand for return new NotFoundResult();.
  • 如果产品确实存在,则返回状态代码 200 及 Product 对象。A 200 status code is returned with the Product object when the product does exist. Ok 便利方法作为 return new OkObjectResult(product); 的简写调用。The Ok convenience method is invoked as shorthand for return new OkObjectResult(product);.

异步操作Asynchronous action

请参考以下异步操作,其中有两种可能的返回类型:Consider the following asynchronous action in which there are two possible return types:

[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);
}
[HttpPost]
[Consumes("application/json")]
[ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> CreateAsync([FromBody] Product product)
{
    if (product.Description.Contains("XYZ Widget"))
    {
        return BadRequest();
    }

    await _repository.AddProductAsync(product);

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

在上述操作中:In the preceding action:

  • 当产品说明包含“XYZ 小组件”时,返回 400 状态代码。A 400 status code is returned when the product description contains "XYZ Widget". BadRequest 便利方法作为 return new BadRequestResult(); 的简写调用。The BadRequest convenience method is invoked as shorthand for return new BadRequestResult();.
  • 在创建产品后,CreatedAtAction 便利方法生成 201 状态代码。A 201 status code is generated by the CreatedAtAction convenience method when a product is created. 调用 CreatedAtAction 的替代方法是 return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);An alternative to calling CreatedAtAction is return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);. 在此代码路径中,将在响应正文中提供 Product 对象。In this code path, the Product object is provided in the response body. 提供了包含新建产品 URL 的 Location 响应标头。A Location response header containing the newly created product's URL is provided.

例如,以下模型指明请求必须包含 NameDescription 属性。For example, the following model indicates that requests must include the Name and Description properties. 未在请求中提供 NameDescription 会导致模型验证失败。Failure to provide Name and Description in the request causes model validation to fail.

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

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

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

如果 [ApiController] 应用 ASP.NET Core 2.1 或更高版本中的属性,则模型验证错误将导致400状态代码。If the [ApiController] attribute in ASP.NET Core 2.1 or later is applied, model validation errors result in a 400 status code. 有关详细信息,请参阅自动 HTTP 400 响应For more information, see Automatic HTTP 400 responses.

ActionResult <T> 类型ActionResult<T> type

ASP.NET Core 2.1 引入了 web API 控制器操作的ActionResult <T> 返回类型。ASP.NET Core 2.1 introduced the ActionResult<T> return type for web API controller actions. 它支持返回从 ActionResult 派生的类型或返回特定类型It enables you to return a type deriving from ActionResult or return a specific type. ActionResult<T> 通过 IActionResult 类型可提供以下优势:ActionResult<T> offers the following benefits over the IActionResult type:

  • [ProducesResponseType] Type 可以排除特性的属性。The [ProducesResponseType] attribute's Type property can be excluded. 例如,[ProducesResponseType(200, Type = typeof(Product))] 简化为 [ProducesResponseType(200)]For example, [ProducesResponseType(200, Type = typeof(Product))] is simplified to [ProducesResponseType(200)]. 此操作的预期返回类型改为根据 ActionResult<T> 中的 T 进行推断。The action's expected return type is instead inferred from the T in ActionResult<T>.
  • 隐式强制转换运算符支持将 TActionResult 均转换为 ActionResult<T>Implicit cast operators support the conversion of both T and ActionResult to ActionResult<T>. T 转换为 ObjectResult,也就是将 return new ObjectResult(T); 简化为 return T;T converts to ObjectResult, which means return new ObjectResult(T); is simplified to return T;.

C# 不支持对接口使用隐式强制转换运算符。C# doesn't support implicit cast operators on interfaces. 因此,必须使用 ActionResult<T>,才能将接口转换为具体类型。Consequently, conversion of the interface to a concrete type is necessary to use ActionResult<T>. 例如,在下面的示例中,使用 IEnumerable 不起作用:For example, use of IEnumerable in the following example doesn't work:

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

上面代码的一种修复方法是返回 _repository.GetProducts().ToList();One option to fix the preceding code is to return _repository.GetProducts().ToList();.

大多数操作具有特定返回类型。Most actions have a specific return type. 执行操作期间可能出现意外情况,不返回特定类型就是其中之一。Unexpected conditions can occur during action execution, in which case the specific type isn't returned. 例如,操作的输入参数可能无法通过模型验证。For example, an action's input parameter may fail model validation. 在此情况下,通常会返回相应的 ActionResult 类型,而不是特定类型。In such a case, it's common to return the appropriate ActionResult type instead of the specific type.

同步操作Synchronous action

请参考以下同步操作,其中有两种可能的返回类型:Consider a synchronous action in which there are two possible return types:

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

在上述操作中:In the preceding action:

  • 当产品不在数据库中时返回状态代码 404。A 404 status code is returned when the product doesn't exist in the database.
  • 如果产品确实存在,则返回状态代码 200 及相应的 Product 对象。A 200 status code is returned with the corresponding Product object when the product does exist. ASP.NET Core 2.1 之前,return product; 行必须是 return Ok(product);Before ASP.NET Core 2.1, the return product; line had to be return Ok(product);.

异步操作Asynchronous action

请参考以下异步操作,其中有两种可能的返回类型:Consider an asynchronous action in which there are two possible return types:

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

在上述操作中:In the preceding action:

  • 在以下情况下,ASP.NET Core 运行时返回 400 状态代码 (BadRequest):A 400 status code (BadRequest) is returned by the ASP.NET Core runtime when:
    • [ApiController]已应用该属性,并且模型验证失败。The [ApiController] attribute has been applied and model validation fails.
    • 产品说明包含“XYZ 小组件”。The product description contains "XYZ Widget".
  • 在创建产品后,CreatedAtAction 方法生成 201 状态代码。A 201 status code is generated by the CreatedAtAction method when a product is created. 在此代码路径中,将在响应正文中提供 Product 对象。In this code path, the Product object is provided in the response body. 提供了包含新建产品 URL 的 Location 响应标头。A Location response header containing the newly created product's URL is provided.

其他资源Additional resources