Options pattern: IOptions, IOptionsSnapshot, IOptionsMonitor, валидация

↑ 3.1 Хост, конфигурация и middleware pipeline · ← Предыдущая · → Следующая

Обязательно~3 мин чтенияУровень: middle

Зачем это на собесе

Разница между тремя интерфейсами Options и умение включить валидацию на старте — типичный вопрос на middle.

Объяснение

Options pattern привязывает секцию конфигурации к типизированному классу.

public class JwtOptions
{
    public const string Section = "Jwt";
    [Required] public string Issuer { get; init; } = "";
    [Required, MinLength(32)] public string Key { get; init; } = "";
    [Range(1, 1440)] public int LifetimeMinutes { get; init; } = 60;
}
 
builder.Services
    .AddOptions<JwtOptions>()
    .Bind(builder.Configuration.GetSection(JwtOptions.Section))
    .ValidateDataAnnotations()
    .ValidateOnStart();
ИнтерфейсВремя жизниОбновлениеКогда
IOptions<T>singletonне обновляетсястатичные настройки
IOptionsSnapshot<T>scopedобновляется на каждый запросвеб-запросы с перечитыванием
IOptionsMonitor<T>singletonобновляется, есть OnChangeфоновые сервисы, singleton-ы
public class TokenService(IOptionsMonitor<JwtOptions> opts)
{
    public string Issuer => opts.CurrentValue.Issuer;   // всегда свежее значение
}

Именованные options и ValidateOnStart

Configure<T>("name", ...) и IOptionsMonitor<T>.Get("name") позволяют держать несколько конфигураций одного типа (несколько клиентов, провайдеров). ValidateOnStart() роняет приложение при старте с понятной ошибкой, а не при первом обращении.

Нюансы и подводные камни

  • IOptionsSnapshot нельзя внедрять в singleton (scoped внутри singleton — captive dependency).
  • Для init/required-свойств и привязки используйте BindConfiguration или Bind.
  • Без ValidateOnStart ошибки конфигурации проявятся на проде при первом запросе.
  • Сложную валидацию делайте через IValidateOptions<T>.
  • Не внедряйте IConfiguration в бизнес-код: сложно тестировать и нет типов.

Практика

  1. Оформите секцию Smtp как Options с валидацией и ValidateOnStart.
  2. Измените appsettings на лету и проследите, как ведут себя Snapshot и Monitor.
  3. Напишите IValidateOptions<T> с зависимой проверкой (если UseTls, то Port не 25).

Вопросы с ответами

Связанные темы