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.2k stars 9.93k forks source link

Using MapGroup() with MapControllers() results in incorrect path in Swagger #57598

Open brianpursley opened 2 weeks ago

brianpursley commented 2 weeks ago

Is there an existing issue for this?

Describe the bug

When doing this:

app.MapGroup("prefix").MapControllers();

Assuming there is a FooController, the generated OpenAPI spec incorrectly says the path is /Foo instead of /prefix/Foo:

  ...
  paths": {
    "/Foo": {
      "get": {
        "tags": [
          "Foo"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string"
                }
              },
              "text/json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    ...

Trying to curl both /Foo and /prefix/Foo shows that the wrong path was generated in the OpenAPI spec:

$ curl -f http://localhost:5147/Foo
curl: (22) The requested URL returned error: 404
$ curl -f http://localhost:5147/prefix/Foo
It works!

Interestingly, this problem only happens with MapControllers() and minimal API endpoints created using MapGet() for example don't experience this problem. See the steps to reproduce to see what I mean.

Expected Behavior

The OpenAPI spec should generate the correct path when using MapGroup() with MapControllers().

Steps To Reproduce

Program.cs

using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.ApiExplorer;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
builder.Services.AddControllers();

var app = builder.Build();
app.UseRouting();
app.UseEndpoints(endpoints =>
{
    endpoints.MapGroup("prefix").MapGet("/Bar", () => "It works!\n");
});
app.MapGroup("prefix").MapControllers();

app.UseSwagger();
app.UseSwaggerUI();

// Print relative paths
var p = app.Services.GetService<IApiDescriptionGroupCollectionProvider>()!;
foreach (var group in p.ApiDescriptionGroups.Items)
{
    foreach (var api in group.Items)
    {
        Console.WriteLine(api.RelativePath);
    }
}

app.Run();

[ApiController]
[Route("[controller]")]
public class FooController : Controller
{
    [HttpGet]
    public string Get() => "It works!\n";
}
$ dotnet run
Building...
prefix/Bar
Foo
info: Microsoft.Hosting.Lifetime[14]
      Now listening on: http://localhost:5147
info: Microsoft.Hosting.Lifetime[0]
      Application started. Press Ctrl+C to shut down.
info: Microsoft.Hosting.Lifetime[0]
      Hosting environment: Development
info: Microsoft.Hosting.Lifetime[0]
      Content root path: /home/brian/projects/SwaggerBugExample/SwaggerBugExample
$ curl -f http://localhost:5147/swagger/v1/swagger.json
{
  "openapi": "3.0.1",
  "info": {
    "title": "SwaggerBugExample",
    "version": "1.0"
  },
  "paths": {
    "/Foo": {
      "get": {
        "tags": [
          "Foo"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              },
              "application/json": {
                "schema": {
                  "type": "string"
                }
              },
              "text/json": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    },
    "/prefix/Bar": {
      "get": {
        "tags": [
          "SwaggerBugExample"
        ],
        "responses": {
          "200": {
            "description": "Success",
            "content": {
              "text/plain": {
                "schema": {
                  "type": "string"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": { }
$ curl -f http://localhost:5147/Foo
curl: (22) The requested URL returned error: 404
$ curl -f http://localhost:5147/prefix/Foo
It works!
$ curl -f http://localhost:5147/prefix/Bar
It works!

Exceptions (if any)

No response

.NET Version

8.0.401

Anything else?

image

BlackStarTLM commented 2 weeks ago

Have you tried to rebuild and rerun the swagger? Since I was facing this issue as well but it got corrected when i cleaned & rebuild the solution. I dont know if it makes sense, just want to get your clarity. @gfoidl