Проверяем Конфигурацию при Запуске. Начало
.NET-приложения со строго типизированной конфигурацией IOptions<T> и его вариантами предоставляют удобный способ привязки разделов appsettings.json к классам C#. Но привязка — это не то же самое, что проверка: отсутствие обязательного значения или число, выходящее за пределы допустимого диапазона, без проблем привяжутся и незаметно сломают приложение во время выполнения. IValidateOptions<T> — механизм, предоставляемый .NET для решения этой проблемы.
Рассмотрим типичный класс параметров:
public class SmtpOptions
{
public string Host { get; set; } = "";
public int Port { get; set; }
public string FromAddress { get; set; } = "";
}
Если в файле appsettings.json отсутствует Host, приложение запустится нормально. Ошибка проявится только при отправке email. Аннотации данных (
[Required], [Range] и т.п.) в сочетании с вызовом ValidateDataAnnotations() помогают, но с ними есть 2 проблемы.1. Они используют рефлексию, что приводит к:
- Затратам на производительность, что важно в сценариях с высокой нагрузкой и требованиям к быстрому запуску.
- Несовместимости с AOT и агрессивным триммингом (
<PublishTrimmed>true</PublishTrimmed>). Могут быть исключены метаданные, от которых зависит рефлексия, что приводит к предупреждениям IL2026/IL3050 или к сбоям во время выполнения.Это можно исправить, используя генератор кода в .NET8+. Для активации генератора для конкретного класса валидатора параметров потребуется:
- Класс параметров, помеченный атрибутами Data Annotation.
- Частичный класс, реализующий IValidateOptions<T> и помеченный
[OptionsValidator], с пустым телом:using Microsoft.Extensions.Options;
[OptionsValidator]
public partial class ValidateSmtpOptions
: IValidateOptions<SmtpOptions>
{
}
Генератор заполнит тело класса при сборке.
2. Но иногда аннотация данных вовсе недостаточно, когда необходимы:
- Проверка нескольких свойств объекта конфигурации;
- Асинхронные или основанные на данных из БД проверки;
- Условная логика в зависимости от среды;
- Многократно используемые валидаторы, общие для нескольких типов конфигурации.
Здесь пригодится реализовать IValidateOptions<T>. Это интерфейс в Microsoft.Extensions.Options с единственным методом:
public interface IValidateOptions<TOptions>
where TOptions : class
{
ValidateOptionsResult Validate(
string? name, TOptions options);
}
Вы реализуете этот интерфейс, регистрируете его в DI-контейнере, и инфраструктура Options вызывает его автоматически:
- лениво (при первом обращении),
- немедленно при запуске, если используется ValidateOnStart().
Вот простой валидатор для класса, описанного выше:
public class SmtpOptionsValidator
: IValidateOptions<SmtpOptions>
{
public ValidateOptionsResult Validate(
string? name, SmtpOptions opts)
{
var errors = new List<string>();
if (string.IsNullOrWhiteSpace(opts.Host))
errors.Add("Требуется Host");
if (opts.Port is < 1 or > 65535)
errors.Add($"Port должен быть от 1 до 65535");
if (string.IsNullOrWhiteSpace(opts.FromAddress))
errors.Add("Требуется FromAddress");
return errors.Count > 0
? ValidateOptionsResult.Fail(errors)
: ValidateOptionsResult.Success;
}
}
Регистрируем в Program.cs:
builder.Services
.Configure<SmtpOptions>(
builder.Configuration.GetSection("Smtp"))
.AddSingleton<IValidateOptions<SmtpOptions>, SmtpOptionsValidator>()
.AddOptions<SmtpOptions>()
.ValidateOnStart();
Если проверка не проходит, во время выполнения
app.Run() генерируется исключение OptionsValidationException, поэтому сервис никогда не запустится с некорректной конфигурацией. Этот вариант «быстрого сбоя» обычно предпочтительнее, чем исключение NullReferenceException во время выполнения, возникающее где-то в глубинах бизнес-логики.Окончание следует…
Источник: https://bartwullems.blogspot.com/2026/03/validating-configuration-at-startup.html