MiniAPI（五）：Options

オプションは設定の一種のアップグレード版で、通常、一定範囲内の設定を型としてラップして使用します。例えば、以下のRedisSettingはRedisの設定パラメーターです：
```json
{
  "Logging": {
    "LogLevel": {
      "Default": "Information",
      "Microsoft.AspNetCore": "Warning"
    }
  },
  "AllowedHosts": "*",
  "RedisSetting": {
    "Host": "127.0.0.1",
    "Port": 6379,
    "Password": "123",
    "ConnectionTimeOut": "10ms"
  }
}
```
以下のように設定型をコンテナに注入（注入について言及していませんが、ここでは初期化と理解できます）できます。
```csharp
var builder = WebApplication.CreateBuilder();
builder.Services.Configure<RedisSetting>(builder.Configuration.GetSection("RedisSetting"));
var app = builder.Build();
```
オプションには、通常オプションIOptions、ホットアップデートオプションIOptionsSnapshot、モニタリングオプションIOptionsMonitorの三種類があります。以下にそれぞれ説明します。

IOptionsオプション
```csharp
app.MapGet("/appinfo", (IOptions<RedisSetting> options) =>{
    return options.Value;
});
```
デモ結果
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRD63ewiah8aWxV6ayAniaJyygubhRvCUbcxE8GJTfXRpEbx4ZZRGxBnBKLRVz2koESibLaOeV8WIw32g/640?wx_fmt=png)

ホットロードオプション：IOptionsSnapshot
```csharp
app.MapGet("/snapshot", (IOptionsSnapshot<RedisSetting> options) =>{
    return options.Value;
});
```
結果
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRD63ewiah8aWxV6ayAniaJyygCr1F0mARIraaJI0Yx6icZevqW9aS6skEsfcMfFBPqibic66YLicp6KTIqw/640?wx_fmt=png)
接続タイムアウトConnectionTimeOutを15msに更新し、サービスを再起動せずに再度リクエストすると、結果は15msになります。
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRD63ewiah8aWxV6ayAniaJyygraT0u1PsDDpAb2VulfcYmGDLGZnLnLXkA2IE5wcSNUAEJv7cVdK0aw/640?wx_fmt=png)

