Description
Is there an existing issue for this?
- I have searched the existing issues
Describe the bug
Description
The OpenAPI generator in ASP.NET Core produces an incorrect schema when a DTO contains multiple properties of the same type.
Instead of reusing the same schema, it creates a duplicate (ChangesetIntDto2
). Additionally, it generates an invalid $ref
, making the OpenAPI document invalid and unusable.
This occurs in both classic and minimal ASP.NET Core project using Microsoft.Extensions.ApiDescription.Server
for OpenAPI generation.
Steps to reproduce
1. Minimal Program.cs
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
var app = builder.Build();
app.MapOpenApi();
app.UseHttpsRedirection();
app.MapGet("/repro", () =>
{
return Enumerable.Range(1, 5).Select(_ =>
new ChangesetDescDto
{
Prop1 = new ChangesetIntDto { Added = [] },
Prop2 = new ChangesetIntDto { Added = [] },
}).ToArray();
}).WithName("reproendpoint");
app.Run();
2. DTOs
public sealed class ChangesetDescDto
{
[JsonPropertyName("value1")]
public required ChangesetIntDto Prop1 { get; init; }
[JsonPropertyName("value2")]
public required ChangesetIntDto Prop2 { get; init; }
}
public sealed class ChangesetIntDto
{
[JsonPropertyName("added")]
public required List<int> Added { get; init; }
}
3. Project Configuration (.csproj
)
Using Microsoft.Extensions.ApiDescription.Server
with OpenAPI document generation:
<!-- For build time OpenAPI JSON generation -->
<PropertyGroup>
<OpenApiDocumentsDirectory>.</OpenApiDocumentsDirectory>
<OpenApiGenerateDocumentsOptions>--file-name repro-open-api</OpenApiGenerateDocumentsOptions>
</PropertyGroup>
4. Generate OpenAPI JSON
After running the project and generating the OpenAPI document, the following incorrect schema is produced.
Expected behavior
- Both
value1
andvalue2
should referenceChangesetIntDto
. - No duplicate schema (
ChangesetIntDto2
). - No invalid
$ref
values.
Actual behavior
value1
correctly referencesChangesetIntDto
, butvalue2
referencesChangesetIntDto2
(a duplicated schema).- An invalid
$ref
is generated:"$ref": "#/components/schemas/#/items/properties/value1/properties/added"
Example of incorrect OpenAPI output
"components": {
"schemas": {
"ChangesetDescDto": {
"required": ["value1", "value2"],
"type": "object",
"properties": {
"value1": {
"$ref": "#/components/schemas/ChangesetIntDto"
},
"value2": {
"$ref": "#/components/schemas/ChangesetIntDto2"
}
}
},
"ChangesetIntDto": {
"required": ["added"],
"type": "object",
"properties": {
"added": {
"type": "array",
"items": {
"type": "integer",
"format": "int32"
}
}
}
},
"ChangesetIntDto2": {
"required": ["added"],
"type": "object",
"properties": {
"added": {
"$ref": "#/components/schemas/#/items/properties/value1/properties/added"
}
}
}
}
}
Annoyance level :-)
- Breaks OpenAPI validation due to an invalid
$ref
entry. - Makes the schema unusable for client code generation (TypeScript, C#).
- Introduces unnecessary schema duplication, which is inefficient and confusing.
Environment
- .NET 9
- ASP.NET Core
- OpenAPI generation using
builder.Services.AddOpenApi()
Microsoft.Extensions.ApiDescription.Server
- Build-time OpenAPI document generation enabled in
.csproj
Thanks.
Steps To Reproduce
See following minimal specific -only for you ❤- repo for reproduction : very simple and focus only on this issue with minimal code. Just Build to regenerate repro-open-api.json (but it is included by default if needed)
https://github.com/Berreip/repro_openApi_issue
thanks :-)
.NET Version
9.0.102
Re-tested on :
- 9.0.2
Anything else?
dotnet --info
SDK .NET :
Version: 9.0.102
Commit: cb83cd4923
Workload version: 9.0.100-manifests.43af17c7
MSBuild version: 17.12.18+ed8c6aec5
Environnement d'exécution :
OS Name: Windows
OS Version: 10.0.26100
OS Platform: Windows
RID: win-x64
Base Path: C:\Program Files\dotnet\sdk\9.0.102\
Charges de travail .NET installées :
[ios]
Source de l’installation: VS 17.9.34616.47
Version de manifeste: 18.0.9617/9.0.100
Chemin d'accès au Manifeste: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.ios\18.0.9617\WorkloadManifest.json
Type d'installation: Msi
[maui-windows]
Source de l’installation: VS 17.9.34616.47
Version de manifeste: 9.0.0/9.0.100
Chemin d'accès au Manifeste: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.maui\9.0.0\WorkloadManifest.json
Type d'installation: Msi
[wasm-tools]
Source de l’installation: VS 17.9.34616.47
Version de manifeste: 9.0.1/9.0.100
Chemin d'accès au Manifeste: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.workload.mono.toolchain.current\9.0.1\WorkloadManifest.json
Type d'installation: Msi
[android]
Source de l’installation: VS 17.9.34616.47
Version de manifeste: 35.0.7/9.0.100
Chemin d'accès au Manifeste: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.android\35.0.7\WorkloadManifest.json
Type d'installation: Msi
[maccatalyst]
Source de l’installation: VS 17.9.34616.47
Version de manifeste: 18.0.9617/9.0.100
Chemin d'accès au Manifeste: C:\Program Files\dotnet\sdk-manifests\9.0.100\microsoft.net.sdk.maccatalyst\18.0.9617\WorkloadManifest.json
Type d'installation: Msi
Configuré pour utiliser loose manifests lors de l’installation de nouveaux manifestes.
Host:
Version: 9.0.1
Architecture: x64
Commit: c8acea2262
.NET SDKs installed:
2.1.202 [C:\Program Files\dotnet\sdk]
2.1.526 [C:\Program Files\dotnet\sdk]
5.0.416 [C:\Program Files\dotnet\sdk]
8.0.200 [C:\Program Files\dotnet\sdk]
8.0.206 [C:\Program Files\dotnet\sdk]
9.0.102 [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 3.1.32 [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.27 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 7.0.16 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.AspNetCore.App 9.0.1 [C:\Program Files\dotnet\shared\Microsoft.AspNetCore.App]
Microsoft.NETCore.App 2.0.9 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 2.1.30 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 3.1.32 [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.27 [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.16 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.NETCore.App 9.0.1 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App]
Microsoft.WindowsDesktop.App 3.1.32 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 5.0.17 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 6.0.27 [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.16 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.2 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 8.0.6 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App]
Microsoft.WindowsDesktop.App 9.0.1 [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:
Not set
global.json file:
Not found