miso_soup3 Blog

主に ASP.NET 関連について書いています。

ASP.NET WEB API エラーの対処について

ASP.NET WEB API には、エラー用フィルター属性のインターフェイス、
System.Web.Http.Filters.IExceptionFilter
が用意されています。

これを使えば、(MVC と似たように)アクションメソッド内でエラーが起きた時に、
独自の処理を挟むことができます。
今日は、これについてです。

基本的な使い方は、Exception Handling in ASP.NET Web API
に書かれています。

デフォルトでは

デフォルトの状態(エラー用フィルター属性を使わない状態)では、
アクションメソッド内でエラーが起きた場合は、
500 Internal Server Error が返ってくるようになっています。

public class ValuesController : ApiController
{
	// GET api/values
	public IEnumerable<string> Get()
	{
		throw new Exception("エラーです");
	}
}

これはフレームワーク内で用意されている動きなので、
MVC の用に、”filters.Add(new HandleErrorAttribute());” といったように
書かなくても、500 Internal Server Error が返ってきます。

エラーの詳細を見せるか設定する

本題から話がずれますが、このデフォルトのレスポンスについて少し。

先ほど帰ってきたレスポンスの中には、スタックトレース等、
外部には公開したくない情報まで記載されています。

この詳細は、WebConfig の customErrors の mode を
On (または RemoteOnly)にすることで、隠すことができます。

// Web.config 
<system.web>
	<customErrors mode="On">
	</customErrors>
...


スタックトレース等が見えなくなった。(つД⊂)

この動きは、WebApiConfig.cs の中の HttpConfiguration の IncludeErrorDetailPolicy で変更することができます。

public static class WebApiConfig
{
	public static void Register(HttpConfiguration config)
	{
		//...
		config.IncludeErrorDetailPolicy = IncludeErrorDetailPolicy.Default;
	}
}

IncludeErrorDetailPolicy は Enum で、
Default, LocalOnly, Always, Never の4つを指定できます。

デフォルトは "Default" が設定されており、
先ほど説明したように、WebConfig の CustomErros の値に依存するようになっています。

エラーカスタム処理

本題のエラーに対する処理についてです。

エラーが起きたら、ログを取ったりメールを送信したり・・・と
自分でエラーを処理したい場合は、エラー用のフィルター属性を作成して
実現することができます。

System.Web.Http.Filters.IExceptionFilter を実装した属性を作成し、
適用したい範囲(Controller, Method, Global)に属性を適用させます。

実装

大抵、IExceptionFilter を一から実装する必要はなく、
すでに用意されている ExceptionFilterAttribute クラスを継承して作成します。

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

上のコードは、NotImplementedException が throw された時、
501 NotImplemented を返すようにした例です。
actionExecutedContext.Exception に例外が格納されているので、
ここで独自の処理を挟むことができます。

属性を作成した後は、適用したい範囲に合わせて
属性を付与する必要があります。

// とあるメソッドのみに適用したい場合
[MyExceptionFilter]
public IEnumerable<string> Get()
{
  //..
}

これで、Get() のメソッド内で NotImplementedException が throw された時、
501 NotImplemented が返ってくるようになります。

HttpResponseException は無視される

このエラー用フィルター属性ですが、HttpResponseException の場合は無視されます。

[MyExceptionFilter]
public void Post([FromBody]string value)
{
	//....
	if (item == null)
	{
		throw new HttpResponseException(HttpStatusCode.NotFound);
	}
}

上の場合、メソッド内で、HttpResponseException が throw されていますが、
[MyExceptionFilter] はスルーされ、属性内に書いた独自のエラー処理は実行されません。

ここからは憶測

スルーされる理由は、憶測になりますが、
WEB API の場合では先の例の様に、追加処理の中でデータが見つからない場合、
HttpResponseException を throw することで、クライアントに 404 Not Found を返す
といった方法がしばしばとられます。

HttpResponseException は、ステータスコードを返す時に用いられるため、
”例外”の中でも特例として扱われているため、IExceptionFilter 属性を適用していても
スルーされるのではないかなと思います。