しばやん雑記

ASP.NET とメイドさんが大好きなフリーランスのプログラマーのブログ

ASP.NET Core でカスタムエラーページを用意する

de:code 2018 の Ask the Speaker で貰った質問にサクッと回答できなかったので、検証した内容をブログに書いて残します。MVC と Web API でのエラーページ周りの質問だったと記憶してます。

ASP.NET と IIS を使っている場合は Web.config で customErrors や httpErrors という設定を追加すれば、ステータスコードに対応したエラーページを用意することが出来ますが、ASP.NET Core にもステータスコードでページを表示する機能がちゃんとあります。

ドキュメントをよく読むと、ちゃんと設定方法が書いてありましたが、気が付きませんでした。

Startup.Configure で以下のどれかを呼ぶと、良い感じにリダイレクトしたり実行してページを返してくれます。よく使うのは下の二つじゃないかなと思います。

  • UseStatusCodePages
  • UseStatusCodePagesWithRedirects
  • UseStatusCodePagesWithReExecute

WithRedirects は URL が変わりますが、WithReExecute は同じ URL のままになるので、この辺りは上手く要件に合わせて使い分けてもらえれば良いですね。

ASP.NET の場合は静的ファイルとそれ以外で処理方法が変わっていて面倒でしたが、ASP.NET Core は統合されているので非常に挙動が分かりやすいです。

app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();

// {0} にステータスコードが入ってくる
app.UseStatusCodePagesWithReExecute("/error/{0}");

app.UseMvc(routes =>
{
    routes.MapRoute(
        name: "default",
        template: "{controller=Home}/{action=Index}/{id?}");
});

これでカスタムな 400 や 404 ページを用意することが出来ますが、Web API の場合は HTML として返す必要がないので、この設定が余計なものになってしまいます。

実際に Web API を追加して、BadRequest などを返すとエラーページが返ってしまいます。

[ApiController]
[Produces("application/json")]
[Route("api/[controller]")]
public class ValuesController : Controller
{
    [HttpGet]
    public ActionResult Get()
    {
        return NotFound();
    }
}

f:id:shiba-yan:20180525225353p:plain

ASP.NET では Response.TrySkipIisCustomErrors でスキップ出来ましたが、ASP.NET Core では SkipStatusCodePages 属性を付けると同じようにスキップ出来ます。

もしくは何かオブジェクトを渡すと自動でスキップされます。地味にはまりそうな部分です。

[ApiController]
[SkipStatusCodePages]
[Produces("application/json")]
[Route("api/[controller]")]
public class ValuesController : Controller
{
    [HttpGet]
    public ActionResult Get()
    {
        return NotFound();
        // return NotFound("404");
    }
}

ちなみに ASP.NET Core 2.1 で追加された ApiController 属性を付けていても、この部分だけは挙動が変わらないので注意が必要です。Issue を見た感じでは 2.2 ぐらいで変わりそうですが。

この属性は内部で IStatusCodePagesFeature を参照しています。ASP.NET Core は HttpContext.Features を使うといろんな処理を触ることが出来ますが、この辺りドキュメントがほぼ無い気がします。