Generic Host の DI が appsettings.json を統べる

2022 年最後の技術系エントリは Generic Host で appsettings.json 等に置いた設定情報(Configuration)と Generic Host 標準の DI コンテナである Microsoft.Extensions.DependencyInjection(以下、MS.E.DI)を組み合わせる方法を紹介します。

尚、このエントリは Visual Studio 2022 Community Edition で .NET 6 以降 と C# を使用するエントリなので、C# での基本的なコーディング知識を持っている人が対象です。

Generic Host の全般的な内容については、以前に書いた『.NET6 で Generic Host を使った常駐アプリ』で紹介していますが、アプリの設定情報を取り扱う方法等に関しては、ほとんど触れませんでした。

上のエントリで紹介しているサンプルアプリは設定情報が 1 つしかなかったので、サンプルアプリに必要最低限の情報しか紹介していません(知りません)でしたが、少し触って、ある程度はネタが貯まったので新規エントリとしてまとめました。

Generic Host の構成とは?

エントリのタイトルに『appsettings.json』を入れていますが、元々は『設定情報』にしていました。『設定情報』ですが、Microsoft 的には【構成】と和訳しているようで、【構成 – .NET | Microsoft Learn】と言うタイトルのページで紹介されています。このページでは Generic Host を利用してアプリ外部に置いた構成情報を一元的に取り扱う方法が紹介されています。

管理人は『構成』と書かれてもピンと来ませんでしたが、英語だと【Configuration】なので、【構成】としか和訳できないのかもしれません…

Generic Host の Configuration は、かなり範囲が広くて【構成 – .NET | Microsoft Learn】には以下のソースが対象と書かれています。

  • appsettings.json 等の設定ファイル
  • 環境変数
  • Azure Key Vault
  • Azure App Configuration
  • コマンドライン引数
  • インストール済みまたは作成済みのカスタムプロバイダー
  • ディレクトリ ファイル(xml や ini ファイル等?)
  • メモリ内 .NET オブジェクト
  • サードパーティ製プロバイダー

環境変数やコマンドライン引数等も対象なので『設定情報』と言うよりは、単純に【Configuration】と読み替えた方が分かりやすいかもしれません。

上に書いた通り、Generic Host で取り扱えるソースは多種多様過ぎて全てを紹介するには 1 エントリには書ききれませんし、何より管理人自身全ては把握できていません。そのため、このエントリでは使用する機会が多いと思われる設定ファイルの appsettings.json だけに絞って紹介します

appsettings.json の使用方法

.NET6 で Generic Host を使った常駐アプリ』でも紹介していますが、Visual Studio 2022 に含まれている『ワーカー サービス テンプレート』からプロジェクトを新規作成すると、fig. 1 のように appsettings.json が 2 つ含まれた状態のプロジェクトが作成されます。

fig.1 ワーカー サービステンプレートから作成した直後のソリューション

appsettings.Development.json は開発用の設定ファイルで、Generic Host の CreateDefaultBuilder メソッド内では appsettings.json の後で appsettings.Development.json が読み込まれるようになっています。Generic Host の Configuration は後から読み込んだ情報を上書きするので、DB の接続文字列のように本番環境と開発環境で別の値を指定したい場合には便利です。

appsettings.Development.json の【.Development】の部分はプロジェクトの環境変数に設定した値で切り替えられるようになっていて、VS 2022 の場合は以下の手順で設定値まで辿り着くことができます。

まずは、プロジェクトのプロパティを開いて、fig. 2 の【デバッグ – 全般 – デバッグ起動プロファイル UI を開く】をクリックします。

fig.2 プロジェクトのプロパティ – デバッグセクション

すると fig. 3 のダイアログが開きます。

fig.3 プロファイルの起動ダイアログ

fig. 3 の赤枠で囲んだ環境変数の値で置き換えた appsettings{.環境変数値}.json が読み込まれるようになっています。

設定値の DI

まずは、appsettings.json に追加した単純なスカラー値を DI する方法を紹介します。『.NET6 で Generic Host を使った常駐アプリ』では DI ではなく、単純に値を取得する方法しか紹介しませんでしたが、DI も可能です

src. 1 のように appsettings.json に設定値を追加します。

2 行目のように【TestConfig】と言うキーで【値:foo】を追加します。ワーカーサービステンプレートからプロジェクトを作成すると、src. 2 のようなトップレベルステートメントを使用したエントリポイントが自動的に作成されています。

