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

本文内容

作者:Scott Addie

查看或下载示例代码如何下载

ASP.NET Core 提供以下 Web API 控制器操作返回类型选项:

本文档说明每种返回类型的最佳适用情况。

特定类型Specific type

最简单的操作返回基元或复杂数据类型(如 string 或自定义对象类型)。请参考以下操作,该操作返回自定义 Product 对象的集合:

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

在执行操作期间无需防范已知条件,返回特定类型即可满足要求。上述操作不接受任何参数,因此不需要参数约束验证。

当在操作中需要考虑已知条件时,将引入多个返回路径。在此类情况下,通常会将 ActionResult 返回类型和基元或复杂返回类型混合。要支持此类操作,必须使用 IActionResultActionResult<T>

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

在 ASP.NET Core 2.2 及更低版本中,从操作返回 IEnumerable<T> 会导致序列化程序同步集合迭代。因此会阻止调用,并且可能会导致线程池资源不足。为了说明这一点,假设 Entity Framework (EF) Core 用于满足 Web API 的数据访问需求。序列化期间,将同步枚举以下操作的返回类型:

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

若要避免在 ASP.NET Core 2.2 及更低版本的数据库上同步枚举和阻止等待,请调用 ToListAsync

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

在 ASP.NET Core 3.0 及更高版本中,从操作返回 IAsyncEnumerable<T>

  • 不再会导致同步迭代。
  • 变成和返回 IEnumerable 一样高效。

    ASP.NET Core 3.0 和更高版本将缓冲以下操作的结果,然后将其提供给序列化程序:
  1. public IEnumerable<Product> GetOnSaleProducts() =>
  2. _context.Products.Where(p => p.IsOnSale);

考虑将操作签名的返回类型声明为 IAsyncEnumerable<T> 以保证异步迭代。最终,迭代模式基于要返回的基础具体类型。MVC 自动对实现 IAsyncEnumerable<T> 的任何具体类型进行缓冲。

请考虑以下操作,该操作将销售价格的产品记录返回为 IEnumerable<Product>

  1. [HttpGet("syncsale")]
  2. public IEnumerable<Product> GetOnSaleProducts()
  3. {
  4. var products = _repository.GetProducts();
  5. foreach (var product in products)
  6. {
  7. if (product.IsOnSale)
  8. {
  9. yield return product;
  10. }
  11. }
  12. }

上述操作的 IAsyncEnumerable<Product> 等效项为:

  1. [HttpGet("asyncsale")]
  2. public async IAsyncEnumerable<Product> GetOnSaleProductsAsync()
  3. {
  4. var products = _repository.GetProductsAsync();
  5. await foreach (var product in products)
  6. {
  7. if (product.IsOnSale)
  8. {
  9. yield return product;
  10. }
  11. }
  12. }

自 ASP.NET Core 3.0 起,前面两个操作均为非阻止性。

IActionResult 类型IActionResult type

当操作中可能有多个 IActionResult 返回类型时,适合使用 ActionResult 返回类型。ActionResult 类型表示多种 HTTP 状态代码。派生自 ActionResult 的任何非抽象类都限定为有效的返回类型。此类别中的某些常见返回类型为 BadRequestResult (400)、NotFoundResult (404) 和 OkObjectResult (200)。或者,可以使用 ControllerBase 类中的便利方法从操作返回 ActionResult 类型。例如,return BadRequest();return new BadRequestResult(); 的简写形式。

由于此操作类型中有多个返回类型和路径,因此必须自由使用 [ProducesResponseType] 特性。此特性可针对 Swagger 等工具生成的 Web API 帮助页生成更多描述性响应详细信息。[ProducesResponseType] 指示操作将返回的已知类型和 HTTP 状态代码。

同步操作Synchronous action

请参考以下同步操作,其中有两种可能的返回类型:

  1. [HttpGet("{id}")]
  2. [ProducesResponseType(StatusCodes.Status200OK)]
  3. [ProducesResponseType(StatusCodes.Status404NotFound)]
  4. public IActionResult GetById(int id)
  5. {
  6. if (!_repository.TryGetProduct(id, out var product))
  7. {
  8. return NotFound();
  9. }
  10. return Ok(product);
  11. }
  1. [HttpGet("{id}")]
  2. [ProducesResponseType(typeof(Product), StatusCodes.Status200OK)]
  3. [ProducesResponseType(StatusCodes.Status404NotFound)]
  4. public IActionResult GetById(int id)
  5. {
  6. if (!_repository.TryGetProduct(id, out var product))
  7. {
  8. return NotFound();
  9. }
  10. return Ok(product);
  11. }

在上述操作中:

  • id 代表的产品不在基础数据存储中时,则返回 404 状态代码。NotFound 便利方法作为 return new NotFoundResult(); 的简写调用。
  • 如果产品确实存在,则返回状态代码 200 及 Product 对象。Ok 便利方法作为 return new OkObjectResult(product); 的简写调用。

异步操作Asynchronous action

