Руководство по шаблону параметров для авторов библиотек .NET
С помощью внедрения зависимостей можно зарегистрировать службы и их соответствующие конфигурации, чтобы использовать шаблон параметров. Шаблон параметров позволяет потребителям библиотеки (и ваших служб) запрашивать экземпляры интерфейсов параметров, где TOptions
— это ваш класс параметров. Использование параметров конфигурации с помощью строго типизированных объектов помогает обеспечить согласованное представление значений, обеспечивает проверку с заметками данных и удаляет нагрузку на анализ строковых значений вручную. Существует множество поставщиков конфигурации, которые могут использоваться потребителями библиотеки. Эти поставщики позволяют пользователям настраивать библиотеку разными способами.
Авторы библиотек для .NET получат здесь общие рекомендации по правильному предоставлению шаблона параметров для потребителей библиотеки. Существует множество способов решить одну задачу. При этом нужно учитывать несколько факторов.
Соглашения об именах
По соглашению методы расширения, отвечающие за регистрацию служб, получают имена вида Add{Service}
, где {Service}
— это понятное описательное имя. Add{Service}
Методы расширения являются обычными в ASP.NET Core и .NET.
✔️ РЕКОМЕНДУЕТСЯ использовать имена, которые отличают службу от других предложений.
❌НЕ ИСПОЛЬЗУЙТЕ имена, которые уже являются частью экосистемы .NET из официальных пакетов Майкрософт.
✔️ РЕКОМЕНДУЕТСЯ присваивать статическим классам, которые предоставляют методы расширения, имена вида {Type}Extensions
, где {Type}
— это расширяемый тип.
Рекомендации по использованию пространства имен
Пакеты Майкрософт используют пространство имен Microsoft.Extensions.DependencyInjection
, чтобы унифицировать регистрацию разных предложений услуг.
✔️ РЕКОМЕНДУЕТСЯ использовать пространство имен, которое четко описывает предложение пакета.
❌НЕ ИСПОЛЬЗУЙТЕ пространство имен 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;
- вызывает OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) с параметром типа
LibraryOptions
; - передает результат в следующий вызов к 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) является частью пакета NuGet Microsoft.Extensions.Options.ConfigurationExtensions
.
Приведенный выше код 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
- Звонки:
- AddOptions с параметром универсального типа
SupportOptions
- BindConfiguration с заданным
configSectionPath
параметром - ValidateDataAnnotations Включение проверки примечаний к данным
- ValidateOnStart принудительное применение проверки при запуске, а не во время выполнения
- AddOptions с параметром универсального типа
В следующем примере пакет NuGet Microsoft.Extensions.Options.DataAnnotations используется для включения проверки заметки данных. Класс 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;
- определяет параметр
LibraryOptions
делегата Action<T>, гдеconfigureOptions
имеет значениеT
; - вызывает Configure для действия
configureOptions
.
Потребители в этом шаблоне предоставляют лямбда-выражение (или делегат, который соответствует параметру 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;
- вызывает OptionsServiceCollectionExtensions.AddOptions<TOptions>(IServiceCollection) с параметром типа
LibraryOptions
; - привязывает вызов к 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;
- определяет параметр
LibraryOptions
делегата Action<T>, гдеconfigureOptions
имеет значениеT
; - вызывает PostConfigure для действия
configureOptions
.
Потребители в этом шаблоне предоставляют лямбда-выражение (или делегат, который соответствует параметру 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();