原文鏈接:傳送門。
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。
額外資源