.NET - オプションのConfigureとPostConfigureを確認する
.NETのオプションパターンを使用するとき、Configure
メソッドやPostConfigure
メソッドを使ってオプションの値を構成するアクションを指定すると思います。
この投稿では次の2点を確認したいと思います。
Configure
メソッドとPostConfigure
メソッドで指定したアクションは、オプションがDIで解決されるときに呼び出されることPostConfigure
で指定したアクションは、Configure
で指定したアクションが呼び出された後に呼び出されること
ConfigureメソッドとPostConfigureメソッドで指定したアクションの呼び出しを確認する
この2点を確認するサンプルコードです。
// DIで解決するオプション
private class SampleOptions {
public int Value { get; set; }
}
var services = new ServiceCollection();
services
.AddOptions<SampleOptions>()
.Configure(options => {
Console.WriteLine("Configure");
// オプションを構成する
options.Value = 1;
})
.PostConfigure(options => {
Console.WriteLine("PostConfigure");
// オプションを構成する
options.Value = 2;
});
var serviceProvider = services.BuildServiceProvider();
// IOptionsを解決する
Console.WriteLine("GetRequiredService");
var optionsProvider = serviceProvider.GetRequiredService<IOptions<SampleOptions>>();
// IOptionsからSampleOptionsを取り出す
// このときConfigureやPostConfigureで指定したアクションが呼び出される
Console.WriteLine("IOptions.Value");
var options = optionsProvider.Value;
Console.WriteLine(options.Value);
/*
// 実行結果
GetRequiredService
IOptions.Value
Configure
PostConfigure
2
*/
実行結果から、IOptions.Value
プロパティでSampleOptions
のインスタンスを取得(解決)するときに、
Configure
メソッドやPostConfigure
メソッドで指定したアクションが呼び出されることを確認できます。
さらに、Configure
メソッドで指定したアクション、PostConfigure
メソッドで指定したアクションの順番に呼び出され、SampleOptions.Value
の値が上書きされていることも確認できます。
.NETの実装を確認する
これらの動きを実装している部分を.NETのコードから確認したいと思います。
まずAddOptions
メソッドの実装を確認しましょう。IOptions<>
に対してUnnamedOptionsManager<>
、IOptionsFactory<>
に対してOptionsFactory<>
が登録されています。この2つの登録以外はこの投稿では扱いません。無視してください。
public static class OptionsServiceCollectionExtensions
{
public static IServiceCollection AddOptions(this IServiceCollection services)
{
ThrowHelper.ThrowIfNull(services);
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptions<>), typeof(UnnamedOptionsManager<>)));
services.TryAdd(ServiceDescriptor.Scoped(typeof(IOptionsSnapshot<>), typeof(OptionsManager<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitor<>), typeof(OptionsMonitor<>)));
services.TryAdd(ServiceDescriptor.Transient(typeof(IOptionsFactory<>), typeof(OptionsFactory<>)));
services.TryAdd(ServiceDescriptor.Singleton(typeof(IOptionsMonitorCache<>), typeof(OptionsCache<>)));
return services;
}
}
runtime/OptionsServiceCollectionExtensions.cs at main · dotnet/runtime · GitHub
次にUnnamedOptionsManager.Value
プロパティを確認すると、IOptionsFactory.Create
メソッドを呼び出してオプションを生成していることがわかります。
internal sealed class UnnamedOptionsManager<TOptions> : IOptions<TOptions> where TOptions : class
{
private readonly IOptionsFactory<TOptions> _factory;
// 省略
public TOptions Value
{
get
{
if (_value is TOptions value)
{
return value;
}
lock (_syncObj ?? Interlocked.CompareExchange(ref _syncObj, new object(), null) ?? _syncObj)
{
return _value ??= _factory.Create(Options.DefaultName);
}
}
}
}
runtime/UnnamedOptionsManager.cs at main · dotnet/runtime · GitHub
最後にOptionsFactory.Create
メソッドを見てみると、IConfigureOptions<TOptions>.Configure
メソッド、IPostConfigureOptions<TOptions>.PostConfigure
メソッドを順番に呼び出していることがわかります。
public class OptionsFactory<TOptions> : IOptionsFactory<TOptions> where TOptions : class
{
// 省略
public TOptions Create(string name)
{
TOptions options = CreateInstance(name);
foreach (IConfigureOptions<TOptions> setup in _setups)
{
if (setup is IConfigureNamedOptions<TOptions> namedSetup)
{
namedSetup.Configure(name, options);
}
else if (name == Options.DefaultName)
{
setup.Configure(options);
}
}
foreach (IPostConfigureOptions<TOptions> post in _postConfigures)
{
post.PostConfigure(name, options);
}
// 省略
return options;
}
}
runtime/OptionsFactory.cs at main · dotnet/runtime · GitHub
以上、Configure
メソッドとPostConfigure
メソッドについて、サンプルと.NETの実装から確認してみました。