Skip to content

[API Proposal]: Obsolete IStartupValidator in favor of IAsyncStartupValidator #131906

Closed

Description

@ViveliDuCh

Background and motivation

This proposal originated in a follow-up by @halter73 on #130719 and is split out here for API review.

#130719 approved making IAsyncStartupValidator derive from IStartupValidator. During implementation in #131197, we found that startup validation does not need the same relationship as options validation.

IAsyncValidateOptions<TOptions> must support both synchronous and asynchronous entry points because options can be retrieved synchronously. Host startup orchestration, however, is already asynchronous through Host.StartAsync. Requiring IAsyncStartupValidator implementations to also provide an unused Validate() method creates ambiguity about whether one or both methods run and encourages unnecessary sync-over-async implementations.

This proposal therefore:

  • Keeps IAsyncStartupValidator independent from IStartupValidator.
  • Obsoletes IStartupValidator in favor of the asynchronous contract.
  • Preserves existing IStartupValidator behavior for compatibility.
  • Allows all registered IAsyncStartupValidator implementations to run.

The IAsyncValidateOptions<TOptions> : IValidateOptions<TOptions> portion of #130719 is unchanged.

Related discussion: #130719 (comment)

API Proposal

namespace Microsoft.Extensions.Options;

[Obsolete("Implement IAsyncStartupValidator instead.")] // NEW
public partial interface IStartupValidator
{
    // EXISTING
    // void Validate();
}

// EXISTING: remains independent of IStartupValidator.
// public interface IAsyncStartupValidator
// {
//     Task ValidateAsync(CancellationToken cancellationToken = default);
// }

ValidateOnStart continues to expose the built-in validator through IStartupValidator for compatibility. Host.StartAsync uses asynchronous startup validation while preserving the precedence of a user-provided legacy IStartupValidator.

Prototype: 4650821

API Usage

Host startup has a single asynchronous entry point:

public sealed class StartupValidator : IAsyncStartupValidator
{
    public async Task ValidateAsync(
        CancellationToken cancellationToken = default)
    {
        string state = await File.ReadAllTextAsync(
            "startup-state.txt",
            cancellationToken);

        if (string.IsNullOrWhiteSpace(state))
        {
            throw new InvalidOperationException(
                "The startup state is invalid.");
        }
    }
}

services.AddSingleton<IAsyncStartupValidator, StartupValidator>();

await host.StartAsync(cancellationToken);

Host.StartAsync invokes ValidateAsync; there is no synchronous host startup entry point that invokes Validate. With the inheritance approved in #130719, the same validator would nevertheless be forced to add a synchronous member:

// Required only because IAsyncStartupValidator derives from IStartupValidator.
public void Validate()
{
    // Host.StartAsync never calls this member. Duplicating the validation here
    // or blocking on ValidateAsync would be unnecessary and potentially unsafe.
    throw new NotSupportedException(
        "Startup validation is invoked asynchronously.");
}

Keeping the interfaces independent makes ValidateAsync the single, complete startup-validation contract and removes any ambiguity over whether Validate, ValidateAsync, or both should run. This differs from IAsyncValidateOptions<TOptions>, which still needs Validate for synchronous options access through APIs such as IOptions<TOptions>.Value.

Alternative Designs

  • Make IAsyncStartupValidator derive from IStartupValidator. This was the shape approved in [API Proposal]: Unify sync and async options validation contracts #130719, but it requires an unused synchronous method and makes invocation semantics unclear.
  • Keep both interfaces without obsoleting IStartupValidator. This provides no migration signal and allows callers to continue using a path that cannot execute asynchronous validators.
  • Add ValidateAsync directly to IStartupValidator. Adding a member to the existing interface would break current implementations.

Risks

  • Existing implementers and direct consumers of IStartupValidator receive an obsoletion warning, but retain binary and runtime compatibility.
  • Host startup must preserve the existing behavior where a custom IStartupValidator replaces the built-in validator.
  • Running every IAsyncStartupValidator registration differs from the legacy single-service behavior, but better matches normal multi-registration expectations.
  • IAsyncStartupValidator has not shipped in a stable release, so keeping it independent is a preview-only compatibility change.

Activity

  1. added
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Aug 5, 2026
  2. dotnet-policy-service commented on Aug 5, 2026

    @dotnet-policy-service
    Contributor

    Tagging subscribers to this area: @dotnet/area-extensions-options
    See info in area-owners.md if you want to be subscribed.

  3. removed
    untriagedNew issue has not been triaged by the area owner
    on Aug 6, 2026
  4. added this to the 11.0.0 milestone on Aug 6, 2026
  5. added
    blockingMarks issues that we want to fast track in order to unblock other important work
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    and removed
    api-suggestionEarly API idea and discussion, it is NOT ready for implementation
    on Aug 6, 2026
  6. removed their assignment
    on Aug 6, 2026
  7. JakeYallop commented on Aug 6, 2026

    @JakeYallop
    Contributor

    Has the split of IAsyncStartupValidator from IStartupValidator already been approved somewhere, or is that what this proposal is about, in addition to the obsoletion? (I'm just curious)

  8. bartonjs commented on Aug 11, 2026

    @bartonjs
    Member

    Video

    Looks good as proposed. (Using the next available warning number)

    namespace Microsoft.Extensions.Options;
    
    [Obsolete("Implement IAsyncStartupValidator instead.")] // NEW
    public partial interface IStartupValidator
    {
        // EXISTING
        // void Validate();
    }
    
    // EXISTING: remains independent of IStartupValidator.
    // public interface IAsyncStartupValidator
    // {
    //     Task ValidateAsync(CancellationToken cancellationToken = default);
    // }
  9. added
    api-approvedAPI was approved in API review, it can be implemented
    and removed
    blockingMarks issues that we want to fast track in order to unblock other important work
    api-ready-for-reviewAPI is ready for review, it is NOT ready for implementation
    on Aug 11, 2026
  10. locked and limited conversation to collaborators on Sep 14, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions