.NETの汎用ホスト(Generic Host)

# .NET 汎用ホスト(Generic Host)とは
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/generic-host?tabs=appbuilder

> ''ホスト'' とは、次のようなアプリのリソースと有効期間機能をカプセル化するオブジェクトです。
> 依存関係の挿入 (DI)
> ログの記録
> 構成
> アプリのシャットダウン
> IHostedService の実装

やってみよう。

# NuGet
Microsoft.Extensions.Hosting をパッケージに追加します。

https://www.nuget.org/packages/Microsoft.Extensions.Hosting/

# ホストの作成
ホストを作るにはビルダーを使います。
```csharp:Program.cs
HostApplicationBuilder hostBuilder = Host.CreateApplicationBuilder(args);
```

`Host.CreateDefaultBuilder()`もあるけど？

https://stackoverflow.com/questions/75229793/host-createdefaultbuilder-vs-host-createapplicationbuilder-in-net-platform-exte

## 依存関係の挿入(DI)
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/dependency-injection

サービスコンテナで依存関係を挿入できry

## 構成(Configuration)
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/configuration

> Host.CreateApplicationBuilder(String[]) メソッドにより、優先度の高い方から低い方へ、次の順序でアプリの既定の構成が提供されます。
> 1. コマンドライン構成プロバイダーを使用するコマンドライン引数。
> 2. 環境変数構成プロバイダーを使用する環境変数。
> 3. Development 環境でアプリが実行される際の App シークレット。
> 4. JSON 構成プロバイダーを使用する appsettings.json。
> 5. JSON 構成プロバイダーを使用する appsettings.Environmentjson。 たとえば、appsettings.Production.json および appsettings.Development.json。
> 6. ChainedConfigurationProvider: 既存の IConfiguration をソースとして追加します。

アプリ独自の設定をappsettings.jsonに定義。
```json:appsettings.json
{
  "lang": "ja",
  "LogOptions": {
    "SingleLine": true,
    "TimestampFormat": "yyyy-MM-dd HH:mm:ss "
  }
}
```

appsettings.jsonの内容をマップするクラスを実装。
```csharp:AppSettings.cs
    internal class AppSettings
    {
        public string Lang { set; get; } = "en";
        public LogOptions LogOptions { set; get; } = new LogOptions();
    }

    internal class LogOptions
    {
        public bool SingleLine { set; get; } = false;
        public string TimestampFormat { set; get; } = "HH:mm:ss ";
    }
```

設定をマップする
```csharp:Program.cs
var appSettings = hostBuilder.Configuration.Get<AppSettings>() ?? new AppSettings();
```

設定の一部分だけをマップする
```csharp:Program.cs
var logOptions = hostBuilder.Configuration.GetSection(nameof(LogOptions)).Get<LogOptions>() ?? new LogOptions();
```

構成(Configuration)は、サービスコンテナンに登録されてます。
```csharp:Program.cs
//using IHost host = hostBuilder.Build();
var configuration = host.Services.GetService<IConfiguration>()!;
```

## ログの記録(Logging)
https://learn.microsoft.com/ja-jp/dotnet/core/extensions/logging?tabs=command-line

