ASP.NET Core Web API 中的控制器動作傳回類型

注意

這不是這篇文章的最新版本。 如需目前版本,請參閱 本文的 ASP.NET Core 8.0 版本。

檢視或下載範例程式碼 \(英文\) (如何下載)

ASP.NET Core 針對 Web API 控制器動作傳回型別提供下列選項:

本文說明使用每個傳回型別的最適當時機。

特定類型

最基本的動作會傳回基本或複雜的資料型別,例如 string 或自訂物件。 請考慮下列動作,它會傳回自訂 Product 物件的集合:

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

若沒有要保護的已知條件,傳回特定類型即已足夠。 上述動作不接受任何參數,因此不需要驗證參數條件約束。

可能有多個傳回型別時,通常會混合 ActionResult 傳回型別與基本型別或複雜傳回型別。 IActionResultActionResult<T> 是容納此動作型別的必要項目。 本文提供多個傳回型別的數個範例。

傳回 IEnumerable<T> 或 IAsyncEnumerable<T>

如需效能考量,請參閱傳回 IEnumerable<T>IAsyncEnumerable<T>

ASP.NET Core 會將傳回 IEnumerable<T> 的動作結果緩衝處理,之後再寫入回應。 考慮將動作簽章的傳回型別宣告為 IAsyncEnumerable<T>,以確保非同步反覆運算。 最後,反覆運算模式是以要傳回的基礎具體型別為基礎,而選取的格式器會影響結果的處理方式:

  • 使用 System.Text.Json 格式器時,MVC 依賴於 System.Text.Json 新增至串流結果的支援。
  • 使用 Newtonsoft.Json 或搭配 XML-based 格式器時,會緩衝處理結果。

考慮下列動作,其會將銷售價格的產品記錄傳回為 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;
        }
    }
}

上述動作的 IAsyncEnumerable<Product> 同等項目為:

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

IActionResult 類型

當動作中可能有多個 ActionResult 傳回型別時,適合使用 IActionResult 傳回型別。 ActionResult 類型代表各種 HTTP 狀態碼。 衍生自 ActionResult 的任何非抽象類別都符合有效的傳回型別。 此類別中的一些常見傳回型別為 BadRequestResult (400)、NotFoundResult (404)和 OkObjectResult (200)。 或者,ControllerBase 類別中的便利方法可以用來從動作傳回 ActionResult 型別。 例如,return BadRequest();return new BadRequestResult(); 的速記形式。

因為此動作型別中有多個傳回型別和路徑,有必要自由使用 [ProducesResponseType] 屬性。 此屬性會產生由 Swagger 等工具所產生之 Web API 說明頁面的更具體描述回應詳細資料。 [ProducesResponseType] 表示已知類型和 HTTP 狀態碼要由動作傳回。

同步動作

請考慮下列有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 當基礎資料存放區中無 id 所代表的產品時,會傳回 404 狀態碼。 叫用 NotFound 便利方法做為 return new NotFoundResult(); 的速記。
  • 產品確實存在時,會隨著 Product 物件傳回 200 狀態碼。 叫用 Ok 便利方法做為 return new OkObjectResult(product); 的速記。

非同步動作

請考慮下列有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 當產品描述包含 "XYZ Widget",會傳回 400 狀態碼。 叫用 BadRequest 便利方法做為 return new BadRequestResult(); 的速記。

  • 建立產品時,CreatedAtAction 便利方法會產生 201 狀態碼。 下列程式碼是呼叫 CreatedAtAction 的替代方法:

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

    在上述程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

例如,下列模型指出要求必須包含 NameDescription 屬性。 若無法在要求中提供 NameDescription,就會導致模型驗證失敗。

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

如果套用 [ApiController] 屬性,模型驗證錯誤會產生 400 狀態碼。 如需詳細資訊,請參閱自動 HTTP 400 回應

ActionResult 與 IActionResult

下一節會將 ActionResultIActionResult 比較

ActionResult<T> 型別

ASP.NET Core 針對 Web API 控制器動作包含 ActionResult<T> 傳回型別。 它可讓您傳回衍生自 ActionResult 的型別或傳回特定型別ActionResult<T> 透過 IActionResult 類型提供下列優點:

  • 可以排除 [ProducesResponseType] 屬性的 Type 屬性。 例如,[ProducesResponseType(200, Type = typeof(Product))] 簡化為 [ProducesResponseType(200)]。 該動作的預期傳回型別會從 ActionResult<T> 中的 T 推斷。
  • 隱含轉型運算子支援 TActionResult 轉換成 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();

