Skip to content

API Proposal: Microsoft.AspNetCore.OpenAPI Not Extensible Due To Internal Types #66408

@commonsensesoftware

Description

@commonsensesoftware

Background and Motivation

The Microsoft.AspNetCore.OpenAPI library provides a number of useful extensions for generating OpenAPI documents, but it does not work well with ASP.NET API Versioning out-of-the-box. ASP.NET API Versioning has provided a new library to bridge the gaps, but it is forced to use unwieldly Reflection techniques to make things work because all of the necessary extension points in Microsoft.AspNetCore.OpenAPI are internal.

While the Reflection workaround is a short-term fix, it is significantly slower, complicates or breaks the AOT story, and places the extensions at future risk of non-visible changes that may only trigger runtime failures. All of these reflect negatively upon developer and customer experience.

These changes are requested to be part of a .NET 10 servicing release. .NET 11 and beyond should consider an alternate design.

Proposed API

using System.ComponentModel;

namespace Microsoft.AspNetCore.OpenApi;

+ [EditorBrowsable(EditorBrowsableState.Never)]
- internal sealed class OpenApiSchemaService( /* ... */ )
+ public sealed class OpenApiSchemaService( /* ... */ )
  {
  }

+ [EditorBrowsable(EditorBrowsableState.Never)]
- internal interface IDocumentProvider
+ public interface IDocumentProvider
  {
  }

+ [EditorBrowsable(EditorBrowsableState.Never)]
- internal sealed class NamedService<TService>(string name)
+ public sealed class NamedService<TService>(string name)
  {
  }

+ [EditorBrowsable(EditorBrowsableState.Never)]
- internal sealed class OpenApiDocumentProvider(IServiceProvider serviceProvider) : IDocumentProvider
+ public sealed class OpenApiDocumentProvider(IServiceProvider serviceProvider) : IDocumentProvider
  {
  }

+ [EditorBrowsable(EditorBrowsableState.Never)]
- internal sealed class OpenApiDocumentService( /* .. */ ) : IOpenApiDocumentProvider
+ public sealed class OpenApiDocumentService( /* .. */ ) : IOpenApiDocumentProvider
  {
-     public async Task<OpenApiDocument> GetOpenApiDocumentAsync( /* .. */ )
+     internal async Task<OpenApiDocument> GetOpenApiDocumentAsync( /* .. */ )
      {
      }
  }

  public sealed class OpenApiOptions
  {
-    public string DocumentName { get; internal set; } = OpenApiConstants.DefaultDocumentName;        
+    public string DocumentName
+    {
+        get => field ?? OpenApiConstants.DefaultDocumentName;
+
+        [EditorBrowsable(EditorBrowsableState.Never)]
+        set;
+    }
  }

The changes in this commit, which is a fork of the library, demonstrate the required changes. If this were accepted, the changes can be submitted as a pull request.

Usage Examples

The changes in this commit demonstrate how API Versioning would use these changes and supplant the need to use Reflection.

The necessary changes were privately built and consumed by API Versioning to confirm the proposed changes will indeed work if they are approved and released.

Alternative Designs

There is a potential alternate design that would take the existing design limitations into consideration, but this would require significantly more time and effort. A refactored, alternate design should be tracked in a separate issue and considered for a future major release in .NET 11 or beyond.

Risks

  • internal types are now public; however, risk is minimized by:
    • Only the type and constructor are public; all other members remain or become internal
    • The [EditorBrowsable(EditorBrowsableState.Never)] can be applied to reduce their accidental discovery
  • IDocumentProvider is a magical internal interface that is matched only by type name
    • This interface has to become public is it can be mapped by dependency injection
    • There may be some other combination or design that limits unintended usage

Metadata

Metadata

Assignees

No one assigned

    Labels

    NativeAOTapi-proposalapi-suggestionEarly API idea and discussion, it is NOT ready for implementationarea-minimalIncludes minimal APIs, endpoint filters, parameter binding, request delegate generator etc

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions