2026年7月14日火曜日

WPF/C#で jsonを使ったAppSettingsの実装

1.パッケージ追加

microsoft.extensions.configuration.json

JSON形式の設定ファイル(appsettings.jsonなど)を読み込むための構成プロバイダー

パッケージのインストール

Visual Studioツール>NuGetマネージャー>パッケージ マネージャー コンソール以下を実行
PM> Install-Package Microsoft.Extensions.Configuration.Json

microsoft.extensions.hosting

依存関係の注入 (DI)構成管理 (Configuration)などを統合的に管理するためのライブラリ(汎用ホスト)

パッケージのインストール
Visual Studioのツール>NuGetマネージャー>ソリューションのNuGet パッケージの管理
Microsoft.Extensions.Hostingを検索>選択>プロジェクトにチェック>インストール

2.appsettiongs.json追加
 (ファイル名は他と干渉しなければ任意)

・ソリューションエクスプローラー>プロジェクト選択>追加>JavaScript JSON 構成ファイル>名前入力>追加

・追加されたappsettiongs.jsonを右クリック>プロパティー>ビルドアクション>ドロップダウンで[コンテンツ]に変更

・出力ディレクトリにコピー>ドロップダウンで[新しい場合はコピーする]に変更

・JSONの例{

  "AppName": "MyApp",
  "MainWin": {
    "Title": "MainWindow", "Width": 800, "Height": 500, "Left": -877, "Top": 202 }

3.Jsonに対応するクラス追加

appsettingsをいくつかに分割する前提で、全体を管理するクラスを作る。名前は任意。
分割した個々の要素(Section)に対応するクラスを作り、そのインスタンスをプロパティーとして設定する。

    public class AppSettings
    {
public string AppName { get; set; } = string.Empty;
public MainWinSettings MainWin { get; set; } = new(); }

appsettings.jsonのセクション名と一致する名前でプロパティーを作る。
上記の例ではappsettings.jsonの中に”MainWin
"というセクションがある。

4.JSONとクラスの依存関係設定

JSONとクラスの依存関係設定

・プロジェクトダブルクリック>csprojに次のようなtemGroup追加

 	<ItemGroup>
<Compile Update="AppSettings.cs">
<DependentUpon>appsettings.json</DependentUpon>
</Compile>
</ItemGroup>

この設定を行うことで、ビルド時に実行ファイル(.exe)と同じフォルダにJSONファイルが出力され、DIコンテナ(Microsoft.Extensions.Configuration など)から正しく読み込めるようになる。

App.xaml.csのようにAppコンストラクタでServiceを構成することで、入出力先を変更できる。

5.App.xaml変更

StartupUriの設定を削除。

    StartupUri="MainWindow.xaml">

6.App.xaml.cs変更

コンストラクタでDIから受け取るServiceを登録

        public static IHost? AppHost { get; private set; }
        /// <summary>
        /// ホスト(Host)を構築し、設定ファイルとウインドウをDIコンテナに登録します。
        /// </summary>
        public App()
        {
            AppHost = Host.CreateDefaultBuilder()
                .ConfigureAppConfiguration((context, config) =>
                {
                    // 1. 環境(Debug/Release)に応じて読み込み元を決定
                    string jsonFileName = AppSettings.FileName; // "appsettings.json";
                    string targetPath = StorageService.GetSettingsFilePath(jsonFileName, true);
                    config.AddJsonFile(targetPath, optional: true, reloadOnChange: true);
                })
                .ConfigureServices((context, services) =>
                {
                    services.AddSingleton<StorageService>();
                    services.Configure<AppSettings>(context.Configuration);

                    var rootSettings = context.Configuration.Get<AppSettings>() ?? new AppSettings();
                    services.PostConfigure<AppSettings>(settings => { settings.PostConfigure(); });
                    // MainWindow や他の設定クラスに渡す AppSettings は、Options パターン(IOptions)を経由して取得したものをシングルトン登録する
                    services.AddSingleton<AppSettings>(provider =>
                    {
                        // ここで .Value を要求した瞬間に、AppSettingsが読み込まれ、PostConfigureが実行される
                        var options = provider.GetRequiredService<Microsoft.Extensions.Options.IOptions<AppSettings>>();
                        return options.Value;
                    });
                    // 画面をDIコンテナに登録
                    services.AddTransient<MainWindow>();
                })
                .Build();
        }
        protected override async void OnStartup(StartupEventArgs e)
        {
            // ホストの起動
            await AppHost.StartAsync();

            // DIコンテナからMainWindowを取得(自動的にAppSettingsが注入される)
            var mainWindow = AppHost.Services.GetRequiredService<MainWindow>();
            mainWindow.Show();

            base.OnStartup(e);
        }

        protected override async void OnExit(ExitEventArgs e)
        {
            // アプリ終了時にホストをクリーンアップ
if (AppHost != null)
{
await AppHost.StopAsync();
}
AppHost.Dispose(); base.OnExit(e); }

7.MainWindow.xaml.cs変更

コンストラクタでDIから登録したServiceを受け取る。
MainWindowの設定に関わるものがある場合は
InitializeComponentの前に呼ぶ。

        public MainWindow(AppSettings settings)
        {
            this.DataContext = settings.MainWin;
InitializeComponent(); }


8.StorageServiceの実装例

        const string brandName = "MyComp";
        const string appName = "MyApp";

         /// <summary>
        /// AppData/Local/MyComp/MyApp 配下の指定されたファイル名のパスを返す。
        /// </summary>
        /// <param name="fileName"></param>
        /// <param name="copyIfNotExistsInAppData"></param>
        /// <returns></returns>
        public static string GetSettingsFilePath(string fileName, bool copyIfNotExistsInAppData)
        {
            #if DEBUG
                    // Debug時はexeと同じフォルダのファイルを返す
                    return Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
            #else
            //Release(インストール後)はAppData配下の専用フォルダを返す
            string appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData); // Local配下に作る場合はこちら
            //string appDataFolder = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData); // Roaming配下に作る場合はこちら
            appDataFolder = Path.Combine((Path.Combine(appDataFolder, Path.Combine(brandName, appName))));
            // フォルダがなければ自動作成
            if (!Directory.Exists(appDataFolder))
            {
                Directory.CreateDirectory(appDataFolder);
            }
            string targetPath = Path.Combine(appDataFolder, fileName);
            // 初回起動時など、AppData にファイルがなければ exe 横からコピーする
            if (!File.Exists(targetPath) && copyIfNotExistsInAppData)
            {
                string sourcePath = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, fileName);
                if (File.Exists(sourcePath))
                {
                    File.Copy(sourcePath, targetPath);
                }
            }
            return targetPath;
            #endif
        }

単純に保存すると日本語がのエスケープ付きバイナリ値(UTF16?)になる。次のようにするとUTF8で保存できる。

        public void SaveJson<T>(T data, string fileName){
            var options = new JsonSerializerOptions
            {
                Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping,
                WriteIndented = true
            };
            string filePath = GetSettingsFilePath(fileName, false);
            var json = JsonSerializer.Serialize(data, options);
            File.WriteAllText(filePath, json, Encoding.UTF8);
        }

0 件のコメント:

コメントを投稿