diff --git a/docs/core/compatibility/11.md b/docs/core/compatibility/11.md index 2ba79f955a4ae..a1ef4f02af5fc 100644 --- a/docs/core/compatibility/11.md +++ b/docs/core/compatibility/11.md @@ -71,6 +71,7 @@ See [Breaking changes in EF Core 11](/ef/core/what-is-new/ef-core-11.0/breaking- | [IHost.RunAsync and IHost.StopAsync throw when a BackgroundService fails](extensions/11/ihost-runasync-stopasync-throw-backgroundservice-failure.md) | Behavioral change | | [PhysicalFilesWatcher validates root and FileSystemWatcher paths](extensions/11/physicalfileswatcher-validates-root-path.md) | Behavioral change | | [Some Microsoft.Extensions packages included in shared framework](extensions/11/extensions-in-shared-framework.md) | Behavioral change | +| [Synchronous access to async-validated options throws](extensions/11/async-options-validation-sync-access.md) | Behavioral/source incompatible | ## Globalization diff --git a/docs/core/compatibility/extensions/11/async-options-validation-sync-access.md b/docs/core/compatibility/extensions/11/async-options-validation-sync-access.md new file mode 100644 index 0000000000000..2be7f183e3c26 --- /dev/null +++ b/docs/core/compatibility/extensions/11/async-options-validation-sync-access.md @@ -0,0 +1,81 @@ +--- +title: "Breaking change: Synchronous access to async-validated options throws" +description: "Learn about the breaking change in .NET 11 where synchronous access to options that use async validators throws instead of skipping validation." +ms.date: 09/08/2026 +ai-usage: ai-assisted +--- + +# Synchronous access to async-validated options throws + +Starting in .NET 11 RC 1, synchronous access to an options type that uses only asynchronous validators fails fast. Instead of returning an options instance without asynchronous validation, the synchronous creation path throws an . + +## Version introduced + +.NET 11 RC 1 + +## Previous behavior + +Previously, in .NET 11 Preview 6 and Preview 7, was independent from . Asynchronous validators ran only through the asynchronous startup-validation path. + +When you accessed an async-validated options type through a synchronous creation path, such as , , , `IOptionsSnapshot.Value`, , or , the asynchronous validator didn't run. The synchronous path returned an unvalidated options instance. + +Types that implemented `IAsyncValidateOptions` directly only needed to implement `ValidateAsync`. + +## New behavior + +Starting in .NET 11 RC 1, `IAsyncValidateOptions` derives from `IValidateOptions`, and the interface is no longer contravariant. Asynchronous validators participate in the same validator collection as synchronous validators. + +When you access an options type with only asynchronous validators through a synchronous creation path, the inherited `Validate` method returns a failed . then throws an `OptionsValidationException`. The exception message directs you to call `ValidateOnStart` and complete startup before you synchronously access the options. + +Custom types that implement `IAsyncValidateOptions` directly must now also implement the inherited `Validate` method. + +## Type of breaking change + +This change is a [behavioral change](../../categories.md#behavioral-change) and can affect [source compatibility](../../categories.md#source-compatibility). In a narrow scenario where a preview binary directly implements `IAsyncValidateOptions` without recompilation, the change can also affect [binary compatibility](../../categories.md#binary-compatibility). + +## Reason for change + +Asynchronous options validation was introduced in .NET 11 Preview 6 as a startup-only validation path. Later design work for post-startup validation exposed a correctness gap: Options also have synchronous creation and access paths. A validator that implemented only the async interface couldn't run through those synchronous paths, so invalid options could be returned and cached before asynchronous validation ran. + +To close that gap before the API reaches a stable release, `IAsyncValidateOptions` now derives from `IValidateOptions`. The unified contract keeps one validator collection, preserves registration order, and makes unsupported synchronous access fail with an actionable exception. For more information, see [dotnet/runtime#131197](https://github.com/dotnet/runtime/pull/131197) and the [approved API proposal](https://github.com/dotnet/runtime/issues/130719). + +## Recommended action + +For options that use only asynchronous validators, call `ValidateOnStart` and complete host startup before you access the options synchronously: + +```csharp +services.AddOptions() + .Configure(o => o.Value = 42) + .ValidateAsync(o => Task.FromResult(o.Value > 0), "Value must be positive.") + .ValidateOnStart(); + +await host.StartAsync(); +``` + +Avoid synchronous access to options with only asynchronous validators before startup completes. This guidance applies to `IOptions.Value`, `IOptionsMonitor.CurrentValue`, `IOptionsMonitor.Get`, `IOptionsSnapshot.Value`, `IOptionsSnapshot.Get`, and `IOptionsFactory.Create`. + +Some paths remain synchronous even after you use `ValidateOnStart`. Startup validation doesn't seed `IOptionsSnapshot` values for later scopes, and `IOptionsMonitor` recreates options synchronously after a configuration change. If you need those paths to validate successfully, keep at least one synchronous validator. + +If you implement `IAsyncValidateOptions` directly, add the inherited `Validate(string? name, TOptions options)` method and recompile against .NET 11. Return when the validator doesn't apply, or return when synchronous validation isn't supported. + +If your code relied on the removed `in TOptions` contravariance, update the affected assignments, casts, or registrations. + +You can't control this behavior with an AppContext switch or configuration setting. + +## Affected APIs + +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- `ValidateAsync` extension methods on `OptionsBuilder`. diff --git a/docs/core/compatibility/toc.yml b/docs/core/compatibility/toc.yml index 8f456b413717f..e9e3443cd125e 100644 --- a/docs/core/compatibility/toc.yml +++ b/docs/core/compatibility/toc.yml @@ -72,6 +72,8 @@ items: href: extensions/11/physicalfileswatcher-validates-root-path.md - name: Some Microsoft.Extensions packages included in shared framework href: extensions/11/extensions-in-shared-framework.md + - name: Synchronous access to async-validated options throws + href: extensions/11/async-options-validation-sync-access.md - name: Globalization items: - name: Japanese Calendar minimum supported date corrected