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



# 結論

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

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

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

# 前提

## ASP.NET Web APIのコントローラーの戻り値の型
次の4パターンがあります。

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

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

このうち

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

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

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

## 任意の型
```csharp
public IEnumerable<Product> Get()
{
    return products;
}
```

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

## IHttpActionResult

```csharp
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

```csharp
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できます。

#### よくない点

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

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

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

2番目は[例外的状態にだけ例外を使用する - Strategic Choice](http://d.hatena.ne.jp/asakichy/20120120/1327011562)によると

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

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

```csharp
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](https://qiita-image-store.s3.amazonaws.com/0/15964/82582b75-5525-2fdf-83bf-8de7940ebd7f.png "スクリーンショット 2016-06-08 10.57.25.png")

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

### テスト

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

```csharp
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](https://qiita-image-store.s3.amazonaws.com/0/15964/d31de3b9-1187-3dc7-d993-9fd7a55c759a.png "スクリーンショット 2016-06-08 11.09.21.png")

例外が発生します。

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

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


## IHttpActionResult
### 可読性
#### 404 Not Found

```csharp
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)](https://msdn.microsoft.com/ja-jp/library/system.web.http.apicontroller_methods(v=vs.118).aspx)に他のメソッド定義があります。


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

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

# まとめ

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

# 参考
- [Action Results in Web API 2 | The ASP.NET Site](http://www.asp.net/web-api/overview/getting-started-with-aspnet-web-api/action-results)
- [ASP.NET Web API 2 - IHttpActionResult - miso_soup3 Blog](http://miso-soup3.hateblo.jp/entry/2013/07/22/230526)
- [IHttpActionResult – new way of creating responses in ASP.NET Web API 2 | StrathWeb. A free flowing web tech monologue.](http://www.strathweb.com/2013/06/ihttpactionresult-new-way-of-creating-responses-in-asp-net-web-api-2/)