Skip to content

[Critical] ASP.NET Core 9.0: AddOpenApi duplicates schema with a number suffix #59427

Closed as duplicate of#58968
@jaliyaudagedara

Description

@jaliyaudagedara

Is there an existing issue for this?

  • I have searched the existing issues

Describe the bug

I was migrating an ASP.NET Core Web API from .NET 8.0 to .NET 9.0.

As part of the migration, I was replacing AddSwaggerGen() with AddOpenApi() and seeing this critical issue. This basically inserts duplicated schema to OpenAPI specification. Solid OpenAPI specification is critical when integrating with services like APIM and hence subject is marked with Critical.

Consider the following minimal reproducible example.

csproj

<Project Sdk="Microsoft.NET.Sdk.Web">

  <PropertyGroup>
    <TargetFramework>net9.0</TargetFramework>
    <Nullable>enable</Nullable>
    <ImplicitUsings>enable</ImplicitUsings>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.0" />
    <PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="7.2.0" />
  </ItemGroup>

</Project>

Startup.cs

namespace WebApplication3;

public class Startup
{
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddOpenApi();
        services.AddControllers();
    }

    public void Configure(IApplicationBuilder app)
    {
        app.UseSwaggerUI(x =>
        {
            x.SwaggerEndpoint("/openapi/v1.json", "My API");
        });

        app.UseHttpsRedirection();
        app.UseRouting();

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapOpenApi();

            endpoints.MapControllers();
        });
    }
}

Program.cs

namespace WebApplication3;

public class Program
{
    public static void Main(string[] args)
    {
        IHost host = CreateHostBuilder(args).Build();
        host.Run();
    }

    public static IHostBuilder CreateHostBuilder(string[] args) =>
       Host.CreateDefaultBuilder(args)
           .ConfigureWebHostDefaults(webBuilder =>
           {
               webBuilder.UseStartup<Startup>();
           });
}

WeatherForecastController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication3.Controllers;

[ApiController]
[Route("/api/[controller]")]
public class WeatherForecastController : ControllerBase
{
    [HttpGet]
    public ActionResult<IEnumerable<WeatherForecast>> Get()
    {
        return Ok();
    }

    [HttpPost]
    public ActionResult<WeatherForecast> Create([FromBody] WeatherForecast weatherForecast)
    {
        return weatherForecast;
    }
}

Up to here, it's pretty standard.

Now carefully review the following WeatherForecast type.

namespace WebApplication3;

public class WeatherForecast
{
    public int Id { get; set; }

    public DateOnly Date { get; set; }

    public int TemperatureC { get; set; }

    public LocationDto Location { get; set; }
}

public class LocationDto
{
    public string Name { get; set; }

    public AddressDto Address { get; set; }
}

public class AddressDto
{
    string AddressLine { get; set; }

    // A circular reference, but not getting any errors
    public LocationDto RelatedLocation { get; set; }
}

When I run this:
Image

Note the highlighted schema.

OpenAPI specification

{
    "openapi": "3.0.1",
    "info": {
        "title": "WebApplication3 | v1",
        "version": "1.0.0"
    },
    "servers": [
        {
            "url": "https://localhost:7286/"
        },
        {
            "url": "http://localhost:5013/"
        }
    ],
    "paths": {
        "/api/WeatherForecast": {
            "get": {
                "tags": [
                    "WeatherForecast"
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "text/plain": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/WeatherForecast"
                                    }
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/WeatherForecast"
                                    }
                                }
                            },
                            "text/json": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "$ref": "#/components/schemas/WeatherForecast"
                                    }
                                }
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "WeatherForecast"
                ],
                "requestBody": {
                    "content": {
                        "application/json": {
                            "schema": {
                                "$ref": "#/components/schemas/WeatherForecast2"
                            }
                        },
                        "text/json": {
                            "schema": {
                                "$ref": "#/components/schemas/WeatherForecast2"
                            }
                        },
                        "application/*+json": {
                            "schema": {
                                "$ref": "#/components/schemas/WeatherForecast2"
                            }
                        }
                    },
                    "required": true
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "text/plain": {
                                "schema": {
                                    "$ref": "#/components/schemas/WeatherForecast2"
                                }
                            },
                            "application/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WeatherForecast2"
                                }
                            },
                            "text/json": {
                                "schema": {
                                    "$ref": "#/components/schemas/WeatherForecast2"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "schemas": {
            "AddressDto": {
                "type": "object",
                "properties": {
                    "relatedLocation": {
                        "$ref": "#/components/schemas/LocationDto2"
                    }
                }
            },
            "AddressDto2": {
                "type": "object",
                "properties": {
                    "relatedLocation": {
                        "$ref": "#/components/schemas/LocationDto4"
                    }
                }
            },
            "LocationDto": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "address": {
                        "$ref": "#/components/schemas/AddressDto"
                    }
                }
            },
            "LocationDto2": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "address": {
                        "$ref": "#/components/schemas/#/items/properties/location/properties/address"
                    }
                }
            },
            "LocationDto3": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "address": {
                        "$ref": "#/components/schemas/AddressDto2"
                    }
                }
            },
            "LocationDto4": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string"
                    },
                    "address": {
                        "$ref": "#/components/schemas/#/properties/location/properties/address"
                    }
                }
            },
            "WeatherForecast": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "date": {
                        "type": "string",
                        "format": "date"
                    },
                    "temperatureC": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "location": {
                        "$ref": "#/components/schemas/LocationDto"
                    }
                }
            },
            "WeatherForecast2": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "date": {
                        "type": "string",
                        "format": "date"
                    },
                    "temperatureC": {
                        "type": "integer",
                        "format": "int32"
                    },
                    "location": {
                        "$ref": "#/components/schemas/LocationDto3"
                    }
                }
            }
        }
    },
    "tags": [
        {
            "name": "WeatherForecast"
        }
    ]
}

Expected Behavior

Under components.schemas, there should be only 3 schemas

  • AddressDto
  • LocationDto
  • WeatherForecast

Steps To Reproduce

https://github.com/jaliyaudagedara/aspnetcore-minimal-repros/tree/main/openapi-dup-schemas

Exceptions (if any)

N/A

.NET Version

9.0.200-preview.0.24575.35

Anything else?

N/A

Metadata

Metadata

Assignees

No one assigned

    Labels

    area-mvcIncludes: MVC, Actions and Controllers, Localization, CORS, most templatesfeature-openapi

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions