.NET ライブラリ作成者のためのオプション パターン ガイダンス
依存関係の挿入の助けを借りて、サービスとそれに対応する構成を登録すると、"オプション パターン" を利用できるようになります。 オプション パターンを使用すると、ライブラリ (およびサービス) のコンシューマーで、TOptions
がオプション クラスであるオプション インターフェイスのインスタンスを要求できます。 厳密に型指定されたオブジェクトを使用して構成オプションを使用すると、一貫性のある値の表現が保証され、データ注釈による検証を可能にし、文字列値を手動で解析する負担がなくなります。 ライブラリのコンシューマーで使用する多くの構成プロバイダーがあります。 コンシューマーでこれらのプロバイダーを使用すると、さまざまな方法でライブラリを構成できます。
.NET ライブラリの作成者のために、ライブラリのコンシューマーにオプション パターンを正しく公開する方法に関する一般的なガイダンスを説明します。 同じことを実現するのにさまざまな方法があり、いくつかの考慮事項があります。
名前付け規則
慣例により、サービスを登録する拡張メソッドの名前は Add{Service}
であり、{Service}
は意味のあるわかりやすい名前です。 Add{Service}
拡張メソッドは、ASP.NET Core でも .NETでも一般的です。
✔️ 自分のサービスと他のオファリングを明確に区別できる名前を考えます。
❌ .既に NET エコシステムの一部になっている、Microsoft の公式パッケージに含まれる名前は使用しないでください。
✔️ 拡張メソッドを公開する静的クラスは、{Type}Extensions
という名前にします。{Type}
は拡張する型です。
名前空間のガイダンス
Microsoft のパッケージでは、Microsoft.Extensions.DependencyInjection
名前空間を使用して、さまざまなサービス オファリングの登録が統合されています。
✔️ 自分のパッケージ オファリングを明確に識別できる名前空間を考えます。
❌ Microsoft が公式に認めていないパッケージには、Microsoft.Extensions.DependencyInjection
名前空間を使用しないでください。
パラメーターなし
サービスが最小限の構成または明示的ではない構成で動作できる場合は、パラメーターなしの拡張メソッドを検討します。
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services)
{
services.AddOptions<LibraryOptions>()
.Configure(options =>
{
// Specify default option values
});
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
上のコードの AddMyLibraryService
では、次のことが行われています。
- IServiceCollection のインスタンスを拡張します
LibraryOptions
の型パラメーターを使用して OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) を呼び出します- Configure の呼び出しをチェーンします。ここでは、既定のオプション値を指定します
IConfiguration
パラメーター
多くのオプションをコンシューマーに公開するライブラリを作成するときは、IConfiguration
パラメーター拡張メソッドが必要になる場合があります。 予想される IConfiguration
インスタンスでは、IConfiguration.GetSection 関数を使用することにより、構成の名前付きセクションにスコープを設定する必要があります。
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
IConfiguration namedConfigurationSection)
{
// Default library options are overridden
// by bound configuration values.
services.Configure<LibraryOptions>(namedConfigurationSection);
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
ヒント
Configure<TOptions>(IServiceCollection, IConfiguration) メソッドは、Microsoft.Extensions.Options.ConfigurationExtensions
NuGet パッケージの一部です。
上のコードの AddMyLibraryService
では、次のことが行われています。
- IServiceCollection のインスタンスを拡張します
- IConfiguration パラメーターの
namedConfigurationSection
を定義します LibraryOptions
のジェネリック型パラメーターと、構成するnamedConfigurationSection
インスタンスを渡して Configure<TOptions>(IServiceCollection, IConfiguration) を呼び出します
このパターンのコンシューマーは、名前付きセクションのスコープ設定された IConfiguration
インスタンスを提供します。
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(
builder.Configuration.GetSection("LibraryOptions"));
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
.AddMyLibraryService
の呼び出しは、IServiceCollection メソッドで行われます。
既定値を指定するかどうかは、ライブラリの作成者が決定します。
Note
構成をオプション インスタンスにバインドすることができます。 ただし、名前が衝突してエラーが発生するリスクがあります。 また、この方法を使用して手動でバインドするときは、オプション パターンの使用を読み取り 1 回に制限します。 設定を変更しても再バインドされないため、コンシューマーは IOptionsMonitor インターフェイスを使用できません。
services.AddOptions<LibraryOptions>()
.Configure<IConfiguration>(
(options, configuration) =>
configuration.GetSection("LibraryOptions").Bind(options));
代わりに、BindConfiguration 拡張メソッドを使用する必要があります。 この拡張メソッドは、構成をオプション インスタンスにバインドし、構成セクションの変更トークン ソースも登録します。 これにより、コンシューマーは IOptionsMonitor インターフェイスを使用できます。
構成セクションのパス パラメーター
ライブラリのコンシューマーは、基になる TOptions
型をバインドするための構成セクション パスを指定できます。 このシナリオに場合は、拡張メソッドで string
パラメーターを定義します。
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
string configSectionPath)
{
services.AddOptions<SupportOptions>()
.BindConfiguration(configSectionPath)
.ValidateDataAnnotations()
.ValidateOnStart();
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
上のコードの AddMyLibraryService
では、次のことが行われています。
- IServiceCollection のインスタンスを拡張します
string
パラメーターconfigSectionPath
を定義します- 呼び出し:
SupportOptions
のジェネリック型パラメーターを持つ AddOptions- 与えられた
configSectionPath
パラメーターを持つ BindConfiguration - ValidateDataAnnotations データ注釈の検証を有効にする
- ValidateOnStart 実行時ではなく起動時に検証を適用する
次の例では、 Microsoft.Extensions.Options.DataAnnotations NuGet パッケージを使用して、データ注釈の検証を有効にします。 SupportOptions
クラスは、次のように定義されます:
using System.ComponentModel.DataAnnotations;
public sealed class SupportOptions
{
[Url]
public string? Url { get; set; }
[Required, EmailAddress]
public required string Email { get; set; }
[Required, DataType(DataType.PhoneNumber)]
public required string PhoneNumber { get; set; }
}
次の JSON appsettings.json ファイルが使用されるとします:
{
"Support": {
"Url": "https://support.example.com",
"Email": "help@support.example.com",
"PhoneNumber": "+1(888)-SUPPORT"
}
}
Action<TOptions>
パラメーター
ライブラリのコンシューマーは、オプション クラスのインスタンスを生成するラムダ式を提供することに関心を持つ場合があります。 このシナリオに場合は、拡張メソッドで Action<LibraryOptions>
パラメーターを定義します。
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
Action<LibraryOptions> configureOptions)
{
services.Configure(configureOptions);
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
上のコードの AddMyLibraryService
では、次のことが行われています。
- IServiceCollection のインスタンスを拡張します
- Action<T> のパラメーター
configureOptions
を定義します。ここで、T
はLibraryOptions
です configureOptions
アクションを指定して Configure を呼び出します
このパターンのコンシューマーは、ラムダ式 (または、Action<LibraryOptions>
パラメーターを満たすデリゲート) を提供します。
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(options =>
{
// User defined option values
// options.SomePropertyValue = ...
});
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
オプション インスタンスのパラメーター
ライブラリのコンシューマーは、インラインのオプション インスタンスを使用することを好む場合があります。 このシナリオの場合は、オプション オブジェクト LibraryOptions
のインスタンスを受け取る拡張メソッドを公開します。
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
LibraryOptions userOptions)
{
services.AddOptions<LibraryOptions>()
.Configure(options =>
{
// Overwrite default option values
// with the user provided options.
// options.SomeValue = userOptions.SomeValue;
});
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
上のコードの AddMyLibraryService
では、次のことが行われています。
- IServiceCollection のインスタンスを拡張します
LibraryOptions
の型パラメーターを使用して OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) を呼び出します- Configure の呼び出しをチェーンします。そこでは、特定の
userOptions
インスタンスからオーバーライドできる既定のオプション値を指定します
このパターンのコンシューマーは、LibraryOptions
クラスのインスタンスを提供し、必要なプロパティ値をインラインで定義します。
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(new LibraryOptions
{
// Specify option values
// SomePropertyValue = ...
});
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
構成後
すべての構成オプション値をバインドまたは指定すると、事後構成の機能を使用できるようになります。 前に詳しく説明したのと同じ Action<TOptions>
パラメーターを公開すると、PostConfigure を呼び出すことができます。 事後構成は、.Configure
のすべての呼び出しの後に実行されます。 PostConfigure
を使用することを検討する理由がいくつかあります:
- 実行順序:
.Configure
呼び出しで設定されたすべての構成値をオーバーライドできます。 - 検証: 他のすべての構成が適用された後で、既定値が設定されていることを検証できます。
using Microsoft.Extensions.DependencyInjection;
namespace ExampleLibrary.Extensions.DependencyInjection;
public static class ServiceCollectionExtensions
{
public static IServiceCollection AddMyLibraryService(
this IServiceCollection services,
Action<LibraryOptions> configureOptions)
{
services.PostConfigure(configureOptions);
// Register lib services here...
// services.AddScoped<ILibraryService, DefaultLibraryService>();
return services;
}
}
上のコードの AddMyLibraryService
では、次のことが行われています。
- IServiceCollection のインスタンスを拡張します
- Action<T> のパラメーター
configureOptions
を定義します。ここで、T
はLibraryOptions
です configureOptions
アクションを指定して PostConfigure を呼び出します
このパターンのコンシューマーは、事後構成ではないシナリオの Action<TOptions>
パラメーター と同様に、ラムダ式 (または、Action<LibraryOptions>
パラメーターを満たすデリゲート) を提供します。
using ExampleLibrary.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
HostApplicationBuilder builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMyLibraryService(options =>
{
// Specify option values
// options.SomePropertyValue = ...
});
using IHost host = builder.Build();
// Application code should start here.
await host.RunAsync();
関連項目
.NET