きなこもち.net

.NET Framework × UiPath,Orchestrator × Azure × AWS × SIer

ASP.NET Core × Nzen(自作WEBアプリ) × リファクタリング(その1)

この記事の目的

この記事では、
自作WEBアプリのリファクタリング内容のまとめ
を目的としています。

ここでは、「プログラミング ASP.NET Core」を一通り読んで、気になったところを中心にリファクタリングした結果をまとめていきます。
とりあえず、今回は、ログ出力と、例外処理についてまとめていきます。

本題

★ログの出力について

Before

従来のASP.NETと同様の考え方をしていたため、Log4netをNugetでインポートし、アプリケーション起動時にILogインスタンスをNewして、どこでも利用できるようにしておくという方針で実装しました。

修正方針

ログの出力といえばLog4netという固定観念から、特に考えずにlog4netを利用していました。しかし、ASP.NET Coreには組み込みのログプロバイダーがあるということなので、今回はそちらを利用することにしました。
docs.microsoft.com


After

各コントローラー(Homeコントローラー)にて

       //コンストラクタと、フィールド変数を追加。
        private ILogger<HomeController> _logger;

        public HomeController(ILogger<HomeController> logger)
        {
            _logger = logger;
        }

ここで、Logger<T>のTの部分は、ログのカテゴリ名になります。HomeControlllerの場合、HomeControllerがカテゴリ名として設定された状態のログを出力することができます。

コントローラにて、ILoggerクラスをコンストラクタの引数として定義しているのは、ASP.NET Coreでは、ILoggerFactory型がDIシステムにおいて規定でマッピングされる項目の一つであるためです。

参考)実験

せっかくなので、どのように情報が出力されるか見てみます。
ログの出力処理を以下のように実装して出力を確認します。

        [HttpGet]
        public IActionResult Index()
        {
            _logger.LogTrace("LogTrace");
            _logger.LogDebug("LogDebug");
            _logger.LogInformation("LogInformation");
            _logger.LogWarning("LogWarning");
            _logger.LogError("LogError");
            _logger.LogCritical("LogCritical");

            return View(new HomeModel());
        }

この時のAppSettingsは以下の通りです。

{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "System": "Information",
      "Microsoft": "Information"
    }
  }
}

一部省略しましたが、このように出力されました。

Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Index", controller = "Home"}. Executing action Nzen.Controllers.HomeController.Index (Nzen)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method Nzen.Controllers.HomeController.Index (Nzen) - Validation state: Valid
Nzen.Controllers.HomeController:Debug: LogDebug
Nzen.Controllers.HomeController:Information: LogInformation
Nzen.Controllers.HomeController:Warning: LogWarning
Nzen.Controllers.HomeController:Error: LogError
Nzen.Controllers.HomeController:Critical: LogCritical


試しに、AppSettingsのDefaultLoglevelをWarningに設定して実行した結果も載せておきます。

Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Route matched with {action = "Index", controller = "Home"}. Executing action Nzen.Controllers.HomeController.Index (Nzen)
Microsoft.AspNetCore.Mvc.Internal.ControllerActionInvoker:Information: Executing action method Nzen.Controllers.HomeController.Index (Nzen) - Validation state: Valid
Nzen.Controllers.HomeController:Warning: LogWarning
Nzen.Controllers.HomeController:Error: LogError
Nzen.Controllers.HomeController:Critical: LogCritical

想定通り、Warningより低いレベルのログは出力されなくなりました。めでたしめでたし。



★例外の処理

Before

従来のASP.NET 開発と同様に、Global.asaxでまとめてハンドリングしようとして、できなくて、いったん放置した状態でした。

修正方針

ここでは、2つの場合分けをしてそれぞれを修正していきます。

  • ASP.NET coreの処理パイプラインが実行される前(アプリケーション起動時)のエラー

アプリケーション起動時にDBから情報をとろうとして失敗したり、設定ファイルの設定ミスで失敗したりと、アプリケーションが起動する際に発生するエラーもなくはないです。まずは、それに対応する必要があります。

アプリケーション起動時のエラーは、Program.csファイルで生成されているWebHostBuilderクラスに対して設定を追加することで、対応することができるので、そのようにしてみます。

  • ASP.NET coreの処理パイプライン中のエラー

ASP.NET coreでは、パイプライン設定時に、例外処理ミドルウェアを追加することで対応することができます。例外処理ミドルウェア自体は、自動生成されるStartupクラスに初めから設定されているので、その呼ばれる先の処理まで含めて改善していきます。


After
  • ASP.NET coreの処理パイプラインが実行される前(アプリケーション起動時)のエラー

CaptureStartupErrors設定を有効化します。規定値は、Falseです。

アプリが IIS の背後で Kestrel を使用して実行されている場合 (既定値は true) を除き、既定では false に設定されます。

あわせて、UseSettingで、詳細なエラー情報を見ることができるエラー画面のテンプレートの利用を強制します。この設定自体は、あってもなくてもDevelopment環境での開発には影響しなさそうです。

有効な場合 (または環境が Development に設定されている場合)、アプリは詳細な例外をキャプチャします。

public static void Main(string[] args) =>
            CreateWebHostBuilder(args)
            .CaptureStartupErrors(true)    //←ココ
            .UseSetting(WebHostDefaults.DetailedErrorsKey, "true") //←ココ
            .Build()
            .Run();
  • ASP.NET coreの処理パイプライン中のエラー

自動生成されるStartupクラスには、最初から以下のように実装されています。
特に記述やコメントはないですが、パイプライン作成処理(Configureメソッド)の一番最初に実装することが1つのポイントのようです。これによりアプリケーション起動後、発生する可能性のある例外をすべて補足できるようになります。

        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage(); //←ココとか
            }
            else
            {
                app.UseExceptionHandler("/Home/Error"); //←ココ
            }

現在の設定では、例外が発生した際、HomeコントローラーのErrorアクションが呼び出されることになりますが、Errorアクションでは、特にエラーに対する処理を実施しておりませんでした。今回は、エラーに関するログを出力するように変更してきます。

        [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)]
        public IActionResult Error()
        {
            var error = HttpContext.Features.Get<IExceptionHandlerFeature>();
            _logger.LogError(error?.Error, "何かしらメッセージ");

            return View(new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier });
        }
参考)実験

CaptureStartupErrorsを無効(規定値)を使った場合のエラー画面↓
f:id:kinakomotitti:20190609221129p:plain

CaptureStartupErrorsを有効にした場合のエラー画面↓
f:id:kinakomotitti:20190609221249p:plain





まとめ

例外処理、ログプロバイダーなどの設定は、基本的にフレームワーク側で用意してくれていたり、実装されたテンプレートを提供してくれたりと、至れり尽くせり状態なのでよくわかってなくても、ある程度できてしまっていました。

次は、環境変数の読み込みについてリファクタリングしていこう。そうしよう。