You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Add async startup validation for Microsoft.Extensions.Options (#128788)
Fixes#128100
Implements async startup validation for `Microsoft.Extensions.Options`
as approved in [API
review](#128100 (comment)).
Follow-up to #128656
Source generator support (`[OptionsValidator]` emitting
`ValidateAsync()`) is tracked separately in
#128882.
`OptionsFactory.Create()` and `IOptions<T>.Value` remain fully
synchronous. Async validators run in a separate step during
`Host.StartAsync()` only. Lazy validation via `.Value` and runtime
reload via `IOptionsMonitor<T>` are not affected. See [design
rationale](#128100).
### What's included
- `IAsyncValidateOptions<TOptions>` — async counterpart to
`IValidateOptions<T>` returning `Task<ValidateOptionsResult>`
- `IAsyncStartupValidator` — async counterpart to `IStartupValidator`
for host-level startup validation
- `AsyncValidateOptions<TOptions>` through
`AsyncValidateOptions<TOptions, TDep1..TDep5>` — lambda-based async
validators (0–5 dependencies), mirroring the sync `ValidateOptions<T,
TDep>` family
- Async `Validate` overloads on `OptionsBuilder<TOptions>` (0–5
dependencies) — registers `IAsyncValidateOptions<T>` via lambda
- `DataAnnotationValidateOptionsAsync<TOptions>` — async counterpart to
`DataAnnotationValidateOptions<T>`, calls
`Validator.TryValidateObjectAsync` and walks
`[ValidateObjectMembers]`/`[ValidateEnumeratedItems]` recursively
- `ValidateDataAnnotationsAsync()` extension method on
`OptionsBuilderDataAnnotationsExtensions`
- `StartupValidator` extended to implement `IAsyncStartupValidator` —
runs sync validators first, then async validators, collecting all
`OptionsValidationException`s
- `Host.StartAsync()` updated to prefer `IAsyncStartupValidator` when
available, falling back to sync `IStartupValidator`
- `ValidateOnStart()` extended to register async validator entries
alongside sync entries when `IAsyncValidateOptions<T>` services are
present
- Ref assembly updates for the full async Options API surface
### Not in scope
- No `IAsyncOptions<T>`, `IAsyncOptionsSnapshot<T>`, or
`IAsyncOptionsMonitor<T>` — lazy async resolution is blocked by
`IOptions<T>.Value` being a C# property and `OptionsCache` using
`Lazy<T>` / `ConcurrentDictionary` (no async counterparts in the BCL)
- No runtime async re-validation on `IOptionsMonitor<T>` config changes
— `OnChange` callbacks remain sync-only
- No Options validation source generator changes — the
`[OptionsValidator]` source generator emitting `ValidateAsync()` for
`IAsyncValidateOptions<T>` is tracked separately
### Implementation notes
- `StartupValidator.ValidateAsync()` calls sync `Validate()` first
(which triggers `IOptions<T>.Value` → `OptionsFactory.Create()` → sync
validators), then iterates registered async validators sequentially
- `ValidateOnStart()` registers both sync and async entries in
`StartupValidatorOptions`. Async entries are only added when
`IAsyncValidateOptions<T>` services are detected at configure-time
- `Host.StartAsync()` resolves `IAsyncStartupValidator` first; if not
available, falls back to `IStartupValidator` (backward compatible)
- Multiple `OptionsValidationException`s from different async validators
are aggregated into an `AggregateException` when more than one fails
### API review decisions reflected
- `Task<ValidateOptionsResult>` return type on
`IAsyncValidateOptions<T>` (not `ValueTask`)
- `IAsyncStartupValidator` as a separate interface (not extending
`IStartupValidator`)
- Async lambda `Validate` overloads on `OptionsBuilder<T>` accept
`Func<TOptions, ..., CancellationToken, Task<bool>>`
- `StartupValidator` implements both `IStartupValidator` and
`IAsyncStartupValidator`, registered as a single service
---------
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
0 commit comments