【C#】appsettings.jsonをコンソールで扱う

コンソールアプリでも App.congi に代わる新しい定義ファイルの形式である appsettings.json を使用する場合の設定と実装方法の紹介です。

ASP.NET Core および ASP.NET 5~6 であれば、IServiceCollection.Configure にセクション名を渡せば勝手に内容をオブジェクトにマッピングしてくれる機能が存在しますが、コンソールアプリで始めてしまうとそこらへんが存在しないので自分で読み書きする形になります。

したがって、定義ファイルにアクセスしやすいようヘルパークラスの実装例を紹介したいと思います。

確認環境

この記事は以下環境で確認しています。

  • VisualStudio 2022
  • .NET 6
  • コンソールプロジェクトが作成済み

パッケージの導入

「NuGet パッケージの管理」の「参照」から探してもいいですが、微妙にわかりにくかったので「パッケージマネージャー コンソール」からインストールします。

ツール > NuGet パッケージ マネージャー > パッケージ マネージャー コンソール

NuGetのページ: https://www.nuget.org/packages/Microsoft.Extensions.Configuration.Json/

以下コマンドを入力します。

PM>
Install-Package Microsoft.Extensions.Configuration.Json -Version 5.0.0

プロジェクトに間接的に必要なDLLが以下の通り追加されます。

// 結構大量に追加されます
Microsoft.Extensions.Configuration.Abstractions.dll
Microsoft.Extensions.Configuration.dll
Microsoft.Extensions.Configuration.FileExtensions.dll
Microsoft.Extensions.Configuration.Json.dll
Microsoft.Extensions.FileProviders.Abstractions.dll
Microsoft.Extensions.FileProviders.Physical.dll
Microsoft.Extensions.FileSystemGlobbing.dll
Microsoft.Extensions.Primitives.dll

定義ファイルの追加・編集

ソリューションエクスプローラー > 対象プロジェクトのコンテキストメニュー > 追加 > 新しい項目

「JSON ファイル」などの JSON 形式のファイルを選択し「appsetting.json」という名前で追加します。(実際は名前は何でも良いです)追加したらファイルのプロパティでビルドアクションを「コンテンツ」に変更し、出力先ディレクトリにコピーを「新しい場合上書きする」に変更します。

JSON ファイルの内容は以下の通りに編集します。

{
    "MySettings": {
        "Key1": "Key1",
        "Key2": "Key2",
        "InnerSettings": {
            "Key3": "Key3",
            "Key4": "Key4"
        }
    }
}

値の取得

以下のように書けばアクセスできるようになります。

using System.IO;
using Microsoft.Extensions.Configuration;

internal class AppMain
{
    private static void Main(string[] args)
    {
        // 定義ファイルの読み書き用のオブジェクトを初期化する
        IConfigurationRoot configuration = 
            new ConfigurationBuilder()
                .SetBasePath(Directory.GetCurrentDirectory())
                    .AddJsonFile("appsettings.json", true, true).Build();

        IConfigurationSection section = config.GetSection("MySettings");
        string key1 = section["Key1"];
        string key2 = section["Key2"];
        
        // 取得できない場合null、例外は発生しない
        string strN = section["KeyXX"];

        // 入れ子の場合「A:B」という風にコロンを挟む
        IConfigurationSection innerSection = config.GetSection("MySettings:InnerSettings");
        string key3 = innerSection["Key3"];
        string key4 = innerSection["Key4"];
    }
}

アクセス用のヘルパークラスの作成

上記のようなコードを毎回書くのも面倒なのでヘルパークラスを作成して手間を軽減したいと思います。

AppSettingJsonクラス

以前の XML 形式の「アプリケーション 構成ファイル」と似たような形でアクセスできるといいので以下のような「MySettings」セクションの下に key-value 形式で値が並んでいる定義ファイルを想定してヘルパークラスを作成したいと思います。

// appsetting.json
{
    "MySettings": {
        "key1": "stringstring",
        "key2": 10,
        "key3": 123.5698
    }
}

実装は以下の通りです。

using System;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Runtime.CompilerServices;
using Microsoft.Extensions.Configuration;

