【.NET】設定ファイル(例 appsettings.json)をクラスにマッピングする

# はじめに
.NET Framework で作成した自社製ライブラリーを、.NET 7 に移植することにした。
.NET Framework では設定ファイルは、App.config (XML 形式)を使用していたが、.NET(旧名 .NET Core)からは、appsettings.json(JSON 形式)が推奨されている。

前回は、コメントに関する記事を書きました。

https://qiita.com/yaju/items/35d94a20e8e3e8ca268c

今回はマッピングについて書いていきます。要はJSONのデシリアライズですね。
この記事はあくまで、`IConfiguration` を使用することを前提としています。

# マッピング
設定ファイルの `appsettings.json` を用意します。

```json:appsettings.json
{
  "appSettings": {
    "foo": "Bob",
    "bar": 42
  }
}
```

マッピング用のクラスを用意します。

```cs:Settings.cs
public class Settings
{
    public string Foo { get; set; } = "";
    public int Bar { get; set; }
}
```

設定ファイルの `appsettings.json` を読み込みます。

```cs:Program.cs
var config = new ConfigurationBuilder()
            .AddJsonFile("appsettings.json", false)
            .Build()
```

## Getが見つからない

```cs:Program.cs
var settings = config.GetSection("appSettings").Get<Settings>();
```

ネット上でマッピングするのに、`Get<T>()`　や `Bind` などを使用した例が記載されているがインテリセンスに出てこないのである。よく見れば参考した記事はどれも `ASP.NET Core` の記事ばかり。コンソールやクラスライブラリー等では使えないの？

「C# Configuration get not found」って英語で検索したトップ記事に回答があった。

>Install the NuGet package:
Microsoft.Extensions.Configuration.Binder
>https://stackoverflow.com/questions/36694099/iconfiguration-does-not-contain-a-definition-for-get

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/109866fd-0db5-909f-3feb-3091b0d0fcd1.png)

## 結果
インストールすることで、`Get<T>()`　や `Bind` が使用できるようになった。
Setting クラスにマッピングできています。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/14053/bd7d8e07-21f5-af84-e2d0-b2f590116d7f.png)

### Get<T>() を使用

```cs:Program.cs
var settings = config.GetSection("appSettings").Get<Settings>();
```

### Bind を使用

```cs:Program.cs
var settings = new Settings();
config.Bind("appSettings", settings);
```

# マッピング(属性指定)
下記サイトにあるようにオブジェクトをJSONにシリアライズする際には、`DataMember`属性を付与し、`Name`に JSONのキーを指定することができた。また`System.Text.Json`の場合なら、`JsonPropertyName`属性となる。

https://dev.classmethod.jp/articles/c-sharp-json/

`DataMember`属性や`JsonPropertyName`属性を指定しても駄目だったので、 `Configuration`で同じことが出来ないか検索してみた。

https://github.com/dotnet/runtime/issues/36010