src. 2 の 3 行目で呼び出している CreateDefaultBuilder(テンプレートから自動生成)の内部で、暗黙的に IConfiguration も DI コンテナへ登録されるので、何もコードを追加しなくても src. 3 のように DI できます。

11 行目のようにコンストラクタのパラメータに IConfiguration を追加するだけで DI できます。DI された IConfiguration から GetValue すると、fig. 4 の赤枠で囲んだ箇所のように、appsettings.json に追加した値が取得できます。

fig.4 DI した IConfiguration から設定値を取得

DI や DI コンテナについての基本的な内容は、【Prism の DI コンテナらは Ioc 上に歌う【step: 4 .NET Core WPF Prism MVVM 入門 2020】】で詳しく紹介しています。

上のエントリにも書きましたが、DI は DI コンテナ(ここでは MS.E.DI)に登録されたクラスであればどこにでも DI できます。このエントリのサンプルには出てきませんが、アプリケーション層や DataAccess 層と言ったいわゆる Model に属するクラスにも IConfiguration を DI できます

Generic Host 標準の DI コンテナ(MS.E.DI)の使い方については【Generic Host の DI たちが依存性を救うようです】で詳しく紹介しているので、そちらを読んでください。

Generic Host は今後、WPF や Windows Form、コンソールアプリ等で標準的に使用されるフレームワークになりそうですし、Generic Host の使用を前提としたライブラリも多くリリースされているので、覚えておいて損は無いと思います。

設定値をクラスにマッピング

前章では設定値がバインドされた IConfiguration を DI して値を直接取得する方法を紹介しました。小規模なアプリや設定項目が少ない場合は前章のような方法でも困らないとは思いますが、設定値をマッピングしたクラスを DI できる方が便利な場合も多いと思います。

以降では設定値として、DB の接続文字列を例に紹介します。『DB なんか使わねーから必要ねーんだよ!』と言う方も当然居ると思いますが、次に書く予定のエントリに都合が良いとか、DB の接続文字列は基本的に外部ファイルに置くことが多いとか、説明する例として取り上げやすいと言う理由なので、ご了承ください。

DB の接続文字列はあくまで例なので、単なる設定値として読み替えてください。基本的に DB 寄りな内容にはなっていないと思っています。

まずは、src. 1 の appsettings.json を src. 4 のように変更して SQLite のデータベースファイル名を追加します。

併せて、src. 5 のようなレコード型も作成します。

src. 5 はレコード型にしていますが、当然、クラスでも構いません。構成情報をマッピングするクラスは【オプションのパターン – .NET | Microsoft Learn】で以下のような制限があると書かれています。

オプション パターンを使用する場合、オプション クラスは次のとおりです。

  • パラメーターなしのパブリック コンストラクターを持った非抽象でなければなりません
  • バインドする読み取り/書き込みのパブリック プロパティが含まれます (フィールドはバインド “されません”)
.NET でのオプション パターン

レコード型独特の public record DatabaseSettings(string ConnectString) のような宣言にしていないのは、上で引用した制限の通りパラメータ無しのコンストラクタが必要だからです。レコード型については『レコード型 – C# によるプログラミング入門 | ++C++; // 未確認飛行 C』等を見てください。

又、src. 5 ではレコード型(クラス)名を appsettings.json のセクション名と合わせていますが、セクション名 = クラス名にする事は必須ではありません。必須ではありませんが、nameof 等が使えるので、一致している方が取り回ししやすいとは思います。クラス名は任意ですが、プロパティ名は appsettings.json のキー名(src. 4 の 3 行目)と一致していないとマッピングできません

後は、src. 6 のように Generic Host の初期化部分(アプリのエントリポイント)にマッピング処理を追加します。

src. 6 のポイントは、4 行目の ConfigureServices から呼び出されるラムダ式の部分です。プロジェクトをテンプレートから作成した直後は【services パラメータ】が 1 つだけ渡されるラムダ式でしたが、HostBuilderContext も渡されるオーバーロードに変更しています。

ConfigureServices メソッド内で Configuration を扱う場合は 6行目のように HostBuilderContext.Configuration が必要になるので、ConfigureServices はパラメータが 2 つ渡されるオーバーロードへの変更が必須です。src. 6 の方法以外にも、もっとスマートな方法があるのかもしれませんが、調べきれませんでした。

6 行目のように IConfiguration が取得できれば、後は 8 行目の Configure と GetSection 2 つのメソッドでセクション丸ごとを型パラメータに指定したクラスにマッピングできます。サンプルの設定項目は 1 つ(ConnectString のみ)だけですが、複数の設定項目(プロパティ)があっても、appsettings.json のキー名と一致する全プロパティにマッピングできます。src. 6 8 行目のまま変更は不要です

ConfigureServices メソッド内で構成情報をマッピングすると、マッピングしたクラスは DI コンテナに登録されるので、src. 7 のようにマッピングしたクラスを DI できるようになります。

構成情報を DI する時は IConfiguration を指定しましたが、設定情報をマッピングしたクラスは、src. 7 のように IOptions<マッピングした型> を指定すると DI されます。DI されるのは IOptions ですが、13 行目のように Value プロパティからマッピングしたクラスそのものを取得できます。

少し見にくいかもしれませんが、実行すると fig. 5 のように appsettings.json の設定値が取得できています。

fig.5 DI された IOptions を表示

.NET Framework の時代は別プロジェクトに分けた DLL に設定値を渡すのに、画面から値を引き継いでいく的な手順が必要だったと思いますが、フレームワークに Generic Host を使用して上のようにマッピングすれば、設定情報もアプリケーション層や DataAccess 層等へ一発で渡せるようになるので、非常に便利になったと思います。

このようにオプションパターンを使用すると、appsettings.json の設定値だけでなく 1 番最初で紹介した色々なソースから取得した構成値も、読み込み時の指定は少し違うと思いますが、統一した方法で取り扱えるはずです(未確認)。

同一型で複数の値が設定された構成情報を取り扱う

ここからは少し応用編になります。ここまでは 1 つの型(ここでは DatabaseSettings)に対して 1 つの設定値の塊をマッピングする方法を紹介してきましたが、1 つの型に対して、複数パターンの設定値を取り扱いたい場合もあると思います。例えば、アプリ内で複数の DB にアクセスする必要があるような場合です。

Generic Host より前の時代では色々な方法で、取得する値を切り替えていたと思いますが、Generic Host のオプションパターンは Dictionary っぽい設定値も取り扱えるようになっています。

src. 4 で紹介した appsettings.json を src. 8 のように変更します。

6 ~ 8 行目のように SQL Server の LocalDB に接続するための設定を追加して、3 行目には SQLite 用の設定値と分かるよう設定値に名前を追加します。src. 8 のように同一セクション内に、識別子(src. 8 では “Sqlite” や “SqlServer”)を付けると【名前付きオプション】として取得出来るようになります。

マッピングする DatabaseSettings に src. 9 のような定数値を追加します。

定数を追加しただけなので変更は必須ではありません。

名前付きオプションは src. 10 のようにマッピングします。

src. 6 とほとんど同じで、第 1 パラメータに名前を指定する Configure のオーバーロードに変更するだけです。名前付きオプションを DI するための DictionaryOption クラスも 13 行目に追加しています。

DictionaryOption は src. 11 のように Worker クラスに DI してインスタンスが生成されるようにしておきます。

そして、名前付きオプションとしてマッピングしたクラスは src. 12 のように DI できます。

src. 7 では IOptions<マッピングしたクラス> で DI しましたが、名前付きオプションは 10 行目のように IOptionsMonitor<マッピングしたクラス> で DI します。Generic Host オプションパターンには複数のインタフェースが用意されていて【オプションのパターン – .NET | Microsoft Learn – オプションのインターフェイス】には以下のような説明があります。

インタフェース 内容
IOptions<TOptions> シングルトンで DI コンテナに登録される。
名前付きオプションは未サポート。
IOptionsSnapshot<TOptions> AddScoped で DI コンテナに登録される。
名前付きオプションもサポート
IOptionsMonitor<TOptions> シングルトンで DI コンテナに登録され、値の変更がモニタ出来る。
名前付きオプションもサポート

つまり、名前付きオプションをサポートしているのは IOptionsSnapshot と IOptionsMonitor なので、名前付きオプションを DI したい場合に IOptions は指定できません

そして、AddScoped で登録されたクラスに DI する場合は IOptionsSnapshot を指定する必要があり、IOptions や IOptionsMonitor はどのスコープで登録したクラスにも DI できます。実際、src. 12 の 10 行目を IOptionsSnapshot に変更すると、実行時に例外が Throw されます。

