ASP.NET MVC でのHTTP Status Code を伴ったエラー通知とエラー内容のロギング

先週から ASP.NET MVC を試しています。以前、ASP.NET Web フォーム を試したときには、生成される HTML のあまりの汚さに使う気が失せたのですが、ASP.NET MVC は素直な HTML が生成されるので好感が持てます 🙂

試している中で、エラー等が発生したときの適切な HTTP Status Code を伴った通知とエラー内容のロギングを組み合わせたものがある程度できたので、書いてみます。

プロジェクトを生成(ASP.NET MVC 3 Web アプリケーション、テンプレートは「インターネットアプリケーション」、ビューエンジンは「Razor」を選択してます)後、まずはエラー内容を記録するための DB が必要ですね。

テーブル Error を作成します。

テーブル名: Error
列名 データ型 nullを許容 備考
Id int false 主キー
Uri varchar(255) false
Controller varchar(30) false
Action varchar(30) false
Exception nvarchar(100) false
Stack nvarchar(max) false
Updated datetime false

DB ができたら、データモデルを作成します(参考資料「[C#] #33. フェーズ #1 – アプリケーションの作成」)。

次に、HTTP Status Code を伴った通知とエラー内容のロギングを行うフィルタを実装します。
ルートに Filters フォルダを作成して、作った Filters フォルダの中に ErrorLogAttribute.cs を作成します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestErrorPage.Models;

namespace TestErrorPage.Filters
{
    public class ErrorLogAttribute : FilterAttribute, IExceptionFilter
    {
        // アクション・メソッドで例外が発生した場合に実行
        public void OnException(ExceptionContext filterContext)
        {
            // ExceptionContext オブジェクトが空の場合はエラー
            if (filterContext == null)
            {
                throw new ArgumentNullException("No ExceptionContext");
            }

            // 例外が HttpException の場合、ログ出力をせず、指定されたレスポンスコードとエラーメッセージを返す
            if (filterContext.Exception.GetType() == typeof(HttpException))
            {
                // 例外を処理済みとみなす
                filterContext.ExceptionHandled = true;

                // HTTP Status code を設定
                filterContext.HttpContext.Response.StatusCode =
                    (filterContext.Exception as HttpException).GetHttpCode();

                // Error ビュー (Shared/ErrorCodeMessage.cshtml) を表示
                filterContext.Result = new ViewResult()
                {
                    ViewName = "ErrorCodeMessage",
                    ViewData = new ViewDataDictionary(filterContext.Exception),
                };

                return;
            }

            var _db = new TestErrorPageEntities();

            // ルート・パラメータを取得
            var route = filterContext.RouteData;

            // Error オブジェクト(エンティティ)を生成
            // (リクエスト URI、コントローラー名、アクション名、
            //  スタック・トレース、エラー発生日時を設定)
            var err = new Error()
            {
                Uri = filterContext.HttpContext.Request.RawUrl,
                Controller = route.Values["controller"].ToString(),
                Action = route.Values["action"].ToString(),
                Exception = filterContext.Exception.GetType().ToString() + ": " +
                    filterContext.Exception.Message,
                Stack = filterContext.Exception.StackTrace,
                Updated = DateTime.Now,
            };

            // Error オブジェクトの内容をデータソースに反映
            _db.Errors.AddObject(err);
            _db.SaveChanges();

            // 例外を処理済みとみなす
            filterContext.ExceptionHandled = true;

            // HTTP Status code を設定
            filterContext.HttpContext.Response.StatusCode = 500;

            // Error ビュー (Shared/Error.cshtml) を表示
            filterContext.Result = new ViewResult()
            {
                ViewName = "Error",
            };
        }
    }
}

次に、このフィルタをすべてのコントローラに適用するように、Global.asax.cs の RegisterGlobalFilters へ記述を追加します。


        public static void RegisterGlobalFilters(GlobalFilterCollection filters)
        {
            filters.Add(new HandleErrorAttribute());

            // ErrorLog フィルタをすべてのコントローラへ適用する
            filters.Add(new ErrorLogAttribute());
        }

次に、エラー通知用のビューを作ります。Views\Shared フォルダの中に ErrorCodeMessage.cshtml を作ります。

@model System.Web.HttpException

@{
    ViewBag.Title = "エラー";
}

<h2>
    @Model.Message
</h2>

次に、動作確認用のコントローラを作ります。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;

namespace TestErrorPage.Controllers
{
    public class TestController : Controller
    {
        public ActionResult LogTest()
        {
            throw new Exception("予期せぬエラーが発生しました");
        }

        public ActionResult Test400()
        {
            throw new HttpException(400, "Bad Request");
        }
    }
}

これで、ブラウザから /Test/LogTest を表示させると、エラー通知とログ記録が行われます。

また、/Test/Test400 を表示させると、エラー通知が行われます。

ただ、この状態だと、存在しないアクションが要求された場合にデフォルトのエラー表示が行われてしまうので、さらに、手を加えます。

ルートの Web.config の<system.web> ~ </system.web> の中に <customErrors> を記述する。


  <system.web>

    <customErrors defaultRedirect="Error" mode="On">
        <error statusCode="404" redirect="Errors/index/404" />
    </customErrors>

次に、Errors コントローラを作成する。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using TestErrorPage.Filters;

namespace TestErrorPage.Controllers
{
    [ErrorLog]
    public class ErrorsController : Controller
    {
        //
        // GET: /Errors/

        public ActionResult Index(int id, string aspxerrorpath)
        {
            string message = "";
            if (id == 404)
                message = "Not Found: " + aspxerrorpath;

            throw new HttpException(id, message);
        }
   }
}

これで、存在しないアクションが呼び出されても、指定したエラー通知の画面が表示されます。

 

もしかしたら、もっとクールなやり方があるかもしれません。あったら、指摘していただけると嬉しいです 😉

参考にさせていただいたページ:

[ASP.NET MVC]例外フィルタをカスタマイズするには?[3.5、C#、VB]

HandleErrorの使い方

 


ASP.NET MVC でのHTTP Status Code を伴ったエラー通知とエラー内容のロギング」への1件のフィードバック

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です