共用方式為


.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 會:

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;
    }
}

在上述程式碼中,AddMyLibraryService 會:

此模式中的取用者會提供具名區段的具範圍 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 會:

在接下來的範例中,會使用 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 會:

此模式中的取用者會提供 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 會:

此模式中的取用者會提供 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 會:

此模式中的取用者會提供 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();

另請參閱