MS.E.DI にクラスを登録する Add ~ メソッドについては【Generic Host の DI たちが依存性を救うようです】で詳しく紹介しているのでそちらを読んでください。

src. 12 の実行結果が fig. 6 です。

fig. 6 名前付きオプションを DI

appsettings.json のキー名(src. 12 では DatabaseSettings.SqliteDatabaseSettings.SqlServer )を指定して設定値が取得されている事が分かると思います。

取得した構成情報の加工

ここまではあえてスルーしてきましたが、src. 8 の 4 行目の設定値では SQLite に接続できません。DB の接続文字列は本来、src. 8 の 7 行目のように【Data Source=】から始まる文字列になっている必要がありますが、4 行目はファイル名しか設定していません。

加えて、SQLite はファイルベースの DB エンジンなので、SQLite への接続はファイルへのフルパスが必要です。ですが、アプリ内で使用するファイルは設定ファイルにフルパスを指定できない場合も多く、実行時にアセンブリがあるフォルダを取得してアクセスしたいファイルパスを作る事は多いと思います。こう言うパターンは SQLite の DB ファイルに限った話ではなく、他にも当てはまる場面はあるはずです。

ここで紹介しているサンプルアプリも SQLite の DB ファイルは実行ファイル(exe)と同じフォルダに置く前提なので、実行時に DB ファイルへのフルパスを生成する必要があり、生成したフルパスを DB の接続文字列に変換する必要があります。

Generic Host のオプションパターンでは src. 13 のような方法でマッピングされた設定値を加工する事ができます

11 行目のように PostConfigure(第 1 パラメータに名前を指定するオーバーロード)を呼び出すと、10 行目でマッピングした構成情報(src. 13 では DatabaseSettings)がラムダ式に渡されるので、15 行目のように実行時の情報を付加した値に加工できます。

後は、ここまで紹介したように IOptions~ を DI するだけで、fig. 7 のように加工後の値が取得できることが確認できます。

fig.7 加工後の設定値を取得

赤枠で囲んだパスの部分はモザイク加工していますが、fig. 6 とは違って【data Source=】から始まる文字列になっている事が確認できます。src. 13 では名前付きオプションを処理するために、第 1 パラメータに名前を指定する PostConfigure を呼び出していますが、名前なしオプションについても同様に PostConfigure で加工できます

と言う訳で、ここまでで appsettings.json の設定値をマッピングして、加工して、DI する例を紹介してきました。次項では、ここまで紹介してきた設定情報と、以前に書いた【Generic Host の DI たちが依存性を救うようです】で紹介した実装クラスの登録パターンを組み合わせて利用する方法を紹介します。

以降では、上のエントリで書いた MS.E.DI の使い方は理解している前提の内容になっているので、MS.E.DI の実装クラスの登録方法を知らない場合や、上のエントリをまだ読んでいただいてない場合は、上のエントリを先に読んでいただく方が良いと思います。

DI コンテナからクラスを取得(Resolve)する

ここまでは主に、クラスを MS.E.DI に登録する方法を紹介してきましたが、当然、登録したクラスを取り出す事も出来ます。DI コンテナに登録したクラスはコンストラクタへの DI を通して取得するのが基本ですが、src. 14 のように IServiceProvider を DI して取得する事も可能です。

IServiceProvider は、おそらくエントリポイントで呼び出している CreateDefaultBuilder 内部で登録されているはず(調べきれませんでした…)なので、2章の『設定値の DI』で紹介した IConfiguration と同じく自分で登録しなくても DI できます

実行すると fig. 8 のように 18 行目で Console.WriteLine した内容が出力されます。

fig.8 IServiceProvider を DI して Resolve した結果

ここでは Worker に DI した IServiceProvider から src. 12 で紹介した DictionaryOption を 17 行目のように GetRequiredService メソッドで Resolve して、クラス名を表示する MyName プロパティの値を出力しているだけなので、DI した IServiceProvider から登録したクラスのインスタンスが取得できている事が分かると思います。

但し、src. 14 のような方法は【Prism の DI コンテナらは Ioc 上に歌う – ServiceLocator パターン】でも紹介した【ServiceLocator パターン】と呼ばれる手法で、基本的には非推奨の方法になります。