モニタリングオプション：IOptionsMonitor
```csharp
app.MapGet("/monitorstart", (IOptionsMonitor<RedisSetting> options) =>{
    options.OnChange(redisSetting => {
        app.Logger.LogInformation(options.CurrentValue.ToString());
    });
    return "Ok";
});
```
モニタリングの呼び出し
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRD63ewiah8aWxV6ayAniaJyygkehXQt0QaVMBGA2PAhl6mCv6kNyibJ0N10DbjzXeejQuKicubTNlMkxg/640?wx_fmt=png)
ConnectionTimeOutを20msに更新すると、OnChangeがトリガーされ、ログが出力されます。
![alt 画像](https://mmbiz.qpic.cn/mmbiz_png/4CAA63nvHRD63ewiah8aWxV6ayAniaJyygOJw6RJRoOViaFfyvbGeSZzNNicYDfBiaiaOvtoaBQD9UbSaMZicJw6tCnpA/640?wx_fmt=png)
このモニタリング機能を活用して、設定が変更されたときに関連するサービス担当者に通知し、これが手動で設定されたものなのか、不正な改変なのかを知らせ、対処できるようにすることができます。

オプション名前付け：
同じ設定が二つある場合には、オプションの名前付けが非常に便利です。例えば、メインとバックアップが一つずつある場合です。
```json
"RedisSettings": {
    "Main": {
      "Host": "127.0.0.1",
      "Port": 6379,
      "Password": "123",
      "ConnectionTimeOut": "10ms"
    },
    "Prepare": {
      "Host": "127.0.0.1",
      "Port": 6380,
      "Password": "456",
      "ConnectionTimeOut": "12ms"
    }
}
```
エンティティクラスは静的定数で区別できます。
```csharp
public record RedisSetting{
    public const string Main = "Main";
    public const string Prepare = "Prepare";
    public string? Host { get; set; }
    public int Port { get; set; }
    public string? Password { get; set; }
    public string? ConnectionTimeOut { get; set; }
}
```
設定をそれぞれ注入するだけでよいです。
```csharp
builder.Services.Configure<RedisSetting>(RedisSetting.Main,
                                  builder.Configuration.GetSection("RedisSettings:Main"));
builder.Services.Configure<RedisSetting>(RedisSetting.Prepare,
                                   builder.Configuration.GetSection("RedisSettings:Prepare"));
```
ただし、IOptionsSnapshotとIOptionsMonitorはGetメソッドを介して名前付き設定を取得できますが、IOptionsにはGetメソッドが実装されていません。
```csharp
app.MapGet("/snapshotredissetting", (IOptionsSnapshot<RedisSetting> options) =>{
    return options.Get(RedisSetting.Main);
});
app.MapGet("/monitorstart", (IOptionsMonitor<RedisSetting> options) =>{
    options.OnChange(redisSetting => {
        app.Logger.LogInformation(options.Get(RedisSetting.Main).ToString());
        app.Logger.LogInformation(options.Get(RedisSetting.Prepare).ToString());
    });
    return options.CurrentValue;
});
```
また、設定データのセキュリティを向上させるために、設定オプションに検証を追加できます。設定エンティティクラスにDataAnnotationsを追加するか、カスタム検証を実装することができます。
```csharp
public record RedisSetting{
    public string? Host { get; set; }
    public int Port { get; set; }
    public string? Password { get; set; }
    [RegularExpression(@"^\d+ms$", ErrorMessage = "形式が正しくありません。msが必要です")]
    public string? ConnectionTimeOut { get; set; }
}
```
上記はConnectionTimeOutのDataAnnotations方式で、以下はカスタム検証モードです。
```csharp
builder.Services.AddOptions<RedisSetting>()
          .Bind(builder.Configuration.GetSection("RedisSetting"))
          .ValidateDataAnnotations()
          .Validate(config =>
          {
              if (config.Port < 1000)
              {
                  return false;
              }
              return true;
          }, "ポートは1000未満にできません");
```
検証がより複雑な場合は、以下のようにカスタムクラスを実装することができます：
```csharp
public class RedisSettingValidation : IValidateOptions<RedisSetting>{
    public RedisSetting _config { get; init; }
    public RedisSettingValidation(IConfiguration config)
    {
        _config = config.GetSection("RedisSetting")
            .Get<RedisSetting>();
    }
    public ValidateOptionsResult Validate(string name, RedisSetting options)
    {
        string? vor=null;
        var rx = new Regex(@"^((25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(25[0-5]|2[0-4]\d|[01]?\d\d?)$");
        if (options != null&&options.Host!=null)
        {
            var match = rx.Match(options.Host);
            if (string.IsNullOrEmpty(match.Value))
            {
                vor = $"{options.Host} 形式が正しくありません";
            }
            if (vor != null)
            {
                return ValidateOptionsResult.Fail(vor);
            }
        }
        return ValidateOptionsResult.Success;
    }
}
```
検証タイプを追加します。
```csharp
builder.Services.Configure<RedisSetting>(builder.Configuration.GetSection("RedisSetting"));
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IValidateOptions
                            <RedisSetting>, RedisSettingValidation>());
```

(Translated by GPT)

元のリンク：https://mp.weixin.qq.com/s?__biz=MzA3NDM1MzIyMQ==&mid=2247484826&idx=1&sn=cc490b8e77f53601dbeb4670bd6507d2&chksm=9f005ab0a877d3a61228e64c92eb1fd792d50f4f9adba943b9fb6d86816db508db68294112e6&token=2050451386&lang=zh_CN#rd