大部分的動作都有特定的傳回型別。 如果在動作執行期間發生非預期的狀況,就不會傳回特定的類型。 例如,動作的輸入參數可能無法驗證模型。 在此情況下,通常會傳回適當的 ActionResult 類型而不是特定的類型。

同步動作

請考慮有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 資料庫中不存在產品時,會傳回 404 狀態碼。
  • 產品確實存在時,會隨著對應的 Product 物件傳回 200 狀態碼。

非同步動作

請考慮有兩個可能傳回型別的非同步動作:

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

在上述動作中:

  • 在下列情況下,ASP.NET Core 執行階段會傳回 400 狀態碼 (BadRequest):
    • 已套用 [ApiController] 屬性,而模型驗證失敗。
    • 產品描述包含 "XYZ Widget"。
  • 建立產品時,CreatedAtAction 方法會產生 201 狀態碼。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

HttpResults 型別

除了 MVC 特定的內建結果型別 (IActionResultActionResult<T>),ASP.NET Core 還包含 HttpResults 型別,可用於基本 API 和 Web API。

與 MVC 特定結果型別不同,HttpResults

  • 這是由呼叫 IResult.ExecuteAsync 所處理的結果實作。

  • 不會利用設定的格式器。 未利用設定的格式器表示:

    • 某些功能如 Content negotiation 無法使用。
    • 產生的 Content-Type 是由 HttpResults 實作決定。

在基本 API 與 Web API 之間共用程式碼時,HttpResults 會很有用。

IResult 型別

Microsoft.AspNetCore.Http.HttpResults 命名空間包含實作 IResult 介面的類別。 IResult 介面會定義代表 HTTP 端點結果的合約。 靜態 Results 類別是用來建立代表不同回應型別的不同 IResult 物件。

內建結果資料表會顯示常見的結果協助程式。

請考慮下列程式碼:

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

在上述動作中:

  • 資料庫中不存在產品時,會傳回 404 狀態碼。
  • 產品確實存在時,會隨著對應的 Product 物件傳回 200 狀態碼,由 Results.Ok<T>() 產生。

請考慮下列程式碼:

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

在上述動作中:

  • 400 狀態碼會在以下情況下傳回:
    • 已套用 [ApiController] 屬性,而模型驗證失敗。
    • 產品描述包含 "XYZ Widget"。
  • 建立產品時,Results.Create 方法會產生 201 狀態碼。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

結果<TResult1、TResultN> 類型

靜態 TypedResults 類別會傳回允許使用 IResult 做為傳回型別的具體 IResult 實作。 具體 IResult 實作的使用方式相較於 IResult 型別提供下列優點:

需要多個 IResult 傳回型別時,傳回 Results<TResult1, TResultN> 優先於傳回 IResult。 因為泛型等位型別會自動保留端點中繼資料,因此會優先傳回 Results<TResult1, TResultN>

Results<TResult1, TResultN> 等位型別會實作隱含轉換運算子,使得編譯器可以自動將泛型引數中指定的型別轉換成等位型別的執行個體。 這會有的增加優點為,提供編譯時間檢查,確定路由處理常式實際上只會傳回其宣告會執行的結果。 嘗試傳回未宣告為其中一個泛型引數的型別至 Results<> 會產生編譯錯誤。

請考慮下列程式碼:

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

在上述動作中:

  • 資料庫中不存在產品時,會傳回 404 狀態碼。
  • 產品確實存在時,會隨著對應的 Product 物件傳回 200 狀態碼,由 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);
}

在上述動作中:

  • 400 狀態碼會在以下情況下傳回:
    • 已套用 [ApiController] 屬性,而模型驗證失敗。
    • 產品描述包含 "XYZ Widget"。
  • 建立產品時,TypedResults.Created 方法會產生 201 狀態碼。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

其他資源

檢視或下載範例程式碼 \(英文\) (如何下載)

ASP.NET Core 針對 Web API 控制器動作傳回型別提供下列選項:

本文說明使用每個傳回型別的最適當時機。

特定類型

