OpenAPI: OpenApiSchemaReference in properties throws InvalidOperationException
- 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
Assessment
This issue has not been assessed yet.