/// <summary>
/// appsettings.json のセクションを簡単に操作できるようにするためのヘルパークラス
/// </summary>
public class AppSettingsSection
{
    #region 見本...
    //
    // 見本:
    // 以下の通り記述されている appSettings.json 内のセクションへのアクセスを簡単にする
    //
    // appsetting.json
    // {
    //     "Setting": { ★ここ
    //         "key1": "stringstring",
    //         "key2": 10,
    //         "key3": 123.5698
    //     }
    // }
    //
    #endregion

    // 対象のセクション
    IConfigurationSection _section;

    //
    // Constructors
    // - - - - - - - - - - - - - - - - - - - -

    public AppSettingsSection(IConfigurationSection section)
    {
        _section = section ?? throw new ArgumentNullException(nameof(section));
    }

    //
    // Static Methods
    // - - - - - - - - - - - - - - - - - - - -

    public static AppSettingsSection CreateInstance(string filePath, string sectionName)
    {
        if (File.Exists(filePath))
            throw new FileNotFoundException($"File not found.", filePath);
        if (string.IsNullOrWhiteSpace(sectionName))
            throw new ArgumentException($"Require {sectionName}", nameof(sectionName));

        var config = new ConfigurationBuilder()
            .SetBasePath(Directory.GetCurrentDirectory())
                .AddJsonFile(filePath, true, true)
                    .Build();

        return new AppSettingsSection(GetSection(config, sectionName));
    }

    public static AppSettingsSection CreateInstance(IConfiguration config, string sectionName)
    {
        if (config == null)
            throw new ArgumentNullException(nameof(config));
        if (string.IsNullOrWhiteSpace(sectionName))
            throw new ArgumentException($"Requre {nameof(sectionName)}", nameof(sectionName));

        return new AppSettingsSection(GetSection(config, sectionName));
    }

    private static IConfigurationSection GetSection(IConfiguration config, string sectionName)
    {
        IConfigurationSection section =
            config.GetSection(sectionName) ??
            throw new KeyNotFoundException($"{sectionName} section is not found.");

        return section;
    }

    //
    // Public Methods
    // - - - - - - - - - - - - - - - - - - - -

    /// <summary>
    /// appSetting セクション内の指定したキーに対応する値を取得します。
    /// </summary>
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public string GetString(string key) => GetValue(key);

    // 以下基本型として値を取得するメソッド
    public bool GetBool(string key) => Convert.ToBoolean(GetValue(key));
    public byte GetByte(string key) => Convert.ToByte(GetValue(key));
    public sbyte GetSByte(string key) => Convert.ToSByte(GetValue(key));
    public char GetChar(string key) => Convert.ToChar(GetValue(key));
    public decimal GetDecimal(string key) => Convert.ToDecimal(GetValue(key));
    public double GetDouble(string key) => Convert.ToDouble(GetValue(key));
    public float GetFloat(string key) => Convert.ToSingle(GetValue(key));
    public int GetInt(string key) => Convert.ToInt32(GetValue(key));
    public uint GetUInt(string key) => Convert.ToUInt32(GetValue(key));
    public long GetLong(string key) => Convert.ToInt64(GetValue(key));
    public ulong GetULong(string key) => Convert.ToUInt64(GetValue(key));
    public short GetShort(string key) => Convert.ToInt16(GetValue(key));
    public ushort GetUShort(string key) => Convert.ToUInt16(GetValue(key));
    // 時刻で取得
    public DateTime GetDateTime(string key, string format)
        => DateTime.ParseExact(GetString(key), format, CultureInfo.InvariantCulture);
    // 時間で取得
    public TimeSpan GetTimeSpan(string key, Func<double, TimeSpan> conv)
        => conv(GetDouble(key));

    //
    // Not-Public Methods
    // - - - - - - - - - - - - - - - - - - - -

    // 対象のセクションの値を取得する
    private string GetValue(string key)
    {
        return _section[key] ?? throw new KeyNotFoundException($"Key not found. key={key}");
    }
}

関連記事

takap-tech.com

takap-tech.com