最基本的動作會傳回基本或複雜的資料型別,例如 string 或自訂物件。 請考慮下列動作,它會傳回自訂 Product 物件的集合:

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

若沒有要保護的已知條件,傳回特定類型即已足夠。 上述動作不接受任何參數,因此不需要驗證參數條件約束。

可能有多個傳回型別時,通常會混合 ActionResult 傳回型別與基本型別或複雜傳回型別。 IActionResultActionResult<T> 是容納此動作型別的必要項目。 本文提供多個傳回型別的數個範例。

傳回 IEnumerable<T> 或 IAsyncEnumerable<T>

如需效能考量,請參閱傳回 IEnumerable<T>IAsyncEnumerable<T>

ASP.NET Core 會將傳回 IEnumerable<T> 的動作結果緩衝處理,之後再寫入回應。 考慮將動作簽章的傳回型別宣告為 IAsyncEnumerable<T>,以確保非同步反覆運算。 最後,反覆運算模式是以要傳回的基礎具體型別為基礎,而選取的格式器會影響結果的處理方式:

  • 使用 System.Text.Json 格式器時,MVC 依賴於 System.Text.Json 新增至串流結果的支援。
  • 使用 Newtonsoft.Json 或搭配 XML-based 格式器時,會緩衝處理結果。

考慮下列動作,其會將銷售價格的產品記錄傳回為 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;
        }
    }
}

上述動作的 IAsyncEnumerable<Product> 同等項目為:

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

IActionResult 類型

當動作中可能有多個 ActionResult 傳回型別時,適合使用 IActionResult 傳回型別。 ActionResult 類型代表各種 HTTP 狀態碼。 衍生自 ActionResult 的任何非抽象類別都符合有效的傳回型別。 此類別中的一些常見傳回型別為 BadRequestResult (400)、NotFoundResult (404)和 OkObjectResult (200)。 或者,ControllerBase 類別中的便利方法可以用來從動作傳回 ActionResult 型別。 例如,return BadRequest();return new BadRequestResult(); 的速記形式。

因為此動作型別中有多個傳回型別和路徑,有必要自由使用 [ProducesResponseType] 屬性。 此屬性會產生由 Swagger 等工具所產生之 Web API 說明頁面的更具體描述回應詳細資料。 [ProducesResponseType] 表示已知類型和 HTTP 狀態碼要由動作傳回。

同步動作

請考慮下列有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 當基礎資料存放區中無 id 所代表的產品時,會傳回 404 狀態碼。 叫用 NotFound 便利方法做為 return new NotFoundResult(); 的速記。
  • 產品確實存在時,會隨著 Product 物件傳回 200 狀態碼。 叫用 Ok 便利方法做為 return new OkObjectResult(product); 的速記。

非同步動作

請考慮下列有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 當產品描述包含 "XYZ Widget",會傳回 400 狀態碼。 叫用 BadRequest 便利方法做為 return new BadRequestResult(); 的速記。

  • 建立產品時,CreatedAtAction 便利方法會產生 201 狀態碼。 下列程式碼是呼叫 CreatedAtAction 的替代方法:

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

    在上述程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

例如,下列模型指出要求必須包含 NameDescription 屬性。 若無法在要求中提供 NameDescription,就會導致模型驗證失敗。

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

如果套用 [ApiController] 屬性,模型驗證錯誤會產生 400 狀態碼。 如需詳細資訊,請參閱自動 HTTP 400 回應

ActionResult 與 IActionResult

下一節會將 ActionResultIActionResult 比較

ActionResult<T> 型別

ASP.NET Core 針對 Web API 控制器動作包含 ActionResult<T> 傳回型別。 它可讓您傳回衍生自 ActionResult 的型別或傳回特定型別ActionResult<T> 透過 IActionResult 類型提供下列優點:

  • 可以排除 [ProducesResponseType] 屬性的 Type 屬性。 例如,[ProducesResponseType(200, Type = typeof(Product))] 簡化為 [ProducesResponseType(200)]。 該動作的預期傳回型別會從 ActionResult<T> 中的 T 推斷。
  • 隱含轉型運算子支援 TActionResult 轉換成 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();

大部分的動作都有特定的傳回型別。 如果在動作執行期間發生非預期的狀況,就不會傳回特定的類型。 例如,動作的輸入參數可能無法驗證模型。 在此情況下,通常會傳回適當的 ActionResult 類型而不是特定的類型。

