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
Asynchronous options validation is a new .NET 11 feature (introduced in Preview 6 by dotnet/runtime#128788). Before it shipped in a stable release, .NET 11 RC 1 reworks its contract: Microsoft.Extensions.Options now unifies the synchronous and asynchronous options-validation contracts. IAsyncValidateOptions<TOptions> now derives from IValidateOptions<TOptions>, so asynchronous validators participate in the same validator collection as synchronous ones. As a result, when an options type is validated by an asynchronous-only validator and is accessed through a synchronous creation path, options creation now fails fast with an OptionsValidationException instead of silently returning an unvalidated instance.
This is a prerelease-to-prerelease change that affects code written against the .NET 11 Preview 6/7 async-validation API; it does not affect any stable (RTM) release. Introduced by dotnet/runtime#131197; approved API proposal dotnet/runtime#130719 (originally approved as dotnet/runtime#128100). Related follow-up proposal (not part of this change): dotnet/runtime#131906.
Version
.NET 11 RC 1
Previous behavior
The behavior below is the .NET 11 Preview 6/7 behavior of the then-new async options-validation API, so this migration targets preview adopters of that feature—not consumers of any stable release.
IAsyncValidateOptions<TOptions> was an independent, contravariant (in TOptions) interface, separate from IValidateOptions<TOptions>. Async validators ran only on the asynchronous startup-validation path.
On synchronous creation paths—IOptions<TOptions>.Value, IOptionsMonitor<TOptions>.CurrentValue / .Get(name), IOptionsSnapshot<TOptions>.Value, and IOptionsFactory<TOptions>.Create(name)—async validators were not executed. The options instance was returned unvalidated (validation was silently skipped).
A type could implement IAsyncValidateOptions<TOptions> by providing only ValidateAsync.
services.AddOptions<MyOptions>().Configure(o =>o.Value=-1).ValidateAsync(o =>Task.FromResult(o.Value>0),"Value must be positive.");// Synchronous access: The async validator was skipped, so an invalid instance// was returned silently with no exception.MyOptionsvalue=provider.GetRequiredService<IOptions<MyOptions>>().Value;// Value == -1
New behavior
IAsyncValidateOptions<TOptions> now derives from IValidateOptions<TOptions>, and the in contravariance is removed.
On a synchronous creation path, the inherited Validate returns a failed ValidateOptionsResult, so OptionsFactory<TOptions> throws OptionsValidationException. The message directs you to run ValidateOnStart and complete startup before synchronously accessing the options.
The built-in AsyncValidateOptions<TOptions, ...> types now implement the synchronous Validate method (fail-fast). Custom types that implement IAsyncValidateOptions<TOptions>directly must now also implement Validate (inherited from IValidateOptions<TOptions>).
services.AddOptions<MyOptions>().Configure(o =>o.Value=-1).ValidateAsync(o =>Task.FromResult(o.Value>0),"Value must be positive.");// Synchronous access now throws instead of returning an unvalidated instance.MyOptionsvalue=provider.GetRequiredService<IOptions<MyOptions>>().Value;// Throws OptionsValidationException:// "Options instance of type 'MyOptions' is validated by an asynchronous validator that// cannot run during synchronous validation. ... use ValidateOnStart and complete startup// before synchronously accessing IOptions<TOptions> or IOptionsMonitor<TOptions> ..."
Recommended pattern:
services.AddOptions<MyOptions>().Configure(o =>o.Value=42).ValidateAsync(o =>Task.FromResult(o.Value>0),"Value must be positive.").ValidateOnStart();// Runs async validation during host startup.usingIHosthost=builder.Build();awaithost.StartAsync();// Async validation completes here and seeds the validated instance.// Later synchronous IOptions<MyOptions>.Value access returns the already-validated instance.
Type of breaking change
Behavioral change: Existing binaries might behave differently at run time. Synchronous access of an async-validated options type now throws OptionsValidationException instead of returning an unvalidated instance.
Source incompatible: IAsyncValidateOptions<TOptions> now derives from IValidateOptions<TOptions> and drops in TOptions contravariance. Types that implement the interface directly must add a Validate method, and code relying on the contravariant conversion might no longer compile.
A preview binary that implements IAsyncValidateOptions<TOptions> directly (only ValidateAsync) can also fail to load against the new assembly until recompiled, so the change is binary-incompatible in that narrow direct-implementer scenario. The common consumer scenario (registering validators through the built-in APIs) is a behavioral change only. All impact is scoped to code built against the .NET 11 Preview 6/7 async API.
Reason for change
Asynchronous options validation was originally approved (dotnet/runtime#128100) as a startup-only, independent async counterpart to IValidateOptions<TOptions>. Implementing post-startup revalidation exposed a correctness gap: Options have synchronous entry points (IOptions<T>.Value, IOptionsSnapshot<T>.Get, IOptionsMonitor<T>.Get, IOptionsFactory<T>.Create), and a validator that implemented only the async interface had no synchronous contract to run through. A value could therefore be returned and cached before any async validation ran, silently bypassing validation. Unifying IAsyncValidateOptions<TOptions> under IValidateOptions<TOptions> gives a single validator collection (preserving registration order and single invocation) and makes the previously silent gap explicit: Synchronous access of an async-validated type now fails fast with an actionable message. Because the async API had not yet reached a stable release, the contract was corrected before shipping. See the approved proposal dotnet/runtime#130719.
Recommended action
Call .ValidateOnStart() on the options builder and run async validation during host startup (await host.StartAsync()). This validates and seeds the initial IOptions<TOptions> / IOptionsMonitor<TOptions> instance so later synchronous access succeeds.
Avoid synchronously accessing (IOptions<T>.Value, IOptionsMonitor<T>.CurrentValue / .Get) an options type that has only asynchronous validators before startup has completed.
Be aware of the paths ValidateOnStart does not cover for async-validated types: IOptionsSnapshot<T> is validated synchronously per scope and is not seeded by startup validation, and IOptionsMonitor<T> recreation after a configuration change is also synchronous. Accessing those paths for an async-only-validated type still throws; keep at least one synchronous validator if you need those paths validated.
If you implement IAsyncValidateOptions<TOptions> directly, add the now-required synchronous Validate(string?, TOptions) method (return ValidateOptionsResult.Skip, or Fail to signal that synchronous validation is unsupported, mirroring the built-in AsyncValidateOptions<TOptions>), and recompile against .NET 11.
If you relied on the removed in TOptions contravariance of IAsyncValidateOptions<TOptions>, adjust the affected code.
There is no AppContext compatibility switch; the supported migration is ValidateOnStart plus completing startup.
Feature area
Extensions
Affected APIs
Microsoft.Extensions.Options.IAsyncValidateOptions<TOptions>—now derives from Microsoft.Extensions.Options.IValidateOptions<TOptions>; in TOptions contravariance removed.
Microsoft.Extensions.Options.AsyncValidateOptions<TOptions> and its dependency overloads AsyncValidateOptions<TOptions, TDep> through AsyncValidateOptions<TOptions, TDep1, TDep2, TDep3, TDep4, TDep5>—now expose ValidateOptionsResult Validate(string?, TOptions).
Related registration APIs (async validators now register under IValidateOptions<TOptions>): Microsoft.Extensions.Options.OptionsBuilder<TOptions>.ValidateAsync(...) and Microsoft.Extensions.DependencyInjection.OptionsBuilderAsyncValidationExtensions.
Note
This issue was drafted with assistance from GitHub Copilot.
Description
Asynchronous options validation is a new .NET 11 feature (introduced in Preview 6 by dotnet/runtime#128788). Before it shipped in a stable release, .NET 11 RC 1 reworks its contract:
Microsoft.Extensions.Optionsnow unifies the synchronous and asynchronous options-validation contracts.IAsyncValidateOptions<TOptions>now derives fromIValidateOptions<TOptions>, so asynchronous validators participate in the same validator collection as synchronous ones. As a result, when an options type is validated by an asynchronous-only validator and is accessed through a synchronous creation path, options creation now fails fast with anOptionsValidationExceptioninstead of silently returning an unvalidated instance.This is a prerelease-to-prerelease change that affects code written against the .NET 11 Preview 6/7 async-validation API; it does not affect any stable (RTM) release. Introduced by dotnet/runtime#131197; approved API proposal dotnet/runtime#130719 (originally approved as dotnet/runtime#128100). Related follow-up proposal (not part of this change): dotnet/runtime#131906.
Version
.NET 11 RC 1
Previous behavior
The behavior below is the .NET 11 Preview 6/7 behavior of the then-new async options-validation API, so this migration targets preview adopters of that feature—not consumers of any stable release.
IAsyncValidateOptions<TOptions>was an independent, contravariant (in TOptions) interface, separate fromIValidateOptions<TOptions>. Async validators ran only on the asynchronous startup-validation path.IOptions<TOptions>.Value,IOptionsMonitor<TOptions>.CurrentValue/.Get(name),IOptionsSnapshot<TOptions>.Value, andIOptionsFactory<TOptions>.Create(name)—async validators were not executed. The options instance was returned unvalidated (validation was silently skipped).IAsyncValidateOptions<TOptions>by providing onlyValidateAsync.New behavior
IAsyncValidateOptions<TOptions>now derives fromIValidateOptions<TOptions>, and theincontravariance is removed.Validatereturns a failedValidateOptionsResult, soOptionsFactory<TOptions>throwsOptionsValidationException. The message directs you to runValidateOnStartand complete startup before synchronously accessing the options.AsyncValidateOptions<TOptions, ...>types now implement the synchronousValidatemethod (fail-fast). Custom types that implementIAsyncValidateOptions<TOptions>directly must now also implementValidate(inherited fromIValidateOptions<TOptions>).Recommended pattern:
Type of breaking change
OptionsValidationExceptioninstead of returning an unvalidated instance.IAsyncValidateOptions<TOptions>now derives fromIValidateOptions<TOptions>and dropsin TOptionscontravariance. Types that implement the interface directly must add aValidatemethod, and code relying on the contravariant conversion might no longer compile.Reason for change
Asynchronous options validation was originally approved (dotnet/runtime#128100) as a startup-only, independent async counterpart to
IValidateOptions<TOptions>. Implementing post-startup revalidation exposed a correctness gap: Options have synchronous entry points (IOptions<T>.Value,IOptionsSnapshot<T>.Get,IOptionsMonitor<T>.Get,IOptionsFactory<T>.Create), and a validator that implemented only the async interface had no synchronous contract to run through. A value could therefore be returned and cached before any async validation ran, silently bypassing validation. UnifyingIAsyncValidateOptions<TOptions>underIValidateOptions<TOptions>gives a single validator collection (preserving registration order and single invocation) and makes the previously silent gap explicit: Synchronous access of an async-validated type now fails fast with an actionable message. Because the async API had not yet reached a stable release, the contract was corrected before shipping. See the approved proposal dotnet/runtime#130719.Recommended action
.ValidateOnStart()on the options builder and run async validation during host startup (await host.StartAsync()). This validates and seeds the initialIOptions<TOptions>/IOptionsMonitor<TOptions>instance so later synchronous access succeeds.IOptions<T>.Value,IOptionsMonitor<T>.CurrentValue/.Get) an options type that has only asynchronous validators before startup has completed.ValidateOnStartdoes not cover for async-validated types:IOptionsSnapshot<T>is validated synchronously per scope and is not seeded by startup validation, andIOptionsMonitor<T>recreation after a configuration change is also synchronous. Accessing those paths for an async-only-validated type still throws; keep at least one synchronous validator if you need those paths validated.IAsyncValidateOptions<TOptions>directly, add the now-required synchronousValidate(string?, TOptions)method (returnValidateOptionsResult.Skip, orFailto signal that synchronous validation is unsupported, mirroring the built-inAsyncValidateOptions<TOptions>), and recompile against .NET 11.in TOptionscontravariance ofIAsyncValidateOptions<TOptions>, adjust the affected code.AppContextcompatibility switch; the supported migration isValidateOnStartplus completing startup.Feature area
Extensions
Affected APIs
Microsoft.Extensions.Options.IAsyncValidateOptions<TOptions>—now derives fromMicrosoft.Extensions.Options.IValidateOptions<TOptions>;in TOptionscontravariance removed.Microsoft.Extensions.Options.AsyncValidateOptions<TOptions>and its dependency overloadsAsyncValidateOptions<TOptions, TDep>throughAsyncValidateOptions<TOptions, TDep1, TDep2, TDep3, TDep4, TDep5>—now exposeValidateOptionsResult Validate(string?, TOptions).Microsoft.Extensions.Options.IOptions<TOptions>.ValueMicrosoft.Extensions.Options.IOptionsMonitor<TOptions>.CurrentValueMicrosoft.Extensions.Options.IOptionsMonitor<TOptions>.Get(System.String)Microsoft.Extensions.Options.IOptionsSnapshot<TOptions>.ValueMicrosoft.Extensions.Options.IOptionsSnapshot<TOptions>.Get(System.String)Microsoft.Extensions.Options.IOptionsFactory<TOptions>.Create(System.String)Microsoft.Extensions.Options.OptionsFactory<TOptions>.Create(System.String)IValidateOptions<TOptions>):Microsoft.Extensions.Options.OptionsBuilder<TOptions>.ValidateAsync(...)andMicrosoft.Extensions.DependencyInjection.OptionsBuilderAsyncValidationExtensions.Note
This issue was drafted with assistance from GitHub Copilot.
Associated WorkItem - 630670