コンストラクタへ DI したクラスは、生存期間が長くなるから困る…と言う場合でも、MS.E.DI には【Generic Host の DI たちが依存性を救うようです – AddScoped するクラスの生存期間】でも紹介した AddScoped で登録したクラスをメソッドスコープで生成する方法が用意されているので、src. 14 のような方法を使用する必要は無いと思います。

とは言え、IServiceProvider はユニットテストを書く場合や、以降で紹介する実装クラスの登録時等で使用する事があるので、MS.E.DI に登録したクラスを取り出す方法も知っておいた方が良いと思います。

IServiceProvider からクラスを取り出すメソッドは以下の 2 つが用意されています。

メソッド 内容
GetService DI コンテナに未登録のクラスを指定すると null が返ります
GetRequiredService DI コンテナに未登録のクラスを指定すると InvalidOperationExceptionThrow されます。

どちらも登録済みのクラスのインスタンスを取得するためのメソッドですが、両者は上に書いた通り、null が返るか、例外が Throw されるかの違いです。ユニットテストを書く場合は Assert で指定するメソッドが変わるので、状況に合わせて使い分けることになります。

IServiceProvider から登録済みのクラスを取得する方法を紹介したので、次項では、MS.E.DI に 実装クラスを登録する際に設定情報を利用する方法を紹介します。

MS.E.DI に登録するクラスの生成時に設定値を反映する

ここまでは Generic Host で設定値を取り扱う方法を紹介してきましたが、実は、本項の内容がこのエントリを書いた本当の目的です。

ここまで例に挙げてきた DB の接続文字列ですが、本来は SqliteConnection 等のコンストラクタに渡す値です。Open(Async) のパラメータに指定することもできますが、管理人の感覚的には SqliteConnection のコンストラクタのパラメータに指定する事の方が多いだろうと思っています。

ここまで、appsettings.json 等に書いた設定値を取得する方法は紹介しましたが、SqliteConnection 等のようにバイナリでパッケージングされているクラスには当然 DI できません。ですが、src. 15 のようにクラス生成時の挙動をカスタマイズする事で可能になります。

そして、21 ~ 30 行目で MS.E.DI に登録した SQLiteConnection を src. 16 のように DictionaryOption へ DI します。

実行すると fig. 9 のように src. 15 で設定した接続文字列が Console.WriteLine されている事が確認できます。

fig.9: 実装クラスの登録時に設定した接続文字列の出力

appsettings.json は src. 8 から変更していませんが、DI された SQLiteConnection に接続文字列が設定されていて、src. 15 12 行目の PostConfigure で設定した【data Source=】から始まる文字列になっている事も確認できます。

前項で非推奨の IServiceProvider からインスタンスを取得する方法を紹介したのは、src. 15 の 23 行目のように使用する事があるからです。そして、src. 15 のように実装クラスを指定する際にラムダ式を使用すると、コメントアウトしている 26 ~ 27 行目のようにプロパティを設定したり、メソッドを呼び出した後のインスタンスを DI する事もできます。

つまり、DictionaryOption が DataAccess 系のクラスだとすると、DI された DbConnection は即、使用可能な状態で受け取る事ができます。(Open 等は DictionaryOption 側で呼び出す方が良いと思いますが…)

又、【Generic Host の DI たちが依存性を救うようです】でも書きましたが、src. 15 の 21 ~ 30 行目のラムダ式は、登録時ではなくインスタンスが生成されるタイミングで呼び出されるデリゲートです。

上のエントリでは、呼び出しタイミングをサンプルプログラムで確認しているので、まだの方は読んでいただけるとありがたいです。

まあ、src. 15 の方法がベストプラクティスでは無いと思いますし、前章までで紹介した IOptionsMonitor 等で設定情報を DI する方法のどちらを採っても構わないと思いますが、管理人個人的には本項で紹介した方法の方が使い易そうな好みの手法です。

ここまでのソースはいつも通り、GitHub リポジトリ (GenericHostJsonConf.sln)に上げています。

おまけ

ここまでは Generic Host の MS.E.DIappsettings.json 等から取得する設定値を組み合わせて使用する方法を紹介してきましたが、『おまけ』として設定値を更新して保存する方法も紹介します。Generic Host のオプションパターンはソースコードから設定値を更新して永続化する機能はサポートされていませんが、設定ファイルが JSON フォーマットなので、シリアライズ処理を自作することで永続化できます。