同步動作

請考慮有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 資料庫中不存在產品時,會傳回 404 狀態碼。
  • 產品確實存在時,會隨著對應的 Product 物件傳回 200 狀態碼。

非同步動作

請考慮有兩個可能傳回型別的非同步動作:

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

在上述動作中:

  • 在下列情況下,ASP.NET Core 執行階段會傳回 400 狀態碼 (BadRequest):
    • 已套用 [ApiController] 屬性,而模型驗證失敗。
    • 產品描述包含 "XYZ Widget"。
  • 建立產品時,CreatedAtAction 方法會產生 201 狀態碼。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

HttpResults 型別

除了 MVC 特定的內建結果型別 (IActionResultActionResult<T>),ASP.NET Core 還包含 HttpResults 型別,可用於基本 API 和 Web API。

與 MVC 特定結果型別不同,HttpResults

  • 這是由呼叫 IResult.ExecuteAsync 所處理的結果實作。

  • 不會利用設定的格式器。 未利用設定的格式器表示:

    • 某些功能如 Content negotiation 無法使用。
    • 產生的 Content-Type 是由 HttpResults 實作決定。

在基本 API 與 Web API 之間共用程式碼時,HttpResults 會很有用。

IResult 型別

Microsoft.AspNetCore.Http.HttpResults 命名空間包含實作 IResult 介面的類別。 IResult 介面會定義代表 HTTP 端點結果的合約。 靜態 Results 類別是用來建立代表不同回應型別的不同 IResult 物件。

內建結果資料表會顯示常見的結果協助程式。

請考慮下列程式碼:

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

在上述動作中:

  • 資料庫中不存在產品時,會傳回 404 狀態碼。
  • 產品確實存在時,會隨著對應的 Product 物件傳回 200 狀態碼,由 Results.Ok<T>() 產生。

請考慮下列程式碼:

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

在上述動作中:

  • 400 狀態碼會在以下情況下傳回:
    • 已套用 [ApiController] 屬性,而模型驗證失敗。
    • 產品描述包含 "XYZ Widget"。
  • 建立產品時,Results.Create 方法會產生 201 狀態碼。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

結果<TResult1、TResultN> 類型

靜態 TypedResults 類別會傳回允許使用 IResult 做為傳回型別的具體 IResult 實作。 具體 IResult 實作的使用方式相較於 IResult 型別提供下列優點:

需要多個 IResult 傳回型別時,傳回 Results<TResult1, TResultN> 優先於傳回 IResult。 因為泛型等位型別會自動保留端點中繼資料,因此會優先傳回 Results<TResult1, TResultN>

Results<TResult1, TResultN> 等位型別會實作隱含轉換運算子,使得編譯器可以自動將泛型引數中指定的型別轉換成等位型別的執行個體。 這會有的增加優點為,提供編譯時間檢查,確定路由處理常式實際上只會傳回其宣告會執行的結果。 嘗試傳回未宣告為其中一個泛型引數的型別至 Results<> 會產生編譯錯誤。

請考慮下列程式碼:

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

在上述動作中:

  • 資料庫中不存在產品時,會傳回 404 狀態碼。
  • 產品確實存在時,會隨著對應的 Product 物件傳回 200 狀態碼,由 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);
}

在上述動作中:

  • 400 狀態碼會在以下情況下傳回:
    • 已套用 [ApiController] 屬性,而模型驗證失敗。
    • 產品描述包含 "XYZ Widget"。
  • 建立產品時,TypedResults.Create 方法會產生 201 狀態碼。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

其他資源

檢視或下載範例程式碼 \(英文\) (如何下載)

ASP.NET Core 提供下列 Web API 控制器動作傳回型別選項:

本文件說明使用每個傳回型別的最適時機。

特定類型

最簡單的動作傳回基本或複雜的資料類型 (例如,string 或自訂物件類型)。 請考慮下列動作,它會傳回自訂 Product 物件的集合:

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

無已知條件,無法確保在動作執行期間能滿足傳回特定類型。 上述動作不接受任何參數,因此不需要驗證參數條件約束。

可能有多個傳回型別時,通常會混合 ActionResult 傳回型別與基本型別或複雜傳回型別。 IActionResultActionResult<T> 是容納此動作型別的必要項目。 本文件提供多個傳回型別的數個範例。

