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
- Rozszerza wystąpienie klasy IServiceCollection
- Wywołania OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) z parametrem typu
LibraryOptions
- W łańcuchu wywołania metody Configure, która określa domyślne wartości opcji
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;
}
}
Napiwek
Metoda Configure<TOptions>(IServiceCollection, IConfiguration) jest częścią Microsoft.Extensions.Options.ConfigurationExtensions
pakietu NuGet.
W poprzednim kodzie :AddMyLibraryService
- Rozszerza wystąpienie klasy IServiceCollection
- IConfiguration Definiuje parametr
namedConfigurationSection
- Wywołania Configure<TOptions>(IServiceCollection, IConfiguration) przekazujące ogólny parametr
LibraryOptions
typu i wystąpienie do skonfigurowanianamedConfigurationSection
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
- Rozszerza wystąpienie klasy IServiceCollection
string
Definiuje parametrconfigSectionPath
- Wywołania:
- AddOptions z ogólnym parametrem typu
SupportOptions
- BindConfiguration z podanym
configSectionPath
parametrem - ValidateDataAnnotations aby włączyć walidację adnotacji danych
- ValidateOnStart aby wymusić walidację przy uruchamianiu, a nie w środowisku uruchomieniowym
- AddOptions z ogólnym parametrem typu
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
- Rozszerza wystąpienie klasy IServiceCollection
- Action<T> Definiuje parametr
configureOptions
, w którymT
jestLibraryOptions
- Wywołania Configure podane w
configureOptions
akcji
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
- Rozszerza wystąpienie klasy IServiceCollection
- Wywołania OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) z parametrem typu
LibraryOptions
- Łańcuch wywołania metody Configure, która określa domyślne wartości opcji, które można zastąpić z danego
userOptions
wystąpienia
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
- Rozszerza wystąpienie klasy IServiceCollection
- Action<T> Definiuje parametr
configureOptions
, w którymT
jestLibraryOptions
- Wywołania PostConfigure podane w
configureOptions
akcji
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();