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 の戻り値の型とプリミティブまたは複合の戻り値の型を混在させるのが一般的です。 この種類のアクションに対応するには、IActionResult または ActionResult<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 型

戻り値の型 IActionResult は、アクションの戻り値の型 ActionResult が複数考えられる場合に適しています。 ActionResult 型は、さまざまな HTTP 状態コードを表します。 ActionResult から派生したすべての非抽象クラスは、有効な戻り値の型として修飾されます。 このカテゴリの一般的な戻り値の型として、BadRequestResult (400)、NotFoundResult (404)、OkObjectResult (200) などがあります。 また、ControllerBase クラスの便利なメソッドを使用して、アクションから ActionResult 型を返すこともできます。 たとえば、return BadRequest(); は、return new BadRequestResult(); の短縮形です。

この種類のアクションには複数の戻り値の型とパスがあるため、[ProducesResponseType] 属性を自由に使える必要があります。 この属性は、Swagger などのツールで生成される Web API ヘルプ ページのよりわかりやすい応答の詳細を生成します。 [ProducesResponseType] は、アクションによって返される既知の型と HTTP 状態コードを示します。

同期アクション

2 つの戻り値の型が考えられる、次の同期アクションを考えてみましょう。

[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); の短縮形として呼び出されます。

非同期アクション

2 つの戻り値の型が考えられる、次の非同期アクションを考えてみましょう。

[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(); の短縮形として呼び出されます。

  • 201 状態コードは、製品の作成時に CreatedAtAction の便利なメソッドによって生成されます。 次のコードは CreatedAtAction の呼び出しの代替です:

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

    前のコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

たとえば、次のモデルでは、要求に Name プロパティと Description プロパティを含める必要があることを示しています。 要求で 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; }
}

