钱行慕

导航

【译】ASP.NET Core Web API的返回类型

原文链接:传送门

ASP.NET Core为Web API控制器动作方法返回类型提供了如下几个选择:

这篇文章解释了什么时候最适合使用各个类型。

指定类型(Specific type)

最简单的API会返回原生的或者复杂的数据类型(比如,string 或者自定义对象类型)。考虑如下的Action方法,其返回了一个自定义的Product对象的集合。

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

在程序的执行过程中,如果没有可知的条件来破坏安全,便可以返回一个特定的类型。前面的Action方法没有接收任何参数,因此不需要任何参数约束验证。

当有可能具有多个返回类型时,通常的做法是将一个ActionResult 返回类型与原生的或者复杂的返回类型混合起来。IActionResult 或者 ActionResult<T> 都可以搭配这种类型的Action。

这篇文章也会提供多个返回类型的几个示例。

返回IEnumerable<T> 或者 IAsyncEnumerable<T>

在ASP.NET Core 2.2及之前的版本中,从一个Action中返回 IEnumerable<T> 会导致序列化器的同步集合迭代。其结果便会阻塞调用以及潜在的资源池匮乏。为了演示这个,假设我们将使用EF  Core来实现Web API的数据访问需求。如下的Action的返回类型在序列化时是同步枚举的。

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

在ASP.NET Core 2.2及之前的版本中,为了避免同步枚举以及在数据库中的阻塞等待,我们可以调用ToListAsync。

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

在ASP.NET Core 3.0及后续的版本中,可直接从Action 中返回IAsyncEnumerable<T>。其将会:

  • 不会导致同步迭代
  • 变得和返回IEnumerable<T> 一样高效。

ASP.NET Core 3.0及后续的版本在将如下结果提供给序列化器之前,会将其缓存起来:

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

可以考虑将Action方法签名的返回类型声明为 IAsyncEnumerable<T> 来保证同步迭代。最终,其迭代模式会基于返回的底层具体类型。MVC会自动缓存任何实现了IAsyncEnumerable<T>的具体类型。

考虑如下Action,其将sale-priced Product 记录作为一个IEnumerable<Product>来返回。

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

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

上述Action的 IAsyncEnumerable<Product> 等价版本如下:

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

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

在ASP.NET Core 3.0中,上述两个Action都是非阻塞的。

IActionResult返回类型

当在一个Action方法中有可能具有多个ActionResult返回类型时,我们便可以使用 IActionResult 返回类型。ActionResult 类型表示了各种各样的HTTP状态码。继承自ActionResult 的任何非抽象类型均描述了一个有效的返回类型。在这个类别中,一些通用的返回类型是: BadRequestResult (400),NotFoundResult (404)以及OkObjectResult (200)。同样的,我峨我们可以在Action中使用ControllerBase 类的一些便捷方法来返回ActionResult类型。举例来说,return BadRequest(),是 return new BadRequestResult()的一种简便写法。

因为在这种类型的Action中会有多个返回类型,我们便可以使用 [ProducesResponseType] 属性。这个属性为由工具(比如Swagger)生成的Web API帮助文档页产生更具描述性的响应详细。[ProducesResponseType] 标识了由Action返回的确知的类型以及状态码。

同步Action

考虑如下同步Action,其有两种可能的返回类型。

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

在上述Action中:

  • 当由id表示的Product并不存在于底层的数据存储中的时候,其便会返回一个404状态码。调用NotFound  方法来作为return new NotFoundResult();.的便捷方式。
  • 当Product确实存在时,便会返回一个200状态码以及Product对象。我们调用Ok 方法来作为return new OkObjectResult(product);的一种便捷写法。

异步Action

考虑如下异步Action,其具有两个可能的返回类型。

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

在上述Action中:

  • 当产品描述包含“XYZ Widget”时,便会返回一个400状态码。我们调用BadRequest  方法来作为 return new BadRequestResult();的一种便捷写法。
  • 当创建一个Product时,CreatedAtAction 方法便会产生一个201状态码。调用CreatedAtAction 的另一种替代方案是返回:return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);。在这个代码路径中,Product 对象提供在响应体中。而响应头Location则包含了新创建的Product对象的URL。

举个例子,如下的Model表明请求必须包含Name和Description属性。在请求中如果不能提供Name或者Descrption属性将会导致模型验证失败。

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

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

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

在ASP.NET Core 2.1及后续版本中,如果应用了[ApiController] 属性,模型验证错误会导致400错误状态码。更多信息,请查看 Automatic HTTP 400 responses

ActionResult<T>类型

ASP.NET Core 2.1 为Web API控制器Action方法引入了ActionResult<T> 返回类型。它使你能够返回一个继承自 ActionResult 的类型或者返回一个特定的类型。相比于IActionResult类型来说,ActionResult<T> 提供了如下优势:

  •  [ProducesResponseType] 属性的Type属性可以被排除。举个例子[ProducesResponseType(200, Type = typeof(Product))] 可以被简化为[ProducesResponseType(200)]。Action期望的类型将会从 ActionResult<T> 中的T中推断出来。
  • 隐式转化操作符(Implicit cast operators )支持T 和ActionResult 到ActionResult<T>的转化。T转化为ObjectResult,其意味着 return new ObjectResult(T);可以简化为return T;。

C#不支持接口上的隐式转化操作符。因此,为了使用ActionResult<T> 我们必须要将接口转化为具体的类型。举个例子 ,在如下的代码示例中使用 IEnumerable 是不会工作的。

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

修正上述方法的一个途径便是返回_repository.GetProducts().ToList();。

大部分Action都有特定的返回类型。在Action的执行中可能会发生非期望的情形,在这种情况下不会返回特定的类型。举个例子,一个Action的输入参数可能没有通过模型验证。在这种情况下,通常会返回一个合适的ActionResult类型而不是一个特定的类型。

同步Action

考虑一个同步的Action,其具有两种可能的返回类型。

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

在上述Action中:

  • 当Product不存在于数据库中时,便返回一个404状态码。
  • 当Product确实存在时候便会随着相关的Product对象返回一个200状态码。在ASP.NET Core之前的版本中, return product; 将会是return Ok(product);。

异步Action

考虑一个异步的Action,其具有两种可能的返回类型。

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

在上述Action中;

  • ASP.NET Core运行时会返回一个400状态码,当
    • 应用了[ApiController]特性并且模型验证失败。
    • Product描述信息包含 “XYZ Widget”。
  • 当创建一个product 时,CreatedAtAction 方法会生成一个201状态码。在这个代码路径中,Product 对象被提供在响应体中,而响应头 Location则包含了新创建的Product的URL。

额外资源

posted on 2020-11-11 15:40  钱行慕  阅读(1649)  评论(0编辑  收藏  举报