と言っても、これから紹介する方法は管理人が考えた訳ではなく、【Saving To appsettings.json – Microsoft Q&A】で海外のイケメンニキが紹介している方法を少しアレンジしただけです。じぇりーさんありがとう!

上で紹介されているサンプルは ASP.NET CoreWebHost を使用したサンプルですが、Generic Host は WebHost から Web に限定した機能を省いたものなので、ほとんどそのまま使用できます。Web アプリで設定ファイルを更新する…?ような用途は管理ページくらいでしか使用する機会は無いと思いますが、このエントリで紹介しているデスクトップアプリでは普通に欲しい機能だと思います。

そして、以降で紹介するサンプルですが、今まで紹介してきたソリューションを流用しようと思いましたが、コメントアウトして残しているソースも多く、見にくいので別ソリューションに分けました。基本的な内容はここまで紹介してきたサンプルと変わりません。

書換え可能な IOptions インタフェース

まず、src. 17 のような書換え可能な IOptions インタフェースを定義します。

元のサンプルでは IOptions を継承していますが、IOptionsMonitor を継承して名前付きオプションもサポートするよう変更しています。元サンプルでは内部的に IOptionsMonitor を使用しているのに何故、継承元が IOptions なのかは理解できませんでした。加えて、元サンプルには無い 23 行目の OnUpdated を追加して、更新を通知できるように変更しています

続いて、src. 17 の IOptionsWritableMS.E.DI に登録するための拡張メソッドも src. 18 のように追加します。

名前の指定あり・無しのオーバーロードを追加している点と、46 行目を AddSingleton に変更(元サンプルは AddTransient)している点が元サンプルからの変更箇所です。(元サンプルが継承していた IOptions はシングルトンなのに AddTransient で登録していた理由は不明です)

そして、かなり長いですが、src. 19 がメインの IOptionsWritable の実装クラスです。

元のサンプルは Json.NET(Newtonsoft.Json)を使用していましたが、src. 19 は System.Text.Json に書き換えています。言い訳になりますが、管理人は JSON を扱ったのは初めてで、今回は大して調べず、トライ・アンド・エラーでウォッチウィンドウと格闘しつつ勢いで書いたのが src. 19 ですw

本当ならもっとスマートで簡単な方法があるのかもしれないので、泥臭い感じのコードになっていると思います。もっとスマートな方法があるなら是非、コメントなどで教えてもらえるとありがたいです!

src. 19 は長いですが、やっている事は単純で、更新したオブジェクトを appsettings.json にシリアライズしているだけです。分かりにくいかもしれませんが、その時考えたことはソース内にコメントとして残してあるので、ここでは詳しく説明しませんw(と言うより、System.Text.Json 自体をふんわりとしが理解できていないので説明できないw)

まあ、System.Text.Json についてはもう少し理解が進めば記事を書くかもしれません。

一応、上で紹介した 3 つのクラスを作成すればソースコードから設定情報を更新して、appsettings.json に書き出す事が出来ます。

又、45 行目の OnUpdated で、JSON に保存した事を通知できるようにしています。尚、47 行目のようにイベントを購読・解除する方法は GitHub の .NET Runtime で公開されている OptionsMonitor の実装 から丸パクリしていますw

src. 19 では設定値を更新して即、appsettings.json の保存まで実行していますが、JSON へのシリアライズ処理を public なメソッドとして分割して、OnUpdated の通知を監視しておけば、例えばアプリ終了時のタイミングで更新内容を一気に appsettings.json へ書き出す事もできると思います。このエントリでは紹介しませんが、機会があれば紹介するかもしれません。

IOptionsWritable の使い方

IOptionsWritable の使い方は、このエントリで紹介してきた IOptionsMonitor と同じです。これはじぇりーさんが書かれた元のサンプルが Cool だからだと言えます!

まず、src. 20 が MS.E.DI への登録です。

src. 20 では名前付きオプションとして登録していますが、第 1 パラメータを削除すれば名前なしのオプションとしても登録できます。

MS.E.DI に登録した IOptionsWritable を src. 21 のように Worker クラスに DI して UpdateAsync(非同期メソッドですが、コンストラクタ内なので同期でしか呼び出せません…)を呼び出しています。

