.NET の Configure について調べたくなったのでまとめます。
動作環境は以下です。
- .NET 6
- C# 10
- Azure Function v4
GetValue
以下のような json file を考えます。
{ "TestValue1": "hogehoge", "TestValue2": "100", "TestValue3": "2023/10/17 12:30:31 +00:00" }
ConfigurationBinder.GetValue
var stringValue = configuration.GetValue("TestValue1", "default value"); // hogehoge var intValue = configuration.GetValue("TestValue2", 200); // 100 var dateTimeValue = configuration.GetValue("TestValue3", DateTimeOffset.UtcNow); // 2023/10/17 12:30:31 +00:00
第一引数に json の key を指定して、第二引数に規定値を指定しています。第二引数を省略した場合、型パラメータを使って明示的に型指定をしないといけません。その場合、key に該当する value がない時は戻り値は型パラメータで指定した型の初期値になります。
Section
Section という仕組みがあります。簡単に言うと、ネストされたオブジェクトを取り扱うためのものです。公式ドキュメントに沿って以下のような Json file を考えます。
{ "Section1": { "TestValue1": "hogehoge", "TestValue2": "fugafuga" }, "Section2": { "TestValue2": "fugafuga" }, "Section3": { "TestValue3": "piyopiyo" } }
実はこれが罠で、Azure Function で Section を扱うには以下のようにしなければいけないようです。
{ "Section1__SectionValue1": "hogehoge", "Section1__SectionValue2": "fugafuga", "Section2__SectionValue2": "fugafuga", "Section3__SectionValue3": "piyopiyo" }
この Json は
- Section1
- Section2
- Section3
という 3 つの Section を持つことになります。各 Section はそれぞれ key を持っています。これを IConfiguration を使って扱うにはいくつか方法があります。
まずは Configuration.GetSection を使うパターンです。
var section1 = configuration.GetSection("Section1"); var value1 = section1.GetValue<string>("SectionValue1"); // hogehoge var value2 = section1.GetValue<string>("SectionValue2"); // fugafuga
と取得する事が出来ます。
もう一つが ConfigurationBinder.GetValue
var value1 = configuration.GetValue<string>("Section1:SectionValue1"); // hogehoge var value2 = configuration.GetValue<string>("Section1:SectionValue2"); // fugafuga
key 名を「:」で区切ることでネストされたオブジェクトの値を取得する事が出来ます。
ちなみに、 Configuration.GetSectionにも「:」は指定可能で、その場合はさらにネストされたオブジェクトにアクセスできます。
以下のような json で
{ "Section1__SectionValue2__SubSectionValue1": "sub section value" }
以下を実行できます。
var subsection = configuration.GetSection("Section1:SectionValue2"); var value1 = subsection.GetValue<string>("SubSectionValue1"); // sub section value
Children
ネストされたオブジェクトを扱うには Children という仕組みもあります。これまでの json と同じものを扱うという前提で、以下のように書きます。
var section = configuration.GetSection("Section1"); if (section.Exists()) { foreach (var subSection in section.GetChildren()) { var value1 = subSection.Value; } }
こうすると特に key を指定せずとも全ての key の value を取り出してくれます。Exists は IConfigurationSection に対する拡張メソッドで、ある Section に key が一つでもあれば true、なければ false を返します。内部の実装は Any を使ってますね。
Option Pattern と Section
長々と書きましたが、最後に Option Pattern とどう組み合わせようかを書きます。Option Pattern は公式ドキュメントに詳しく書いてあるので読んでいただければと思います。私は「構成値を型化して、Singleton で都合よく使い回す」くらいの理解をしています。
なので Startup で ServiceProvider に登録できるのですが、Section を使うとシンプルに書けます。
builder.Services.Configure<MyOption>(); public class MyOption { public const string SectionName = "MyOption"; public string Name { get; init; } public int Age { get; init; } }
また、このパターンを使うときにはネストされたオブジェクトの区切り文字を「:」にしなければいけません (上述の例だと「_」にしていました)。
{ "MyOption:Name": "taro", "MyOption:Age": "10" }
コードを以下のようにすると、
builder.Services.Configure<MyOption>(configuration.GetSection(MyOption.SectionName)); var myOption = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<MyOption>>().Value; // myOption.Age => 10 // myOption.Name => "taro"
もしくは以下のようにも出来ます。
builder.Services.AddOptions<MyOption>().Configure<IConfiguration>((options, configuration) => { configuration.GetSection(MyOption.SectionName).Bind(options); }); var myOption = builder.Services.BuildServiceProvider().GetRequiredService<IOptions<MyOption>>().Value; // myOption.Age => 10 // myOption.Name => "taro"
Json の key 名と class の property 名が一致したら value を代入してくれる感じですね。こうしておくと参照側では IOption<MyOption>
を使って構成値にアクセスする事が出来ます。
開発者体験良いですね。色々と応用も効きそう。
ちなみに、Azure Function の構成値は文字列でなけれないけません。なので Section を使うときには「__」や「:」といった予約語を使わなければいけないのです。また、「:」は DI をサポートするためのものです。以下引用です。
Values must be strings and not JSON objects or arrays. Setting names can't include a double underline (__) and shouldn't include a colon (:). Double underline characters are reserved by the runtime, and the colon is reserved to support dependency injection.