ASP.NET Web API での例外処理

この記事では、ASP.NET Web API でのエラー処理と例外処理について説明します。

HttpResponseException

Web API コントローラーで未処理の例外がスローされた場合はどうなりますか? 既定では、ほとんどの例外は、状態コード 500 の内部サーバー エラーを含む HTTP 応答に変換されます。

HttpResponseException 型は特殊なケースです。 この例外は、例外コンストラクターで指定した HTTP 状態コードを返します。 たとえば、id パラメーターが無効な場合、次のメソッドは 404 (Not Found) を返します。

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        throw new HttpResponseException(HttpStatusCode.NotFound);
    }
    return item;
}

応答をより詳細に制御するためには、応答メッセージ全体を構築して、HttpResponseException に含めることもできます。

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var resp = new HttpResponseMessage(HttpStatusCode.NotFound)
        {
            Content = new StringContent(string.Format("No product with ID = {0}", id)),
            ReasonPhrase = "Product ID Not Found"
        };
        throw new HttpResponseException(resp);
    }
    return item;
}

例外フィルター

Web API で例外を処理する方法をカスタマイズするには、例外フィルターを記述します。 例外フィルターは、コントローラー メソッドが HttpResponseException 例外以外のハンドルされない例外をスローしたときに実行されます。 HttpResponseException 型は、HTTP 応答を返すように特別に設計されているため、特殊なケースです。

例外フィルターでは、System.Web.Http.Filters.IExceptionFilter インターフェイスが実装されます。 例外フィルターを記述する最も簡単な方法は、System.Web.Http.Filters.ExceptionFilterAttribute クラスから派生させ、OnException メソッドをオーバーライドすることです。

Note

ASP.NET Web API の例外フィルターは、ASP.NET MVC の例外フィルターと似ています。 ただし、これらの例外フィルターは個別の名前空間で宣言され、別々に機能します。 特に、MVC で使用される HandleErrorAttribute クラスでは、Web API コントローラーによってスローされる例外は処理されません。

NotImplementedException 例外を HTTP 状態コード 501 (Not Implemented) に変換するフィルターを次に示します。

namespace ProductStore.Filters
{
    using System;
    using System.Net;
    using System.Net.Http;
    using System.Web.Http.Filters;

    public class NotImplExceptionFilterAttribute : ExceptionFilterAttribute 
    {
        public override void OnException(HttpActionExecutedContext context)
        {
            if (context.Exception is NotImplementedException)
            {
                context.Response = new HttpResponseMessage(HttpStatusCode.NotImplemented);
            }
        }
    }
}

HttpActionExecutedContext オブジェクトの Response プロパティには、クライアントに送信される HTTP 応答メッセージが含まれています。

例外フィルターの登録

Web API 例外フィルターを登録する方法はいくつかあります。

  • アクションごと
  • コントローラーごと
  • グローバル

特定のアクションにフィルターを適用するには、フィルターをアクションに属性として追加します。

public class ProductsController : ApiController
{
    [NotImplExceptionFilter]
    public Contact GetContact(int id)
    {
        throw new NotImplementedException("This method is not implemented");
    }
}

コントローラー上のすべてのアクションにフィルターを適用するには、フィルターをコントローラー クラスに属性として追加します。

[NotImplExceptionFilter]
public class ProductsController : ApiController
{
    // ...
}

このフィルターをすべての Web API コントローラーにグローバルに適用するには、構成時にフィルターのインスタンスを GlobalConfiguration.Configuration.Filters コレクションに追加します。 このコレクション内の例外フィルターは、任意の Web API コントローラー アクションに適用されます。

GlobalConfiguration.Configuration.Filters.Add(
    new ProductStore.NotImplExceptionFilterAttribute());

"ASP.NET MVC 4 Web アプリケーション" プロジェクト テンプレートを使用してプロジェクトを作成する場合は、App_Start フォルダーにある WebApiConfig クラス内に Web API 構成コードを配置します。

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new ProductStore.NotImplExceptionFilterAttribute());

        // Other configuration code...
    }
}

HttpError

HttpError オブジェクトは、応答本文でエラー情報を返す一貫した方法を提供します。 次の例は、応答本文で HttpError を使用して HTTP 状態コード 404 (Not Found) を返す方法を示しています。

public HttpResponseMessage GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Product with id = {0} not found", id);
        return Request.CreateErrorResponse(HttpStatusCode.NotFound, message);
    }
    else
    {
        return Request.CreateResponse(HttpStatusCode.OK, item);
    }
}

CreateErrorResponse は、System.Net.Http.HttpRequestMessageExtensions クラスで定義されている拡張メソッドです。 内部的には、CreateErrorResponseHttpError インスタンスを作成し、その HttpError を含む HttpResponseMessage を作成します。

この例では、メソッドが成功した場合、HTTP 応答で製品を返します。 ただし、要求された製品が見つからない場合、HTTP 応答には要求本文に HttpError が含まれます。 応答は次のようになります。

HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8
Date: Thu, 09 Aug 2012 23:27:18 GMT
Content-Length: 51

{
  "Message": "Product with id = 12 not found"
}

この例では、HttpError が JSON にシリアル化されていることに注意してください。 HttpError を使用する利点の一つは、他の厳密に型指定されたモデルと同じコンテンツ ネゴシエーションとシリアル化プロセスを経ることです。

HttpError とモデルの検証

モデル検証では、モデルの状態を CreateErrorResponse に渡して、検証エラーを応答に含めることができます。

public HttpResponseMessage PostProduct(Product item)
{
    if (!ModelState.IsValid)
    {
        return Request.CreateErrorResponse(HttpStatusCode.BadRequest, ModelState);
    }

    // Implementation not shown...
}

この例では、次の応答が返される場合があります。

HTTP/1.1 400 Bad Request
Content-Type: application/json; charset=utf-8
Content-Length: 320

{
  "Message": "The request is invalid.",
  "ModelState": {
    "item": [
      "Required property 'Name' not found in JSON. Path '', line 1, position 14."
    ],
    "item.Name": [
      "The Name field is required."
    ],
    "item.Price": [
      "The field Price must be between 0 and 999."
    ]
  }
}

モデル検証の詳細については、「ASP.NET Web API のモデル検証」を参照してください。

HttpResponseException での HttpError の使用

前の例では、コントローラー アクションから HttpResponseMessage メッセージが返されますが、HttpResponseException を使用して HttpError を返すこともできます。 これにより、通常の成功の場合は厳密に型指定されたモデルを返し、エラーがある場合は HttpError を返すことができます。

public Product GetProduct(int id)
{
    Product item = repository.Get(id);
    if (item == null)
    {
        var message = string.Format("Product with id = {0} not found", id);
        throw new HttpResponseException(
            Request.CreateErrorResponse(HttpStatusCode.NotFound, message));
    }
    else
    {
        return item;
    }
}