【F# + ASP.NET MVC】 例外フィルターによるエラーログを出力するには

◆ 例外フィルター

namespace Sample.Attributes

open System;
open System.Web;
open System.Web.Mvc;
open System.Diagnostics

type ErrorLogAttribute() =
  inherit FilterAttribute()
  // IExceptionFilter インターフェースを実装
  interface IExceptionFilter with
    // アクション・メソッドで例外が発生した場合に実行
    member this.OnException(filterContext : ExceptionContext) =
      if box(filterContext) = null then
        raise <| new ArgumentNullException("NoContextException")
      else
        // 例外が HttpException の場合、ログ出力をせず、
        // 指定されたレスポンスコードとエラーメッセージを返す
        if filterContext.Exception.GetType() = typeof<HttpException> then
          // 例外を処理済みとみなす
          filterContext.ExceptionHandled <- true
          // HTTP Status code を設定
          filterContext.HttpContext.Response.StatusCode <-
            (filterContext.Exception :?> HttpException).GetHttpCode()
          // エラーView(Shared\CustomError.cshtml)を表示
          filterContext.Result <- 
            new ViewResult(ViewName = "CustomError", 
              ViewData = new ViewDataDictionary(filterContext.Exception))
        else
          // エラーログ出力
          // 今回はトレースログに出力とする
          let route = filterContext.RouteData
          Trace.WriteLine(filterContext.HttpContext.Request.RawUrl)
          Trace.WriteLine(route.Values.Item("controller").ToString)
          Trace.WriteLine(route.Values.Item("action").ToString)
          Trace.WriteLine(filterContext.Exception.StackTrace)
          Trace.WriteLine(DateTime.Now)

          // 例外を処理済みとみなす
          filterContext.ExceptionHandled <- true
          // HTTP Status code を設定
          filterContext.HttpContext.Response.StatusCode <- 500;
          // エラーView(Shared\CustomError.cshtml)を表示
          filterContext.Result <- 
            new ViewResult(ViewName = "CustomError")

◆ Global.fs

namespace Sample.Routing

open System
open System.Web
open System.Web.Mvc
open System.Web.Routing
open System.Reflection

open Sample.Mef
open Sample.Controllers
open Sample.Attributes

type Route = { controller : string
               action : string
               id : UrlParameter }

type Global() =
    inherit System.Web.HttpApplication() 

    // フィルターを登録
    static member RegisterGlobalFilters(filters:GlobalFilterCollection) =
        filters.Add(new HandleErrorAttribute())
        // ErrorLog フィルターをすべてのコントローラーへ適用する
        filters.Add(new ErrorLogAttribute())

    static member RegisterRoutes(routes:RouteCollection) =
        routes.IgnoreRoute("{resource}.axd/{*pathInfo}")
        routes.MapRoute("Default", 
                        "{controller}/{action}/{id}", 
                        { controller = "Home"; action = "Index"
                          id = UrlParameter.Optional } )

    member this.Start() =
        AreaRegistration.RegisterAllAreas() 
        
        // フィルターを登録
        Global.RegisterGlobalFilters(GlobalFilters.Filters)
        Global.RegisterRoutes(RouteTable.Routes) |> ignore
        

◆ Controller

namespace Sample.Controllers

open System
open System.Web
open System.Web.Mvc
open Sample.Attributes
open System.Diagnostics

type ErrorController() =
    inherit Controller()
    
    member this.Index() =
        // 例外を発生させる(テスト)
        raise <| new Exception()
        this.View() :> ActionResult

◆ View(エラー発生時に表示)

// ..\Views\Shared\CustomError.cshtml
<!DOCTYPE html>
<html>
<head>
    <title>カスタムエラーページ</title>
</head>
<body>
    <div>
      予期せぬエラーが発生しました。
    </div>
</body>
</html>

コメントを残す