dotnet / dotnet/aspnetcore

OpenAPI wrong schema generation

Open
#64,325 18 comments 1 reaction 1 assignee Claimed by @Youssef1313 View on GitHub
area-minimal feature-openapi
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 6h
Merged PRs (30d)
290

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Describe the bug

With .net 9, we had Type, Type2, Type3 generated in openapi definition (using Microsoft.Extensions.ApiDescription.Server) - that was buggy but at least tractable.

With .net 10, components are emitted using type name - and not using the type - leading to wrong types being generated if multiple types have same name (not-considering namespace) and __completely silently__.

### Expected Behavior

Components shall be all generated, deduplicated and eventually named with a numbering scheme as before in case of name conflicts.

### Steps To Reproduce

# OpenApiOneFile.csproj
````


net10.0
enable
enable
.




runtime; build; native; contentfiles; analyzers; buildtransitive
all

````

# Program.cs
````
namespace SampleApiOneFile.Models {
public record User {
public required int Id { get; init; }
public required string FirstName { get; init; }
public required string LastName { get; init; }
}
}

namespace SampleApiOneFile.Models.Patch {
public record User {
public string? FirstName { get; init; }
public string? LastName { get; init; }
}
}

namespace SampleApiOneFile.Controllers {
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;

[ApiController]
[Route("[controller]")]
public partial class UserController() : ControllerBase {
[HttpGet("{id}")]
public ActionResult Get(int id) {
return new Models.User {
Id = id,
FirstName = $"FirstName {id}",
LastName = $"LastName {id}"
};
}

[HttpPatch("{id}")]
[ProducesResponseType(StatusCodes.Status200OK)]
public ActionResult Update(int id, Models.Patch.User patch) {
return new Models.User {
Id = id,
FirstName = patch.FirstName ?? $"FirstName {id}",
LastName = patch.LastName ?? $"LastName {id}"
};
}

}
}

namespace SampleApiOneFile {
public static class Program {
public static int Main(string[] args) {
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddOpenApi();
builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();
app.Run();
return 0;
}
}
}
````

# Generated SampleApiOneFile.json (OpenAPI schema):
````
{
"openapi": "3.1.1",
"info": {
"title": "SampleApiOneFile | v1",
"version": "1.0.0"
},
"paths": {
"/User/{id}": {
"get": {
"tags": [
"User"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int32"
}
}
],
"responses": {
"200": {
"description": "OK",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/User"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
},
"patch": {
"tags": [
"User"
],
"parameters": [
{
"name": "id",
"in": "path",
"required": true,
"schema": {
"pattern": "^-?(?:0|[1-9]\\d*)$",
"type": [
"integer",
"string"
],
"format": "int32"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
},
"application/*+json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
},
"required": true
},
"responses": {
"200": {
"description": "OK",
"content": {
"text/plain": {
"schema": {
"$ref": "#/components/schemas/User"
}
},
"application/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
},
"text/json": {
"schema": {
"$ref": "#/components/schemas/User"
}
}
}
}
}
}
}
},
"components": {
"schemas": {
"User": {
"type": "object",
"properties": {
"firstName": {
"type": [
"null",
"string"
]
},
"lastName": {
"type": [
"null",
"string"
]
}
}
}
}
},
"tags": [
{
"name": "User"
}
]
}
````

Look at User component definition: it's plain wrong. We shall have here 2 components:
- one for Models.User - with no nullable
- one for Models.Patch.User - with nullable

### Exceptions (if any)

_No response_

### .NET Version

10.0.100

### Anything else?

macOS 15.7.2

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.