### Console / SimpleConsole
ログプロバイダーとしてSimpleConsoleを追加します。
ログレベルは、appsettings.Development.jsonの `/Logging/LogLevel`に設定した内容が反映されます。
出力オプションは[構成(Configuration)](#構成configuration)から取得した内容を設定しています。
```csharp:Program.cs
hostBuilder.Logging.ClearProviders().AddSimpleConsole(options =>
{
    options.SingleLine = appSettings.LogOptions.SingleLine;
    options.TimestampFormat = appSettings.LogOptions.TimestampFormat;
});
```

ログレベルは、環境毎のファイルに定義。
```json:appsettings.Development.json
{
  "Logging": {
    "LogLevel": {
      "Default": "Debug",
      "Microsft": "Information"
    }
  }
}
```

### Nlog
2024/11/31更新！
#### NuGet
https://www.nuget.org/packages/NLog.Extensions.Logging/

#### プロバイダーの追加
ログプロバイダーとしてNLogを追加します。
```csharp:Program.cs
using NLog.Extensions.Logging;
hostBuilder.Logging.ClearProviders().AddNLog();
```

#### NLogの構成
```json:appsettings.json
{
  "NLog": {
    "autoReload": true,
    "throwConfigExceptions": true,
    "internalLogLevel": "Info",
    "internalLogFile": "LOG/internal-nlog.txt",
    "extensions": [
      { "assembly": "NLog.Extensions.Logging" }
    ],
    "targets": {
      "file": {
        "type": "File",
        "fileName": "LOG/log.txt"
      },
      "console": {
        "type": "Console"
      }
    }
  }
}
```
```appsettings.Development.json
{
  "NLog": {
    "rules": {
      "0": {
        "logger": "Microsoft.*",
        "maxLevel": "Debug",
        "final": true
      },
      "1": {
        "logger": "*",
        "minLevel": "Trace",
        "writeTo": "file, console"
      }
    }
  }
}
```

ロガーは、サービスコンテナンに登録されてます。
```csharp:Program.cs
//using IHost host = hostBuilder.Build();
var logger = host.Services.GetService<ILogger<Program>>()!;
```

## ローカライズ
2024/11/18更新！

https://learn.microsoft.com/ja-jp/dotnet/core/extensions/localization

### NuGet
https://www.nuget.org/packages/Microsoft.Extensions.Localization

### リソースファイル追加
```xml:MessageResources.reex
<root>
  <data name="hello_world" xml:space="preserve">
    <value>こんにちは、世界！</value>
  </data>
</root>
```
```xml:MessageResources.en.reex
<root>
  <data name="hello_world" xml:space="preserve">
    <value>Hello World!</value>
  </data>
</root>
```

### サービス作成
```csharp:MessageService.cs
internal class MessageService(IStringLocalizer<MessageResources> localizer)
{
    internal string this[string key]
    {
        get
        {
            return localizer[key];
        }
    }
}
```

### ホストの作成
```csharp:Program.cs
// カルチャの設定
CultureInfo.CurrentUICulture = CultureInfo.CurrentCulture = CultureInfo.GetCultureInfo(appSettings.Lang);

// ローカライゼーションサービスの追加
hostBuilder.Services.AddLocalization();

// MessageServiceを登録
hostBuilder.Services.AddSingleton<MessageService>();
```

[次](#ihostedserviceの実装) で利用してみます。

## IHostedServiceの実装

> ホストが起動すると、サービス コンテナーのホステッド サービスのコレクションに登録されている IHostedService の各実装で IHostedService.StartAsync が呼び出されます。 
> アプリの相互依存するすべてのリソースを 1 つのオブジェクトに含める主な理由は、アプリの起動と正常なシャットダウンの制御の有効期間の管理のためです。

`IHostedService`の実装`HostedService`を作成します。
```csharp:HostedService.cs
    internal class HostedService(
        //コンストラクタの引数はサービスコンテナによってDIされる
        IHostApplicationLifetime hostApplicationLifetime,
        IHostEnvironment environment,
        IHostLifetime hostLifetime,
        IConfiguration configuration,
        ILogger<HostedService> logger,
        MessageService messageService
    ) : IHostedService
    { 
        public Task StartAsync(CancellationToken cancellationToken)
        {
            logger.LogTrace("HostedService.StartAsync");

            // 構成ファイルの中から独自設定を取得
            var appSettings = configuration.Get<AppSettings>() ?? new AppSettings();
            logger.LogDebug(appSettings.Lang);

            // ローカライズされたメッセージの取得
            logger.LogDebug(messageService["hello_world"]);

            // アプリを終了する
            hostApplicationLifetime.StopApplication();

            return Task.CompletedTask;
        }

        public Task StopAsync(CancellationToken cancellationToken)
        {
            logger.LogTrace("HostedService.StopAsync");
            return Task.CompletedTask;
        }
    }
```

実装した`HostedService`をサービスコンテナンに登録します。
```csharp:Program.cs
hostBuilder.Services.AddHostedService<HostedService>();
```

## IHostEnvironment
> アプリケーションが実行されているホスティング環境に関する情報を提供します。

## IHostApplicationLifetime
アプリケーションの終了を通知する。

## IHostLifetime
ホストを開始および停止するタイミングが制御できるらしい。

# ホストの起動
```csharp:Program.cs
using IHost host = hostBuilder.Build();
await host.RunAsync();
```