.NET 程式庫作者的選項模式指導
在相依性插入的協助下,您可以使用「選項模式」註冊服務及其對應的組態。 選項模式可讓程式庫 (和服務) 的取用者要求選項介面的執行個體,其中 TOptions
是選項類別。 透過強型別物件取用組態選項有助於確保一致的值表示法、啟用資料註釋的驗證,以及去除手動剖析字串值所帶來的負擔。 程式庫取用者有許多組態提供者可供使用。 透過這些提供者,取用者可使用許多方式設定您的程式庫。
身為 .NET 程式庫作者,您將了解如何正確向程式庫取用者公開選項模式的一般指導。 有多種方法可以達成相同的目的,並需要思索數個考量。
命名規範
依照慣例,負責註冊服務的擴充方法會命名為 Add{Service}
,其中 {Service}
是有意義的描述性名稱。 Add{Service}
擴充方法在 ASP.NET Core 和 .NET 中很常見。
✔️ 請考慮可釐清服務與其他供應項目的名稱。
❌ 請勿使用已屬於官方 Microsoft 套件 .NET 生態系統的名稱。
✔️ 請考慮為將擴充方法公開為 {Type}Extensions
的靜態類別命名,其中 {Type}
是您要擴充的類型。
命名空間指導
Microsoft 套件會使用 Microsoft.Extensions.DependencyInjection
命名空間來統一各種服務供應項目的註冊。
✔️ 請考慮可清楚識別套件供應項目的命名空間。
❌ 請勿將 Microsoft.Extensions.DependencyInjection
命名空間用於非官方 Microsoft 套件。
無參數
如果您的服務可以使用最少或不使用明確組態,請考慮使用無參數擴充方法。
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
- 呼叫 Configure<TOptions>(IServiceCollection, IConfiguration) 會傳遞
LibraryOptions
的泛型型別參數和要設定的namedConfigurationSection
執行個體
此模式中的取用者會提供具名區段的具範圍 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 類型上進行。
身為程式庫作者,您將負責指定預設值。
注意
您可以將組態繫結至選項執行個體。 不過,這有可能發生名稱衝突的風險,並會導致錯誤。 此外,以這種方式手動繫結時,您會將選項模式的耗用量限制為讀取一次。 設定的變更不會重新繫結,例如取用者將無法使用 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>
參數
程式庫的取用者可能有興趣提供可產生選項類別執行個體的 Lambda 運算式。 在此案例中,您會在擴充方法中定義 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
此模式中的取用者會提供 Lambda 運算式 (或滿足 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
此模式中的取用者會提供 Lambda 運算式 (或滿足 Action<LibraryOptions>
參數的委派),就像在非後置組態案例中使用Action<TOptions>
參數一樣:
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();