dotnet / dotnet/aspnetcore

OpenAPI: OpenApiSchemaReference in properties throws InvalidOperationException

Open
#65,600 4 comments 4 reactions 1 assignee Claimed by @Youssef1313 View on GitHub
area-minimal feature-openapi
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Describe the bug

When generating OpenAPI via `Microsoft.AspNetCore.OpenApi` and replacing schemas with `OpenApiSchemaReference` in a property, referencing a component schema from a property schema causes a runtime exception.

I investigated further and found that referencing works only if I set OpenApiSchema.Metadata["x-schema-id"] to the component id, which looks like an internal workaround rather than an intended public API.

Or did I overlook a public API that supports manual referencing in properties?

### Expected Behavior

Developers should be able to reference another schema in an intended supported public API without exceptions. For example:

- `OpenApiSchemaReference` in properties should be respected, so that developers may specify the referencing manually.
- If `x-schema-id` is required, it should be documented as a supported contract, and there should be some clear public API for creating schema references by component id.

Additionally, I noticed that [the document](https://learn.microsoft.com/en-us/aspnet/core/fundamentals/openapi/customize-openapi?view=aspnetcore-10.0#customize-schema-reuse) demonstrates:

> The details of this processing are complicated and might change in future versions of .NET, but in general:
> - Schemas for class/record/struct types are replaced with a `$ref` to a schema in `components.schemas` if they appear more than once in the document.

Does this mean I shouldn’t manually modify the schema to reference it, and instead should rely on the OpenAPI framework as much as possible to automatically extract shared components and generate the corresponding references?

### Steps To Reproduce

Create a Web API project, and replace the Program.cs with:

```csharp
using System.Text.Json.Nodes;
using Microsoft.AspNetCore.OpenApi;
using Microsoft.OpenApi;

WebApplicationBuilder builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi(o =>
{
o.AddDocumentTransformer();
o.AddSchemaTransformer();
});
WebApplication app = builder.Build();
app.MapOpenApi();
app.MapGet("/test", () => new ApiResponse(default));
await app.RunAsync();

///
/// Represents a response from the API, containing a response code and an optional message.
///
/// The response code indicating the result of the API call.
public record ApiResponse(int Code);

internal class OpenApiResponseCodeTransformer : IOpenApiSchemaTransformer, IOpenApiDocumentTransformer
{
private const string ReferenceId = "ResponseCode";

///
public Task TransformAsync(OpenApiSchema schema, OpenApiSchemaTransformerContext context,
CancellationToken cancellationToken)
{
if (context.JsonTypeInfo.Type != typeof(ApiResponse))
return Task.CompletedTask;

schema.Properties ??= new Dictionary();
// Next line causes System.InvalidOperationException is thrown: The input schema must be an OpenApiSchema or OpenApiSchemaReference.
// schema.Properties["code"] = new OpenApiSchemaReference(ReferenceId);
// I need to directly modify the metadata to make the code property reference the ResponseCode schema
(schema.Properties["code"] as OpenApiSchema)?.Metadata?["x-schema-id"] = ReferenceId;
return Task.CompletedTask;
}

///
public Task TransformAsync(OpenApiDocument document, OpenApiDocumentTransformerContext context,
CancellationToken cancellationToken)
{
OpenApiSchema responseCodeSchema = new()
{
Type = JsonSchemaType.Integer,
Format = "int32",
Description = "The response code.",
Enum = Enumerable.Range(0, 10).Select(JsonNode(x) => JsonValue.Create(x)).ToList()
};
document?.AddComponent(ReferenceId, responseCodeSchema);
return Task.CompletedTask;
}
}

```

### Exceptions (if any)

```log
An unhandled exception has occurred while executing the request.
System.InvalidOperationException: The input schema must be an OpenApiSchema or OpenApiSchemaReference.
at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.UnwrapOpenApiSchema(IOpenApiSchema sourceSchema)
at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.InnerApplySchemaTransformersAsync(IOpenApiSchema inputSchema, JsonTypeInfo jsonTypeInfo, JsonPropertyInfo jsonPropertyInfo, OpenApiSchemaTransformerContext context, IOpenApiSchemaTransformer transformer, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.InnerApplySchemaTransformersAsync(IOpenApiSchema inputSchema, JsonTypeInfo jsonTypeInfo, JsonPropertyInfo jsonPropertyInfo, OpenApiSchemaTransformerContext context, IOpenApiSchemaTransformer transformer, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.ApplySchemaTransformersAsync(OpenApiDocument document, IOpenApiSchema schema, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription parameterDescription, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.GetOrCreateUnresolvedSchemaAsync(OpenApiDocument document, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription parameterDescription, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiSchemaService.GetOrCreateSchemaAsync(OpenApiDocument document, Type type, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, ApiParameterDescription parameterDescription, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetResponseAsync(OpenApiDocument document, ApiDescription apiDescription, Int32 statusCode, ApiResponseType apiResponseType, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetResponsesAsync(OpenApiDocument document, ApiDescription description, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationAsync(ApiDescription description, OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOperationsAsync(IGrouping`2 descriptions, OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiPathsAsync(OpenApiDocument document, IServiceProvider scopedServiceProvider, IOpenApiOperationTransformer[] operationTransformers, IOpenApiSchemaTransformer[] schemaTransformers, CancellationToken cancellationToken)
at Microsoft.AspNetCore.OpenApi.OpenApiDocumentService.GetOpenApiDocumentAsync(IServiceProvider scopedServiceProvider, HttpRequest httpRequest, CancellationToken cancellationToken)
at Microsoft.AspNetCore.Builder.OpenApiEndpointRouteBuilderExtensions.<>c__DisplayClass0_0.<b__0>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Http.Generated.F0399B8B15BE443123F4BECEE84B112C1912EE3F44D9DDD7BFA38E371FB917343__GeneratedRouteBuilderExtensionsCore.<>c__DisplayClass2_0.<g__RequestHandler|5>d.MoveNext()
--- End of stack trace from previous location ---
at Microsoft.AspNetCore.Diagnostics.DeveloperExceptionPageMiddlewareImpl.Invoke(HttpContext context)

```

### .NET Version

11.0.100-preview.1.26104.118

### Anything else?

- The project is targeting .NET 10
- `Microsoft.AspNetCore.OpenApi` version: 10.0.3
- JetBrains Rider 2025.3.3
Build #RD-253.31033.136, built on February 19, 2026
Source revision: 565764487c99e

dotnet --info

```bash

dotnet --info
.NET SDK:
Version: 11.0.100-preview.1.26104.118
Commit: 87bc0b04e2
Workload version: 11.0.100-manifests.68dce6a7
MSBuild version: 18.4.0-preview-26104-118+87bc0b04e

运行时环境:
OS Name: Windows
OS Version: 10.0.26200
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\11.0.100-preview.1.26104.118\

已安装 .NET 工作负载:
[maccatalyst]
安装源文件: VS 18.3.11512.155
清单版本: 26.2.11310-net11-p1/11.0.100-preview.1
清单路径: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.maccatalyst\26.2.11310-net11-p1\WorkloadManifest.json
安装类型: Msi

[ios]
安装源文件: VS 18.3.11512.155
清单版本: 26.2.11310-net11-p1/11.0.100-preview.1
清单路径: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.ios\26.2.11310-net11-p1\WorkloadManifest.json
安装类型: Msi

[android]
安装源文件: VS 18.3.11512.155
清单版本: 36.1.99-preview.1.119/11.0.100-preview.1
清单路径: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.android\36.1.99-preview.1.119\WorkloadManifest.json
安装类型: Msi

[wasm-tools-net6]
安装源文件: VS 18.3.11512.155
清单版本: 11.0.100-preview.1.26104.118/11.0.100-preview.1
清单路径: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.workload.mono.toolchain.net6\11.0.100-preview.1.26104.118\WorkloadManifest.json
安装类型: Msi

[wasm-tools-net7]
安装源文件: VS 18.3.11512.155
清单版本: 11.0.100-preview.1.26104.118/11.0.100-preview.1
清单路径: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.workload.mono.toolchain.net7\11.0.100-preview.1.26104.118\WorkloadManifest.json
安装类型: Msi

[wasm-tools]
安装源文件: VS 18.3.11512.155
清单版本: 11.0.100-preview.1.26104.118/11.0.100-preview.1
清单路径: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.workload.mono.toolchain.current\11.0.100-preview.1.26104.118\WorkloadManifest.json
安装类型: Msi

[maui-windows]
安装源文件: VS 18.3.11512.155
清单版本: 11.0.0-preview.1.26102.3/11.0.100-preview.1
清单路径: C:\Program Files\dotnet\sdk-manifests\11.0.100-preview.1\microsoft.net.sdk.maui\11.0.0-preview.1.26102.3\WorkloadManifest.json
安装类型: Msi

已配置为在安装新清单时使用 workload sets。
未安装任何 workload sets。运行 “dotnet workload restore” 以安装工作负载集。

Host:
Version: 11.0.0-preview.1.26104.118
Architecture: x64
Commit: 87bc0b04e2

.NET SDKs installed:
5.0.408 [C:\Program Files\dotnet\sdk]
6.0.428 [C:\Program Files\dotnet\sdk]
7.0.410 [C:\Program Files\dotnet\sdk]
8.0.418 [C:\Program Files\dotnet\sdk]
10.0.100-preview.1.25120.13 [C:\Program Files\dotnet\sdk]
10.0.100-rc.1.25451.107 [C:\Program Files\dotnet\sdk]
10.0.103 [C:\Program Files\dotnet\sdk]
11.0.100-preview.1.26104.118 [C:\Program Files\dotnet\sdk]

.NET runtimes installed:
Microsoft.AspNetCore.All 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.All]
Microsoft.AspNetCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.24 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 9.0.13 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 10.0.3 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 11.0.0-preview.1.26104.118 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.24 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 9.0.13 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.0-rc.1.25451.107 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 10.0.3 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 11.0.0-preview.1.26104.118 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.36 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 7.0.20 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.24 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 9.0.13 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 10.0.0-rc.1.25451.107 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 10.0.3 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 11.0.0-preview.1.26104.118 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]

Other architectures found:
x86 [C:\Program Files (x86)\dotnet]
registered at [HKLM\SOFTWARE\dotnet\Setup\InstalledVersions\x86\InstallLocation]

Environment variables:
DOTNET_ENVIRONMENT [Development]
DOTNET_SYSTEM_GLOBALIZATION_INVARIANT [false]

global.json file:
Not found

Learn more:
https://aka.ms/dotnet/info

Download .NET:
https://aka.ms/dotnet/download
```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.