Purpose
ASP.NET core Web APIで例外をハンドルする。その3。
内容について
ASP.NET Core Web API のエラーを処理するの方法を中心に、例外ハンドルの方法をまとめる。
チュートリアルメモ
Step6 検証失敗のエラー応答
Postなどで送られてきたリクエスト情報の検証に失敗したときに、独自のエラー情報を返すための方法。規定の動作は以下の通り。
Web API コントローラーでは、モデルの検証が失敗すると、MVC が ValidationProblemDetails という応答の種類で応答します。
簡単な動作確認のために、以下のPOCOを用意する。
public class SamplePOCO { [Required] public int Id { get; set; } [Required] public string Name { get; set; } }
これに対し、以下のリクエストをPOSRする。
{ "Ida":1, "Namea":"Test" }
結果は以下の通り。これが検証失敗時の既定のエラー内容。
{ "type": "https://tools.ietf.org/html/rfc7231#section-6.5.1", "title": "One or more validation errors occurred.", "status": 400, "traceId": "|8bab970d-45f7cfce6071a71b.", "errors": { "Name": [ "The Name field is required." ] } }
これを、InvalidModelStateResponseFactoryを使って独自のエラー応答に差し替える。
services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.InvalidModelStateResponseFactory = context => { var result = new BadRequestObjectResult(context.ModelState); // TODO: add `using System.Net.Mime;` to resolve MediaTypeNames result.ContentTypes.Add(MediaTypeNames.Application.Json); result.ContentTypes.Add(MediaTypeNames.Application.Xml); return result; }; });
上記実装では、以下のような結果を得ることができる。
{ "Name": [ "The Name field is required." ] }
モデルの検証に関して、何か処理を加えたい場合、リソースフィルターを利用するのかと思っていたが、ASP.NET Core - 実際の ASP.NET Core MVC フィルターによると、リソースフィルターの役割は以下のようになっているので、勘違いということがわかった。
次がリソース フィルターです。このフィルターは、(承認後の) 要求処理の最初と最後の両方で実行します。つまり、リソース フィルターを使って、要求の一番最初と一番最後 (MVC パイプラインを終える直前) にコードを実行することができます。リソース フィルターの好例が出力キャッシュです。このフィルターを使ってパイプラインの開始時にキャッシュをチェックし、キャッシュされている結果を返すことができます。キャッシュが設定されていなければ、パイプラインの終了時にアクションの応答をキャッシュに追加します。
Step7 クライアントのエラー応答
ProblemDetailsFactoryの実装
チュートリアルのはじめに、例外処理ミドルウェアと、Problemクラスを使ってエラーの内容を制御する方法に触れた。その際は、既定のProblemDetailsFactoryを使って最終的なProblemDetailsオブジェクトを作成した。今回は、このProblemDetailsの出力処理をカスタマイズして、独自の処理を実行させるというアプローチになる。具体的には、ProblemDetailsFactory.CreateProblemDetailsを実装することになると予想するが、こちらには、サンプルがなかったので、想像だけになる。最終的にProblemDetailsオブジェクトを返却することになるので、すべてのエラー情報に共通して何かを入れたいとか、特別な要件があるときに使うようなものなのかな・・・具体的なユースケースが思いつかない。
ApiBehaviorOptions.ClientErrorMapping の使用
ProblemDetails の応答の内容を構成するには、ClientErrorMapping プロパティを使用します。 たとえば、Startup.ConfigureServicesの次のコードにより、404 応答の type プロパティが更新されます。
がっつり引用することしかできない・・・ ProblemDetailsのTyppeプロパティに、指定したリンクを挿入することができるようだ。サンプルでは、404エラーについて記載されたページのURLを設定している。
services.AddControllers() .ConfigureApiBehaviorOptions(options => { options.SuppressConsumesConstraintForFormFileParameters = true; options.SuppressInferBindingSourcesForParameters = true; options.SuppressModelStateInvalidFilter = true; options.SuppressMapClientErrors = false; options.ClientErrorMapping[StatusCodes.Status404NotFound].Link = "https://httpstatuses.com/404"; });
Note:
サンプルでは、options.SuppressMapClientErrors = true;となっているが、SuppressMapCLientErrorsプロパティは、ProblemDetails応答を無効にするオプションなので、これをFalseとしたら、下記のようなレスポンスは得られない。
上記コードをStartUpクラスで設定したうえで、Action Method内で、Notfound Action resultを返却すると、次のようなレスポンスを取得できる。
{ "type": "https://httpstatuses.com/404", "title": "Not Found", "status": 404, "traceId": "|74646da-41b66640713bfcb0." }
今回の例では、Action ResultとしてのNotfound(業務ロジック的なエラー)を扱っているので混乱したが、URLが間違っていることによって発生するNot Foundには、対応していないので、動作確認をするときには注意が必要。
Step8 例外を処理するカスタムミドルウェア
最後のチュートリアルだが、内容は、Step1の例外ハンドラーと重複しているみたいである。例外を処理するカスタムミドルウェア=例外処理ミドルウェアをカスタマイズ(実装)したもの。と、理解してみた。
まとめ
Action Methodでハンドルされない/しない例外処理方法。 - 例外処理ミドルウェア/例外フィルターを使って共通的に処理。 - 出力するエラー内容(ProblemDetails)をカスタマイズしたいなら、Problemに設定orProblemDetailsFactoryを実装。 - 検証失敗時の内容をカスタムするときは、InvalidModelStateResponseResultを実装。
残課題:ミドルウェアと、フィルターのすみわけ。どっちでもやろうとしていることはできそうなイメージ。