実行すると、fig. 10 のようにコマンドプロンプトの後ろに表示している appsettings.json が書き換わるのが確認できます。(画像をクリックすると再生/停止します)

fig.10 IOptionsWritable から appsettings.json の書き換え

ASP.NET Core では設定ファイルの書き換えをする必要性は少ないと思いますが、クライアントアプリには欲しい機能だと思います。Generic Host 標準で設定値の更新がサポートされるのはいつになるか分かりませんが、Generic Host は WPF や Windows Form にも組み込めるので、それなりに使い所はあると思っています。

ただ、元サンプルと大きく構造を変更している箇所が 1 点だけあるので、補足しておきます。src. 19 の 111 行目で appsettings.json のパスを取得していますが、元サンプルでは IHostingEnvironment.ContentRootFileProvider からファイルのパスを取得していました。管理人も最初は元サンプルと同じ(IHostingEnvironment は廃止なので、IHostEnvironment を使用)ように書いていましたが、実行すると【bin フォルダ】内の appsettings.json ではなくプロジェクトにぶら下げている方の appsettings.json が更新されるような状態でした。(テストプロジェクトでは想定通り【bin フォルダ】内のファイルのみ更新される)

ASP.NET Core では IHostEnvironmentを使用する方が正しいのかもしれませんが、解消方法も分かりませんし、ASP.NET Core 自体詳しくないので、使い慣れた AppContext.BaseDirectory を使って appsettings.json のフルパスを取得する形に変更しています。

併せて、元サンプルの IHostingEnvironment(IHostEnvironmen)は appsettings.json のフルパスを取得するためだけに使用されていたので、src. 19 23 行目のコンストラクタパラメータからも IHostingEnvironment(IHostEnvironmen) を削除している…と言う辺りが変更箇所になります。

本章で紹介したソースも GitHub リポジトリ (GenericHostWritableOptions.sln)に上げています!

まとめ的な

このエントリを公開する前に『appsettings.json』で検索してみると、日本語で書かれたブログや記事はそれなりに見かけましたが、大半の記事はこのエントリの第 2 章『設定値の DI』で紹介した IConfiguration を DI して設定値を取得する辺りで終わっている記事がほとんどだったので、あまり見かけない情報を紹介できたと思っています。

加えて、『appsettings.json』について書かれた記事は ASP.NET Core 向けに内容が大半でした。世の中の流れ的に Web 系の需要が高いのは当然だと思いますが、コンソールアプリを初めとした Windows で動作するクライアントアプリ系の需要が 0 になる事は無いと思っているので、使える場面もそれなりにありそうだと思います。

又、このエントリではコンソールアプリを例に紹介しましたが、.NET 6 以降では、ASP.NET Core アプリを作成する場合でも、ここで紹介したコンソールアプリと同じパターンで記述する形に変わったようなので、このエントリで紹介した内容は ASP.NET Core でもそのまま適用できるはずです。

そして、上にも書いた通り、Generic Host はコンソールアプリや ASP.NET Core だけでなく WPF や Windows Form にも組み込めるので、このエントリで紹介した内容はそのまま適用できます。WPF アプリに Generic Host を組み込む記事も近々書こうと思っていて、その記事では Configuration に関する内容はこの記事を参照する形で書く予定です。

 

 

 

 

おすすめ

2件のフィードバック

  1. juner より:

    appsettings.json を取り扱うならばできれば ユーザーシークレットの話もちょっと上げていただけるとありがたい感……
    https://learn.microsoft.com/ja-jp/aspnet/core/security/app-secrets?view=aspnetcore-6.0&tabs=windows

    なお、vscode でもユーザーシークレットにアクセスする為の拡張機能もあります。(パスの構成ルールがあるので拡張機能も作られているみたいです。
    https://marketplace.visualstudio.com/items?itemName=adrianwilczynski.user-secrets

    • 沖田玲朗 より:

      junerさん
      『Generic Host の DI たちが依存性を救うようです』に続いてこちらもありがとうございます!

      > appsettings.json を取り扱うならばできれば ユーザーシークレットの話も
      > ちょっと上げていただけるとありがたい感……
      『ユーザーシークレット』…言葉自体は見た事がある気はしますが知りませんでした!
      内容を確認して記事にできそうか吟味してみます!(現在は別の記事を書いているので…)

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です

このサイトはスパムを低減するために Akismet を使っています。コメントデータの処理方法の詳細はこちらをご覧ください