LoginSignup
26
32

More than 5 years have passed since last update.

ASP.NET Web APIのコントローラーの戻り値型の使い分け

Last updated at Posted at 2016-06-08

結論

以下の理由で、エラーレスポンスを返すときはIHttpActionResult型を使うのが良さそうです。

  1. 可読性がよい
  2. デバッグしやすい
  3. テストコードにstubが要らない

エラーレスポンスを返さないときは任意の型を使うのが良いです。

前提

ASP.NET Web APIのコントローラーの戻り値の型

次の4パターンがあります。

  1. 任意の型
  2. HttpResponseMessage
  3. void
  4. IHttpActionResult

IHttpActionResult は ASP.NET Web API 2 から増えました。

このうち

  • HttpResponseMessageは、特に便利ではない
  • voidは204レスポンス固定

ので、今回の議論からは外します。

エラーレスポンスのないAPI

GET /api/pruductsのようなエラーレスポンスを返さないAPIを実装します。

任意の型

public IEnumerable<Product> Get()
{
    return products;
}

メソッドシグネチャで戻り値の型が明示されていて良さげです。

IHttpActionResult

public IHttpActionResult Get()
{
    return Ok(products);
}
  • 戻り値型が固定のIHttpActionResult
  • Ok(product)

が、ちょっと格好わるいです。

エラーレスポンスのないAPIは任意の型を使うのが良さそうです。

エラーレスポンスのあるAPI

GET /api/pruducts/:idのようなエラーレスポンスを返すAPIを実装します。

任意の型

可読性

200 OK

public Product Get(int id)
{
    return products.FirstOrDefault(p => p.Id == id);
}

ここまではエラーレスポンスのないAPIと一緒です。
404などのエラーレスポンスを返す時はどうするのでしょうか?Product型以外はreturnできません。

Product型をHttpResponseMessageのサブクラスにしてレスポンスコードを持たせるのは、面倒です。

404 Not Found

public Product Get(int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);

    if (product == null)
    {
        throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
    }

    return product;
}

HttpResponseExceptionをthrowします。
例外であれば、メソッドの戻り値と違う型をthrowできます。

よくない点

ここで二つの疑問があります。

  1. 例外を作るソースコードnew HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound))がやたら長い
  2. 制御フローに例外を使う

1番目はヘルパーメソッドを書けば解決できそうです。

2番目は例外的状態にだけ例外を使用する - Strategic Choiceによると

パフォーマンスが悪くなる。
可読性が低くなる。
関係ない例外を消費して、他のバグを見逃す危険がある。

の点があげられています。
パフォーマンスはチューニングの問題なので、ここでは考えません。
可読性が悪くてバグを見逃す可能性が高い点を考えると
こうすれば・・・

public Product Get(int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);

    if (product == null)
    {
        _404();
    }

    return product;
}

private void _404()
{
    throw new HttpResponseException(Request.CreateResponse(HttpStatusCode.NotFound));
}

可読性はクリアできそうです。

デバッグ

ところで、デバッグしてみましょう。

スクリーンショット 2016-06-08 10.57.25.png

ハンドルしていない例外を投げると、デバッガはそこで毎回止まります。
不便です。

テスト

controllerのテストを書いてみます。

using Microsoft.VisualStudio.TestTools.UnitTesting;

namespace HelloWorldAPI.Controllers.Tests
{
    [TestClass()]
    public class ProductsControllerTests
    {
        [TestMethod()]
        public void GetTest()
        {
            new ProductsController().Get(2);
        }
    }
}

動かしてみると・・・
スクリーンショット 2016-06-08 11.09.21.png

例外が発生します。

よく見るとArgumentNullExceptionです。Requestプロパティの値がnullなので、起きる例外です。

Requestプロパティに何かしらのstubを設定する必要があります。

IHttpActionResult

可読性

404 Not Found

public IHttpActionResult Get(int id)
{
    var product = products.FirstOrDefault(p => p.Id == id);

    if (product == null)
    {
        return NotFound();
    }

    return Ok(product);
}

最初から読みやすいです。

ヘルパーメソッド

  • Ok
  • NotFound

はApiControllerに定義されたヘルパーメソッドです。

ApiController メソッド (System.Web.Http)に他のメソッド定義があります。

デバッグ

もちろん、デバッガで意図せずに止まりません。

テスト

先ほどのテストコードがそのまま動きます。

まとめ

  1. GET /api/pruductsのようなエラーレスポンスを返さないAPIは任意の型を使うのが簡単
  2. GET /api/pruducts/:idのようなエラーレスポンスを返すAPIはIHttpActionResultを使うのが便利

参考

26
32
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
26
32