请参考以下异步操作,其中有两种可能的返回类型:

  1. [HttpPost]
  2. [Consumes(MediaTypeNames.Application.Json)]
  3. [ProducesResponseType(StatusCodes.Status201Created)]
  4. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  5. public async Task<IActionResult> CreateAsync(Product product)
  6. {
  7. if (product.Description.Contains("XYZ Widget"))
  8. {
  9. return BadRequest();
  10. }
  11. await _repository.AddProductAsync(product);
  12. return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
  13. }
  1. [HttpPost]
  2. [Consumes("application/json")]
  3. [ProducesResponseType(typeof(Product), StatusCodes.Status201Created)]
  4. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  5. public async Task<IActionResult> CreateAsync([FromBody] Product product)
  6. {
  7. if (product.Description.Contains("XYZ Widget"))
  8. {
  9. return BadRequest();
  10. }
  11. await _repository.AddProductAsync(product);
  12. return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
  13. }

在上述操作中:

  • 当产品说明包含“XYZ 小组件”时,返回 400 状态代码。BadRequest 便利方法作为 return new BadRequestResult(); 的简写调用。
  • 在创建产品后,CreatedAtAction 便利方法生成 201 状态代码。调用 CreatedAtAction 的替代方法是 return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);。在此代码路径中,将在响应正文中提供 Product 对象。提供了包含新建产品 URL 的 Location 响应标头。

例如,以下模型指明请求必须包含 NameDescription 属性。未在请求中提供 NameDescription 会导致模型验证失败。

  1. public class Product
  2. {
  3. public int Id { get; set; }
  4. [Required]
  5. public string Name { get; set; }
  6. [Required]
  7. public string Description { get; set; }
  8. }

如果应用的是 ASP.NET Core 2.1 或更高版本中的 [ApiController] 属性,模型验证错误会导致生成 400 状态代码。有关详细信息,请参阅自动 HTTP 400 响应

ActionResult<T> 类型ActionResult<T> type

ASP.NET Core 2.1 引入了面向 Web API 控制器操作的 ActionResult<T> 返回类型。它支持返回从 ActionResult 派生的类型或返回特定类型ActionResult<T> 通过 IActionResult 类型可提供以下优势:

  • 可排除 [ProducesResponseType] 特性的 Type 属性。例如,[ProducesResponseType(200, Type = typeof(Product))] 可简化为 [ProducesResponseType(200)]。此操作的预期返回类型改为根据 T 中的 ActionResult<T> 进行推断。
  • 隐式强制转换运算符支持将 TActionResult 均转换为 ActionResult<T>。将 T 转换为 ObjectResult,也就是将 return new ObjectResult(T); 简化为 return T;

C# 不支持对接口使用隐式强制转换运算符。因此,必须使用 ActionResult<T>,才能将接口转换为具体类型。例如,在下面的示例中,使用 IEnumerable 不起作用:

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

上面代码的一种修复方法是返回 _repository.GetProducts().ToList();

大多数操作具有特定返回类型。执行操作期间可能出现意外情况,不返回特定类型就是其中之一。例如,操作的输入参数可能无法通过模型验证。在此情况下,通常会返回相应的 ActionResult 类型,而不是特定类型。

同步操作Synchronous action

请参考以下同步操作,其中有两种可能的返回类型:

  1. [HttpGet("{id}")]
  2. [ProducesResponseType(StatusCodes.Status200OK)]
  3. [ProducesResponseType(StatusCodes.Status404NotFound)]
  4. public ActionResult<Product> GetById(int id)
  5. {
  6. if (!_repository.TryGetProduct(id, out var product))
  7. {
  8. return NotFound();
  9. }
  10. return product;
  11. }

在上述操作中:

  • 当产品不在数据库中时返回状态代码 404。
  • 如果产品确实存在,则返回状态代码 200 及相应的 Product 对象。ASP.NET Core 2.1 之前,return product; 行必须是 return Ok(product);

异步操作Asynchronous action

请参考以下异步操作,其中有两种可能的返回类型:

  1. [HttpPost]
  2. [Consumes(MediaTypeNames.Application.Json)]
  3. [ProducesResponseType(StatusCodes.Status201Created)]
  4. [ProducesResponseType(StatusCodes.Status400BadRequest)]
  5. public async Task<ActionResult<Product>> CreateAsync(Product product)
  6. {
  7. if (product.Description.Contains("XYZ Widget"))
  8. {
  9. return BadRequest();
  10. }
  11. await _repository.AddProductAsync(product);
  12. return CreatedAtAction(nameof(GetById), new { id = product.Id }, product);
  13. }

在上述操作中:

  • 在以下情况下,ASP.NET Core 运行时返回 400 状态代码 (BadRequest):
    • 已应用 [ApiController] 属性,且模型验证失败。
    • 产品说明包含“XYZ 小组件”。
  • 在创建产品后,CreatedAtAction 方法生成 201 状态代码。在此代码路径中,将在响应正文中提供 Product 对象。提供了包含新建产品 URL 的 Location 响应标头。

其他资源Additional resources