[.NET Core] Console Application で汎用ホストを利用する

.NET Core 3.1 でバッチを作成する機会があり、Console Application 作成について調べたことのまとめ。

# やりたいこと
- .NET Core 3.1 Console Application で DI したい
- 設定ファイルの値をいい感じに使いたい
- NLog の設定をしたい

# 環境
- Windows 10 Pro
- Visual Studio 2019
- .NET Core 3.1

# 汎用ホストとは
ASP.NET Core では Web ホストを生成するが、Console Application では、汎用ホストを利用する。
汎用ホストを利用することで、DI や設定ファイルの取得などが簡単に実現できる。
https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1

# 実装
## パッケージのインストール
汎用ホストを利用するために必要なパッケージをインストールする。

- Microsoft.Extensions.Hosting

NLog を利用したい場合は、以下パッケージも追加。

- NLog.Extensions.Logging

## Program.cs の実装
``` Program.cs
public static void Main(string[] args) => CreateHostBuilder(args).Build().Run();

public static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureAppConfiguration((hostContext, config) =>
        {
            // 引数を取得したい場合
            config.AddCommandLine(args);
        })
        .ConfigureLogging((hostContext, logging) =>
        {
            logging.ClearProviders();
            // appsettings.json から NLog の設定情報を取得する
            logging.AddNLog(new NLogLoggingConfiguration(hostContext.Configuration.GetSection("NLog")));
        })
        .ConfigureServices((hostContext, services) =>
        {
            // 必要なサービスのDIを記載する
        });
```

Host.CreateDefaultBuilder の規定の動作に何も追加する必要がなければ、ConfigureAppConfiguration、ConfigureLogging、ConfigureServicesは不要。
（ただし、DI したい場合がほとんどだと思うので、ConfigureServices は必要になってくる）

プロジェクトに appsettings.json を追加すれば、既定の動きとして値を取得してくれる。
また、appsettings.json 内に NLog の設定値を記載できる。（NLog.config の追加が不要）

``` appsettings.json
{
  // NLog設定
  "NLog": {
    "throwConfigExceptions": true,
    "targets": {
      "logfile": {
        "type": "File",
        "fileName": "C:\\Log\\${shortdate}.log"
      },
      "logconsole": {
        "type": "Console"
      }
    },
    "rules": [
      {
        "logger": "*",
        "minLevel": "Info",
        "writeTo": "logconsole"
      },
      {
        "logger": "*",
        "minLevel": "Debug",
        "writeTo": "logfile"
      }
    ]
  },
  // 必要なアプリケーション設定値があれば以下に追記する
}
```

## appsettings.json の取り扱い
Host.CreateDefaultBuilder で appsettings.json の読み込みを行ってくれる。
読み込んだ値をクラスに展開（オプションパターン）しておくと、いろいろなところで使えて便利。
appsettings.json の形に合わせたクラスを定義する。

``` appsettings.json
{
  "Group": {
    "Item1": "ItemValue",
    "Item2": 1
  }
}
```
``` AppSettings.cs
public class AppSettings
{
    public Group Group { get; set; }
}
public class Group
{
    public string Item1 { get; set; }
    public int Item2 { get; set; }
}
```

実行時に appsettings.json が必要なため、ファイルのプロパティを以下に変更しておく。

- ビルドアクション　⇒　コンテンツ
- 出力ディレクトリにコピー　⇒　新しい場合はコピーする or 常にコピーする

## IHostedService の実装
https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1#ihostapplicationlifetime
上記を参考に、IHostedService を実装する。
logger と options を使えるようにしたサンプル。

``` LifetimeEventsHostedService.cs
public class LifetimeEventsHostedService : IHostedService
{
    private readonly IHostApplicationLifetime appLifetime;
    private readonly ILogger<LifetimeEventsHostedService> logger;
    private readonly IOptions<AppSettings> options;

    public LifetimeEventsHostedService(
        IHostApplicationLifetime appLifetime
        , ILogger<LifetimeEventsHostedService> logger
        , IOptions<AppSettings> options)

    {
        this.appLifetime = appLifetime;
        this.logger = logger;
        this.options = options;
    }

    public Task StartAsync(CancellationToken cancellationToken)
    {
        appLifetime.ApplicationStarted.Register(OnStarted);
        appLifetime.ApplicationStopping.Register(OnStopping);
        appLifetime.ApplicationStopped.Register(OnStopped);

        logger.LogInformation("StartAsync");
        return Task.CompletedTask;
    }

    public Task StopAsync(CancellationToken cancellationToken)
    {
        logger.LogInformation("StopAsync");
        return Task.CompletedTask;
    }

    private void OnStarted()
    {
        logger.LogInformation("OnStarted");
        logger.LogInformation($"{options.Value.Group.Item1}{options.Value.Group.Item2}");
    }

    private void OnStopping()
    {
        logger.LogInformation("OnStopping");
    }

    private void OnStopped()
    {
        logger.LogInformation("OnStopped");
    }
}
```
上記を実装後、Program.cs の ConfigureServices を修正すれば、logger と options の使用が可能。

``` Program.cs
.ConfigureServices((hostContext, services) =>
{
    services.AddHostedService<LifetimeEventsHostedService>();
    services.Configure<AppSettings>(hostContext.Configuration);
})
```

# 参考URL
- https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/host/generic-host?view=aspnetcore-3.1
- https://docs.microsoft.com/ja-jp/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1