[ConfigurationKeyNameAttribute Class](https://learn.microsoft.com/en-us/dotnet/api/microsoft.extensions.configuration.configurationkeynameattribute?view=dotnet-plat-ext-7.0)が、.NET 6 からサポートされ、`ConfigurationKeyName` が使用できるようになりました。

:::note warn
IgnoreDataMember属性 は見当たらないので、無視は出来なそう。
:::

```cs
[ConfigurationKeyName(Name = "Named_Property")]
public string NamedProperty { get; set; }
```

## サンプルソース
複数のデータベースを共通化するクラスを作成するため、接続情報として`database.json`を設定した。

```jsonc:database.json
{
  "dbSettings":{
    "dbType":"SQLServer",
    "connection":[
        {
            // 接続名
            "@name":"PostgreSQL",
            // 接続名
            "connectionString":"Server=localhost;Port=5432;User Id=test;Password=P@ssw0rd;Database=test;CommandTimeout=600;"
        },
        {
            // 接続名
            "@name":"SQLServer",
            // 接続名
            "connectionString":"Data Source=localhost;Initial Catalog=ServerWatch;User ID=Test;Password=P@ssw0rd;TrustServerCertificate=True;"
        },
        {
            // 接続名
            "@name":"Oracle",
            // 接続名
            "connectionString":"Data Source=(DESCRIPTION=(ADDRESS=(PROTOCOL=tcp)(HOST=localhost)(PORT=1521))(CONNECT_DATA=(SERVICE_NAME=TEST.DB))); User Id=test; Password=P@ssw0rd"
        }
    ]
  }
}
```

プロパティ名とキー名が不一致でも、`ConfigurationKeyName` 属性を使用してマッピングするキーを指定できます。

```cs:DBSettingData.cs
public class DBSettingData
{
    public string DBType { get; set; } = "";
    [ConfigurationKeyName("connection")]
    public DbConnections DBConnections { get; set; } = new DbConnections();
}

public class DbConnections : List<DbConnection>
{
    private int IndexOf(string name)
    {
        for (int i = 0; i < Count; i++)
        {
            DbConnection con = base[i];
            if (con.Name == name)
                return i;
        }
        return -1;
    }
    public DbConnection this[string name]
    {
        get
        {
            foreach (DbConnection con in this)
            {
                if (con.Name == name)
                    return con;
            }
            return null;
        }
        set
        {
            int i = IndexOf(name);
            if (i == -1)
                Add(value);
            else
                base[i] = value;
        }
    }
}

public class DbConnection
{
    [ConfigurationKeyName("@name")]
    public string Name { get; set; } = "";
    public string ConnectionString { get; set; } = "";
}
```

データベース設定情報 `database.json` を読み込みます。
設定情報の`DBType：SQLServer`なので、`ConnectionString`には、SQLServerの接続文字列がセットされます。

```cs:DataBaseInfo.cs
using Microsoft.Extensions.Configuration;

public class DatabaseInfo
{
    // データベース種類定義
    public const string DB_TYPE_POSTGRESQL = "PostgreSQL";
    public const string DB_TYPE_SQLSERVERL = "SQLServer";
    public const string DB_TYPE_ORACLE = "Oracle";
    public const string DB_INFO_JSON_FILENAME = "database.json";
    public const string DB_INFO_DEFAULT_SECTION = "dbSettings";
    // データベースの種類
    public string DBType { get; set; } = "";
    // データベースの接続情報
    public string ConnectionString { get; set; }
    /// <summary>
    /// コンストラクタ
    /// </summary>
    public DatabaseInfo(string path)
    {
        SetConnectionInfo(path,  DB_INFO_DEFAULT_SECTION);
    }
    /// <summary>
    /// DB情報をセットします。
    /// </summary>
    /// <param name="path">データベース接続情報パス名</param>
    /// <param name="section">セクション名</param>
    public void SetConnectionInfo(string path, string section)
    {
        try
        {
            var dbs = new DBSettingData();
            var config = new ConfigurationBuilder()
                            .AddJsonFile(path)
                            .Build();

            config.Bind(section, dbs);
            // データベースの種類セット
            DBType = dbs.DBType;
            // 接続文字列の情報セット
            ConnectionString = dbs.DBConnections[DBType].ConnectionString;
        }
        catch
        {
            throw;
        }
    }
}
```

# 最後に
まだここらへんに関して検索する限りだと、日本では情報がなかなか見当たらないですね。
なので、検索キーを英語にして検索するなど工夫する必要があります。

`ConfigurationKeyName` など一旦キーワードが見つかれば情報が出て来たりしますが、自分自身で発信していくしかないですね。

`IConfiguration`はJSON以外にも環境変数やコマンドラインパラメータで指定した設定情報などが読み込める。

https://blog.ecbeing.tech/entry/2020/03/16/114500

単にJSONのみなら`System.Text.Json`を使えばいい気もする。属性で色々出来そうだし。

https://iwasiman.hatenablog.com/entry/20210614-CSharp-json