傳回 IEnumerable<T> 或 IAsyncEnumerable<T>

ASP.NET Core 會將傳回 IEnumerable<T> 的動作結果緩衝處理,之後再寫入回應。 考慮將動作簽章的傳回型別宣告為 IAsyncEnumerable<T>,以確保非同步反覆運算。 最後,反覆運算模式是以要傳回的基礎具體型別為基礎。 MVC 會自動緩衝實作 IAsyncEnumerable<T> 的任何具體型別。

考慮下列動作,其會將銷售價格的產品記錄傳回為 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> 同等項目為:

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

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

IActionResult 類型

當動作中可能有多個 ActionResult 傳回型別時,適合使用 IActionResult 傳回型別。 ActionResult 類型代表各種 HTTP 狀態碼。 衍生自 ActionResult 的任何非抽象類別都符合有效的傳回型別。 此類別中的一些常見傳回型別為 BadRequestResult (400)、NotFoundResult (404)和 OkObjectResult (200)。 或者,ControllerBase 類別中的便利方法可以用來從動作傳回 ActionResult 型別。 例如,return BadRequest();return new BadRequestResult(); 的速記形式。

因為此動作型別中有多個傳回型別和路徑,有必要自由使用 [ProducesResponseType] 屬性。 此屬性會產生由 Swagger 等工具所產生之 Web API 說明頁面的更具體描述回應詳細資料。 [ProducesResponseType] 表示已知類型和 HTTP 狀態碼要由動作傳回。

同步動作

請考慮下列有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 當基礎資料存放區中無 id 所代表的產品時,會傳回 404 狀態碼。 叫用 NotFound 便利方法做為 return new NotFoundResult(); 的速記。
  • 產品確實存在時,會隨著 Product 物件傳回 200 狀態碼。 叫用 Ok 便利方法做為 return new OkObjectResult(product); 的速記。

非同步動作

請考慮下列有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 當產品描述包含 "XYZ Widget",會傳回 400 狀態碼。 叫用 BadRequest 便利方法做為 return new BadRequestResult(); 的速記。
  • 建立產品時,CreatedAtAction 便利方法會產生 201 狀態碼。 呼叫 CreatedAtAction 的替代方法是 return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product);。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

例如,下列模型指出要求必須包含 NameDescription 屬性。 若無法在要求中提供 NameDescription,就會導致模型驗證失敗。

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

如果套用 [ApiController] 屬性,模型驗證錯誤會產生 400 狀態碼。 如需詳細資訊,請參閱自動 HTTP 400 回應

ActionResult 與 IActionResult

下一節會將 ActionResultIActionResult 比較

ActionResult<T> 型別

ASP.NET Core 針對 Web API 控制器動作包含 ActionResult<T> 傳回型別。 它可讓您傳回衍生自 ActionResult 的型別或傳回特定型別ActionResult<T> 透過 IActionResult 類型提供下列優點:

  • 可以排除 [ProducesResponseType] 屬性的 Type 屬性。 例如,[ProducesResponseType(200, Type = typeof(Product))] 簡化為 [ProducesResponseType(200)]。 該動作的預期傳回型別會改為從 ActionResult<T> 中的 T 推斷。
  • 隱含轉型運算子支援 TActionResult 轉換成 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();

大部分的動作都有特定的傳回型別。 如果在動作執行期間發生非預期的狀況,就不會傳回特定的類型。 例如,動作的輸入參數可能無法驗證模型。 在此情況下,通常會傳回適當的 ActionResult 類型而不是特定的類型。

同步動作

請考慮有兩個可能傳回型別的同步動作:

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

在上述動作中:

  • 資料庫中不存在產品時,會傳回 404 狀態碼。
  • 產品確實存在時,會隨著對應的 Product 物件傳回 200 狀態碼。

非同步動作

請考慮有兩個可能傳回型別的非同步動作:

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

在上述動作中:

  • 在下列情況下,ASP.NET Core 執行階段會傳回 400 狀態碼 (BadRequest):
    • 已套用 [ApiController] 屬性,而模型驗證失敗。
    • 產品描述包含 "XYZ Widget"。
  • 建立產品時,CreatedAtAction 方法會產生 201 狀態碼。 在此程式碼路徑中,Product 物件會在回應本文中提供。 Location 回應標頭會包含所提供新建立產品的 URL。

其他資源