dotnet / aspnetcore

ASP.NET Core is a cross-platform .NET framework for building modern cloud-based web applications on Windows, Mac, or Linux.
https://asp.net
MIT License
35.15k stars 9.92k forks source link

WithOpenAPI Generates Empty requestBody.content #54189

Closed LouisTrinczek closed 6 months ago

LouisTrinczek commented 6 months ago

Is there an existing issue for this?

Describe the bug

I am facing the same issues with Microsoft.AspNetCore.OpenAPI 8.0.2 as mentioned in this issue https://github.com/dotnet/aspnetcore/issues/47644. Apparently this has been fixed, but we are encountering the same issues on the most recent version as well. The only difference is, that we are injecting a Service using [FromServices].

  "/route": {
      "get": {
        "tags": [
          "tag"
        ],
        "summary": "get xy",
        "operationId": "getXy",
        "requestBody": {
          "content": { }
        },
        "responses": { 
...

This is currently breaking our frontend API Client Generation with NSwag, because usually a GET Request doesn't allow a body, but it is being generated, due to the wrong generation of the swagger.json.

Expected Behavior

Generated JSON without requestBody

  "/route": {
      "get": {
        "tags": [
          "tag"
        ],
        "summary": "get xy",
        "operationId": "getXy",
        "responses": { 
...

Steps To Reproduce

    private static IEndpointRouteBuilder MapXy(this IEndpointRouteBuilder app)
    {
        var endpoints = app.MapGroup("/xy");

        endpoints.MapGet(
            "/",
            async ([FromServices] IXyService xyService) =>
            {
                var xy = await xyService.ListAllAsync();
                return TypedResults.Ok(xy);
            }
        ).WithOpenApi(openapi => new(openapi)
        {
            OperationId = "getXy",
            Summary = "Get xy",
            Tags = new List<OpenApiTag>() {new OpenApiTag() {Name = "tag"}}
        }).Produces<IReadOnlyList<XyDto>>(statusCode: StatusCodes.Status200OK);

        return app;
    }

Exceptions (if any)

No response

.NET Version

8.0.200

Anything else?

.NET SDK: Version: 8.0.200
Commit: 438cab6a9d Workload version: 8.0.200-manifests.e575128c

Runtime Environment: OS Name: Windows OS Version: 10.0.19045 OS Platform: Windows RID: win-x64 Base Path: C:\Program Files\dotnet\sdk\8.0.200\

.NET workloads installed: There are no installed workloads to display.

Host: Version: 8.0.2 Architecture: x64 Commit: 1381d5ebd2

.NET SDKs installed: 7.0.406 [C:\Program Files\dotnet\sdk] 8.0.200 [C:\Program Files\dotnet\sdk]

.NET runtimes installed: Microsoft.AspNetCore.App 7.0.7 [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.NETCore.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.NETCore.App] Microsoft.NETCore.App 7.0.7 [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.WindowsDesktop.App 6.0.27 [C:\Program Files\dotnet\shared\Microsoft.WindowsDesktop.App] Microsoft.WindowsDesktop.App 7.0.7 [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]

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

LouisTrinczek commented 6 months ago

Any news on this?

LouisTrinczek commented 6 months ago

Right now this would be a Workaround. Just applying a filter and nulling the RequestBody whenever the HttpMethod is "GET".

public class HttpGetBodyFilter : IOperationFilter
{
    public void Apply(OpenApiOperation operation, OperationFilterContext context)
    {
        if (context.ApiDescription.HttpMethod is not "GET")
        {
            return;
        }

        if (operation.RequestBody is not null)
        {
            operation.RequestBody = null;
        }
    }
}
captainsafia commented 6 months ago

@LouisTrinczek Thanks for reporting this issue!

I've tracked down that this seems to only repro when using the copy constructor provided for OpenApiOperation by the Microsoft.OpenApi package. It doesn't repro if you use the setters on the provided OpenAPI like so:

.WithOpenApi(openApi => {
    openApi.OperationId = "GetWeatherForecast";
    openApi.Summary = "Get weather forecast";
    openApi.Tags = new List<OpenApiTag>() {new OpenApiTag() {Name = "WeatherForecast"}};
    return openApi;
});

It looks like the underlying issue was fixed in https://github.com/microsoft/OpenAPI.NET/pull/1195. We'll need to bump up the version of Microsoft.OpenApi we use in our package to resolve this.

LouisTrinczek commented 6 months ago

Thank you! @captainsafia When is the fix expected to be available for us, or in which version?

captainsafia commented 6 months ago

@LouisTrinczek I've only merged the fix into .NET 9 for now. Locally, you should be able to resolve this by bumping up the version of Microsoft.OpenApi package that you use. I believe the transitive dependencies will get sorted out.

dotnet add package Microsoft.OpenApi --version 1.6.13