ASP.NET Core 2.1 以降の [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> への変換をサポートしています。 TObjectResult に変換されます。つまり、return new ObjectResult(T);return T; に簡略化されます。

C# はインターフェイス上での暗黙的なキャスト演算子をサポートしていません。 そのため、ActionResult<T> を使用するには、インターフェイスを具象型に変換する必要があります。 たとえば、次の例における IEnumerable の使用は機能しません。

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

上記のコードを修正するための選択肢の 1 つは、_repository.GetProducts().ToList(); を返すことです。

ほとんどのアクションには、特定の戻り値の型があります。 アクションの実行中に予期しない状態が発生する場合、特定の型は返されません。 たとえば、アクションの入力パラメーターがモデルの検証に失敗する場合があります。 このような場合、通常は特定の型ではなく、適切な ActionResult 型が返されます。

同期アクション

2 つの戻り値の型が考えられる、同期アクションを考えてみましょう。

[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 状態コードが返されます。

非同期アクション

2 つの戻り値の型が考えられる、非同期アクションを考えてみましょう。

[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" が含まれている。
  • 201 状態コードは、製品の作成時に CreatedAtAction メソッドによって生成されます。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

HttpResults 型

MVC 固有の組み込み結果の型 (IActionResultActionResult<T>) に加えて、ASP.NET Core には、最小限の API と Web API の両方で使用できる HttpResults 型が含まれています。

MVC 固有の結果の型とは異なり、HttpResults:

  • IResult.ExecuteAsync の呼び出しによって処理される結果の実装です。

  • 構成されたフォーマッタは利用しません。 構成されたフォーマッタを利用しないということは、次のことを意味します:

    • Content negotiation のような一部の機能が利用できません。
    • 生成される Content-Type は、HttpResults の実装によって決定されます。

HttpResults は、最小限の API と Web API の間でコードを共有する場合に便利です。

IResult 型

Microsoft.AspNetCore.Http.HttpResults 名前空間には、IResult インターフェイスを実装するクラスが含まれています。 IResult インターフェイスでは、HTTP エンドポイントの結果を表すコントラクトが定義されています。 異なる型の応答を表すさまざまな IResult オブジェクトを作成するには、静的な Results クラスを使います。

組み込みの結果テーブルには、一般的な結果ヘルパーが表示されます。

次のコードがあるとします。

[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 オブジェクトと共に、Results.Ok<T>() によって生成された 200 状態コードが返されます。

次のコードがあるとします。

[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" が含まれている。
  • 201 状態コードは、製品の作成時に Results.Create メソッドによって生成されます。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

Results<TResult1, TResultN> 型

静的な TypedResults クラスは、戻り値の型として IResult を使用できるようにする具象 IResult 実装を返します。 具象 IResult 実装を使用すると、IResult 型に対して次の利点があります:

  • HttpResult 実装はエンドポイント メタデータに自動的に貢献するため、すべての [ProducesResponseType] 属性を除外できます。

複数の IResult 戻り値の型が必要な場合は、IResult を返すよりも Results<TResult1, TResultN> を返す方が適しています。 ジェネリック共用体型はエンドポイント メタデータを自動的に保持するため、Results<TResult1, TResultN> を返す方が優れています。

また、Results<TResult1, TResultN> 共用体型では暗黙的なキャスト演算子が実装されているため、コンパイラはジェネリック引数で指定されている型を共用体型のインスタンスに自動的に変換できます。 これにはさらに、ルート ハンドラーから宣言されている結果のみが実際に返されることがコンパイル時にチェックされるという利点があります。 ジェネリック引数の 1 つとして宣言されていない型を 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 オブジェクトと共に、Results.Ok<T> によって生成された 200 状態コードが返されます。
[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" が含まれている。
  • 201 状態コードは、製品の作成時に TypedResults.Created メソッドによって生成されます。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

その他の技術情報

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

:ASP.NET Core には、Web API コントローラー アクションの戻り値の種類に対して次のオプションが用意されています:

このドキュメントでは、各戻り値の型を使用するのが最適な場合について説明します。

特定の型

最もシンプルなアクションでは、プリミティブ データ型または複合データ型が返されます (string やカスタム オブジェクトの型など)。 カスタム Product オブジェクトのコレクションを返す次のアクションを考えてみましょう。

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

アクションの実行中に保護できる既知の条件がない場合は、特定の型を返すだけで十分です。 前のアクションでパラメーターを受け取っていないので、パラメーターの制約の検証は必要ありません。

複数の戻り値の型がある場合、ActionResult の戻り値の型とプリミティブまたは複合の戻り値の型を混在させるのが一般的です。 この種類のアクションに対応するには、IActionResult または ActionResult<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 型

戻り値の型 IActionResult は、アクションの戻り値の型 ActionResult が複数考えられる場合に適しています。 ActionResult 型は、さまざまな HTTP 状態コードを表します。 ActionResult から派生したすべての非抽象クラスは、有効な戻り値の型として修飾されます。 このカテゴリの一般的な戻り値の型として、BadRequestResult (400)、NotFoundResult (404)、OkObjectResult (200) などがあります。 また、ControllerBase クラスの便利なメソッドを使用して、アクションから ActionResult 型を返すこともできます。 たとえば、return BadRequest(); は、return new BadRequestResult(); の短縮形です。

この種類のアクションには複数の戻り値の型とパスがあるため、[ProducesResponseType] 属性を自由に使える必要があります。 この属性は、Swagger などのツールで生成される Web API ヘルプ ページのよりわかりやすい応答の詳細を生成します。 [ProducesResponseType] は、アクションによって返される既知の型と HTTP 状態コードを示します。

同期アクション

2 つの戻り値の型が考えられる、次の同期アクションを考えてみましょう。

[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); の短縮形として呼び出されます。

非同期アクション

2 つの戻り値の型が考えられる、次の非同期アクションを考えてみましょう。

[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(); の短縮形として呼び出されます。

  • 201 状態コードは、製品の作成時に CreatedAtAction の便利なメソッドによって生成されます。 次のコードは CreatedAtAction の呼び出しの代替です:

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

    前のコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

たとえば、次のモデルでは、要求に Name プロパティと Description プロパティを含める必要があることを示しています。 要求で 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; }
}

ASP.NET Core 2.1 以降の [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> への変換をサポートしています。 TObjectResult に変換されます。つまり、return new ObjectResult(T);return T; に簡略化されます。

C# はインターフェイス上での暗黙的なキャスト演算子をサポートしていません。 そのため、ActionResult<T> を使用するには、インターフェイスを具象型に変換する必要があります。 たとえば、次の例における IEnumerable の使用は機能しません。

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

上記のコードを修正するための選択肢の 1 つは、_repository.GetProducts().ToList(); を返すことです。

ほとんどのアクションには、特定の戻り値の型があります。 アクションの実行中に予期しない状態が発生する場合、特定の型は返されません。 たとえば、アクションの入力パラメーターがモデルの検証に失敗する場合があります。 このような場合、通常は特定の型ではなく、適切な ActionResult 型が返されます。

同期アクション

2 つの戻り値の型が考えられる、同期アクションを考えてみましょう。

[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 状態コードが返されます。

非同期アクション

2 つの戻り値の型が考えられる、非同期アクションを考えてみましょう。

[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" が含まれている。
  • 201 状態コードは、製品の作成時に CreatedAtAction メソッドによって生成されます。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

HttpResults 型

MVC 固有の組み込み結果の型 (IActionResultActionResult<T>) に加えて、ASP.NET Core には、最小限の API と Web API の両方で使用できる HttpResults 型が含まれています。

MVC 固有の結果の型とは異なり、HttpResults:

  • IResult.ExecuteAsync の呼び出しによって処理される結果の実装です。

  • 構成されたフォーマッタは利用しません。 構成されたフォーマッタを利用しないということは、次のことを意味します:

    • Content negotiation のような一部の機能が利用できません。
    • 生成される Content-Type は、HttpResults の実装によって決定されます。

HttpResults は、最小限の API と Web API の間でコードを共有する場合に便利です。

IResult 型

Microsoft.AspNetCore.Http.HttpResults 名前空間には、IResult インターフェイスを実装するクラスが含まれています。 IResult インターフェイスでは、HTTP エンドポイントの結果を表すコントラクトが定義されています。 異なる型の応答を表すさまざまな IResult オブジェクトを作成するには、静的な Results クラスを使います。

組み込みの結果テーブルには、一般的な結果ヘルパーが表示されます。

次のコードがあるとします。

[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 オブジェクトと共に、Results.Ok<T>() によって生成された 200 状態コードが返されます。

次のコードがあるとします。

[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" が含まれている。
  • 201 状態コードは、製品の作成時に Results.Create メソッドによって生成されます。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

Results<TResult1, TResultN> 型

静的な TypedResults クラスは、戻り値の型として IResult を使用できるようにする具象 IResult 実装を返します。 具象 IResult 実装を使用すると、IResult 型に対して次の利点があります:

  • HttpResult 実装はエンドポイント メタデータに自動的に貢献するため、すべての [ProducesResponseType] 属性を除外できます。

複数の IResult 戻り値の型が必要な場合は、IResult を返すよりも Results<TResult1, TResultN> を返す方が適しています。 ジェネリック共用体型はエンドポイント メタデータを自動的に保持するため、Results<TResult1, TResultN> を返す方が優れています。

また、Results<TResult1, TResultN> 共用体型では暗黙的なキャスト演算子が実装されているため、コンパイラはジェネリック引数で指定されている型を共用体型のインスタンスに自動的に変換できます。 これにはさらに、ルート ハンドラーから宣言されている結果のみが実際に返されることがコンパイル時にチェックされるという利点があります。 ジェネリック引数の 1 つとして宣言されていない型を 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 オブジェクトと共に、Results.Ok<T> によって生成された 200 状態コードが返されます。
[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" が含まれている。
  • 201 状態コードは、製品の作成時に TypedResults.Create メソッドによって生成されます。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

その他の技術情報

サンプル コードを表示またはダウンロードします (ダウンロード方法)。

ASP.NET Core では、Web API コントローラー アクションの戻り値の型に次のオプションを提供しています。

このドキュメントでは、各戻り値の型を使用するのが最適な場合について説明します。

特定の型

最もシンプルなアクションでは、プリミティブ データ型または複合データ型が返されます (string やカスタム オブジェクトの型など)。 カスタム Product オブジェクトのコレクションを返す次のアクションを考えてみましょう。

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

アクションの実行中に保護できる既知の条件がない場合は、特定の型を返すだけで十分です。 前のアクションでパラメーターを受け取っていないので、パラメーターの制約の検証は必要ありません。

複数の戻り値の型がある場合、ActionResult の戻り値の型とプリミティブまたは複合の戻り値の型を混在させるのが一般的です。 この種類のアクションに対応するには、IActionResult または ActionResult<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 型

戻り値の型 IActionResult は、アクションの戻り値の型 ActionResult が複数考えられる場合に適しています。 ActionResult 型は、さまざまな HTTP 状態コードを表します。 ActionResult から派生したすべての非抽象クラスは、有効な戻り値の型として修飾されます。 このカテゴリの一般的な戻り値の型として、BadRequestResult (400)、NotFoundResult (404)、OkObjectResult (200) などがあります。 また、ControllerBase クラスの便利なメソッドを使用して、アクションから ActionResult 型を返すこともできます。 たとえば、return BadRequest(); は、return new BadRequestResult(); の短縮形です。

この種類のアクションには複数の戻り値の型とパスがあるため、[ProducesResponseType] 属性を自由に使える必要があります。 この属性は、Swagger などのツールで生成される Web API ヘルプ ページのよりわかりやすい応答の詳細を生成します。 [ProducesResponseType] は、アクションによって返される既知の型と HTTP 状態コードを示します。

同期アクション

2 つの戻り値の型が考えられる、次の同期アクションを考えてみましょう。

[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); の短縮形として呼び出されます。

非同期アクション

2 つの戻り値の型が考えられる、次の非同期アクションを考えてみましょう。

[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(); の短縮形として呼び出されます。
  • 201 状態コードは、製品の作成時に CreatedAtAction の便利なメソッドによって生成されます。 CreatedAtAction を呼び出す代替の方法として return new CreatedAtActionResult(nameof(GetById), "Products", new { id = product.Id }, product); があります。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

たとえば、次のモデルでは、要求に Name プロパティと Description プロパティを含める必要があることを示しています。 要求で 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; }
}

ASP.NET Core 2.1 以降の [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> への変換をサポートしています。 TObjectResult に変換されます。つまり、return new ObjectResult(T);return T; に簡略化されます。

C# はインターフェイス上での暗黙的なキャスト演算子をサポートしていません。 そのため、ActionResult<T> を使用するには、インターフェイスを具象型に変換する必要があります。 たとえば、次の例における IEnumerable の使用は機能しません。

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

上記のコードを修正するための選択肢の 1 つは、_repository.GetProducts().ToList(); を返すことです。

ほとんどのアクションには、特定の戻り値の型があります。 アクションの実行中に予期しない状態が発生する場合、特定の型は返されません。 たとえば、アクションの入力パラメーターがモデルの検証に失敗する場合があります。 このような場合、通常は特定の型ではなく、適切な ActionResult 型が返されます。

同期アクション

2 つの戻り値の型が考えられる、同期アクションを考えてみましょう。

[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 状態コードが返されます。

非同期アクション

2 つの戻り値の型が考えられる、非同期アクションを考えてみましょう。

[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" が含まれている。
  • 201 状態コードは、製品の作成時に CreatedAtAction メソッドによって生成されます。 このコード パスでは、Product オブジェクトは応答本文で指定されます。 新しく作成された製品の URL を含む Location 応答ヘッダーが用意されています。

その他の技術情報