Wskazówki dotyczące wzorca opcji dla autorów bibliotek platformy .NET

Dzięki iniekcji zależności rejestrowanie usług i ich odpowiednich konfiguracji może korzystać ze wzorca opcji. Wzorzec opcji umożliwia użytkownikom biblioteki (i usługom) wymaganie wystąpień interfejsów opcji, w których TOptions jest klasą opcji. Korzystanie z opcji konfiguracji za pośrednictwem silnie typiowanych obiektów pomaga zapewnić spójną reprezentację wartości, umożliwia walidację adnotacjami danych i usuwa obciążenie ręcznego analizowania wartości ciągów. Istnieje wielu dostawców konfiguracji, których użytkownicy biblioteki mogą używać. Dzięki tym dostawcom użytkownicy mogą konfigurować bibliotekę na wiele sposobów.

Jako autor biblioteki platformy .NET poznasz ogólne wskazówki dotyczące poprawnego uwidaczniania wzorca opcji dla użytkowników biblioteki. Istnieją różne sposoby osiągnięcia tego samego celu i kilka zagadnień, które należy wziąć pod uwagę.

Konwencje nazewnictwa

Zgodnie z konwencją metody rozszerzeń odpowiedzialne za rejestrowanie usług mają nazwę Add{Service}, gdzie {Service} jest zrozumiałą i opisową nazwą. Add{Service} metody rozszerzeń są powszechne zarówno w ASP.NET Core , jak i na platformie .NET.

✔️ ROZWAŻ nazwy, które uściślają twoją usługę od innych ofert.

❌ NIE używaj nazw, które są już częścią ekosystemu platformy .NET z oficjalnych pakietów firmy Microsoft.

✔️ ROZWAŻ nazewnictwo klas statycznych, które uwidaczniają metody rozszerzenia jako {Type}Extensions, gdzie {Type} jest typem, który rozszerzasz.

Wskazówki dotyczące przestrzeni nazw

Pakiety firmy Microsoft korzystają z Microsoft.Extensions.DependencyInjection przestrzeni nazw w celu ujednolicenia rejestracji różnych ofert usług.

✔️ ROZWAŻ przestrzeń nazw, która wyraźnie identyfikuje ofertę pakietu.

❌ NIE używaj Microsoft.Extensions.DependencyInjection przestrzeni nazw dla pakietów firmy Microsoft innych niż oficjalne.

Bezparametrowego

Jeśli usługa może pracować z minimalną lub bez jawnej konfiguracji, rozważ metodę rozszerzenia bez parametrów.

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

W poprzednim kodzie :AddMyLibraryService

IConfiguration Parametr

Podczas tworzenia biblioteki, która uwidacznia wiele opcji użytkownikom, warto rozważyć wymaganie metody rozszerzenia parametrów IConfiguration . Oczekiwane IConfiguration wystąpienie powinno być ograniczone do nazwanej sekcji konfiguracji przy użyciu IConfiguration.GetSection funkcji .

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

W poprzednim kodzie :AddMyLibraryService

Konsumenci w tym wzorcu udostępniają wystąpienie o IConfiguration określonym zakresie w nazwanej sekcji:

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();

Wywołanie metody jest .AddMyLibraryService wykonywane na typie IServiceCollection .

Jako autor biblioteki określenie wartości domyślnych jest do Ciebie.

Uwaga

Istnieje możliwość powiązania konfiguracji z wystąpieniem opcji. Istnieje jednak ryzyko kolizji nazw , co spowoduje błędy. Ponadto w przypadku ręcznego powiązania w ten sposób można ograniczyć użycie wzorca opcji do odczytu jednokrotnego. Zmiany ustawień nie zostaną ponownie powiązane, ponieważ użytkownicy nie będą mogli używać interfejsu IOptionsMonitor .

services.AddOptions<LibraryOptions>()
    .Configure<IConfiguration>(
        (options, configuration) =>
            configuration.GetSection("LibraryOptions").Bind(options));

Zamiast tego należy użyć BindConfiguration metody rozszerzenia. Ta metoda rozszerzenia wiąże konfigurację z wystąpieniem opcji, a także rejestruje źródło tokenu zmiany dla sekcji konfiguracji. Dzięki temu użytkownicy mogą korzystać z interfejsu IOptionsMonitor .

Parametr ścieżki sekcji konfiguracji

Użytkownicy biblioteki mogą chcieć określić ścieżkę sekcji konfiguracji, aby powiązać podstawowy TOptions typ. W tym scenariuszu string zdefiniujesz parametr w metodzie rozszerzenia.

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

W poprzednim kodzie :AddMyLibraryService

W następnym przykładzie pakiet NuGet Microsoft.Extensions.Options.DataAnnotations służy do sprawdzania poprawności adnotacji danych. Klasa jest definiowana SupportOptions w następujący sposób:

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

Załóżmy, że używany jest następujący plik appsettings.json JSON:

{
    "Support": {
        "Url": "https://support.example.com",
        "Email": "help@support.example.com",
        "PhoneNumber": "+1(888)-SUPPORT"
    }
}

Action<TOptions> Parametr

Użytkownicy biblioteki mogą być zainteresowani udostępnieniem wyrażenia lambda, które daje wystąpienie klasy options. W tym scenariuszu zdefiniujesz Action<LibraryOptions> parametr w metodzie rozszerzenia.

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

W poprzednim kodzie :AddMyLibraryService

Odbiorcy w tym wzorcu udostępniają wyrażenie lambda (lub delegat, który spełnia Action<LibraryOptions> parametr):

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();

Parametr wystąpienia opcji

Użytkownicy biblioteki mogą wolisz udostępnić wystąpienie opcji w formacie wbudowanym. W tym scenariuszu uwidaczniasz metodę rozszerzenia, która przyjmuje wystąpienie obiektu opcji . 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;
    }
}

W poprzednim kodzie :AddMyLibraryService

Odbiorcy w tym wzorcu udostępniają wystąpienie LibraryOptions klasy, definiując żądane wartości właściwości w tekście:

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();

Po konfiguracji

Po określeniu lub powiązaniu wszystkich wartości opcji konfiguracji funkcja po konfiguracji jest dostępna. Uwidaczniając ten sam Action<TOptions> parametr opisany wcześniej, można wywołać metodę PostConfigure. Po skonfigurowaniu przebiegów po wszystkich .Configure wywołaniach. Istnieje kilka powodów, dla których warto rozważyć użycie polecenia PostConfigure:

  • Kolejność wykonywania: można zastąpić wszystkie wartości konfiguracji, które zostały ustawione w wywołaniach .Configure .
  • Walidacja: Możesz sprawdzić, czy wartości domyślne zostały ustawione po zastosowaniu wszystkich innych konfiguracji.
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;
    }
}

W poprzednim kodzie :AddMyLibraryService

Konsumenci w tym wzorcu udostępniają wyrażenie lambda (lub delegat, który spełnia Action<LibraryOptions> parametr), podobnie jak w przypadku parametru Action<TOptions> w scenariuszu konfiguracji innej niż post:

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();

Zobacz też