Search code examples
swaggerasp.net-core-webapiopenapinswag

Multiple Swagger docs in an ASP.NET Core Web API project with different URLs


I have an ASP.NET Core 8 Web API . I want to have the API docs on a separate swagger page per controller. e.g. all endpoints under AmazonController should be served on ..../v1/swagger/amazon.html. Similarly, all endpoints under EBayController should be served on ...v1/swagger/ebay.html.

Currently, I am using NSwag to generate ApiClient per controller and services.AddOpenApiDocument to configure the OpenAPI docs. The issue is I can add register multiple AddOpenApiDocument in DI with different configurations e.g. using DocumentProcessers to filter out irrelevant endpoints by name in a document, but, the resulting swagger page remains the same. i.e. ...swagger/index.html.

Each document appears as a separate "definition" on the same ...swaggger/index.html page.

enter image description here

I want to keep each controller's docs on its own respective swagger docs page and have its own versioning. Cannot seem to find how to do it best. I am open to all suggestions.


Solution

  • I want to have the API docs on a separate swagger page per controller. e.g. all endpoints under AmazonController should be served on ..../v1/swagger/amazon.html. Similarly, all endpoints under EBayController should be served on ...v1/swagger/ebay.html.

    You could register multiple OpenAPI documents in the DI container, each with a filter to include only the relevant controller's endpoints and serve each document on a separate endpoint instead of listing them all on the same Swagger UI page.

    Refer to the following sample:

    1. Configure NSwag to Generate Separate Documents

       builder.Services.AddControllers(); 
       // Register multiple OpenAPI documents  
       builder.Services.AddOpenApiDocument(config =>
       {
           config.DocumentName = "todo";
           config.Title = "todo API";
           config.ApiGroupNames = new[] { "todo" }; // Tag controllers with [ApiExplorerSettings(GroupName = "Amazon")]
           config.PostProcess = document =>
           {
               document.Info.Version = "v1";
               document.Info.Title = "todo API";
           };
       });
      
       builder.Services.AddOpenApiDocument(config =>
       {
           config.DocumentName = "values";
           config.Title = "values API";
           config.ApiGroupNames = new[] { "values" };
           config.PostProcess = document =>
           {
               document.Info.Version = "v1";
               document.Info.Title = "values API";
           };
       }); 
       var app = builder.Build();
       app.UseHttpsRedirection();
       app.UseOpenApi(settings =>
       {
           settings.Path = "/v1/swagger/{documentName}.json"; // Serves separate OpenAPI documents
       });
      
       app.UseSwaggerUi3(settings =>
       {
           settings.Path = "/v1/swagger/todo.html";
           settings.DocumentPath = "/v1/swagger/todo.json";
       });
      
       app.UseSwaggerUi3(settings =>
       {
           settings.Path = "/v1/swagger/values.html";
           settings.DocumentPath = "/v1/swagger/values.json";
       });
      
      
       app.UseRouting();
       app.UseAuthorization();
      
       app.MapControllers();
      
       app.Run();
      
    2. Tag Controllers with [ApiExplorerSettings]

       [Route("api/[controller]")]
       [ApiController]
       [ApiExplorerSettings(GroupName = "todo")]
       public class TodoController : ControllerBase
       {
           // GET: api/<TodoController>
           [HttpGet]
           public IEnumerable<string> Get()
           {
               return new string[] { "value1", "value2" };
           }
        ...
      
       [Route("api/[controller]")]
       [ApiController]
       [Authorize]
       [ApiExplorerSettings(GroupName = "values")]
       public class ValuesController : ControllerBase
       {
           // GET: api/<ValuesController>
           [HttpGet]
           public IEnumerable<string> Get()
           {
               return new string[] { "value1", "value2" };
           }
        ...
      

    After running the application, the result as below:

    Todo controller: https://localhost:7245/v1/swagger/todo.html → Todo API docs

    todo

    Values controller: https://localhost:7245/v1/swagger/values.html → Todo API docs

    values

    And you can view the .json result use https://localhost:7245/v1/swagger/todo.json or https://localhost:7245/v1/swagger/values.json.

    Update:

    I want to have one more group that should show all the controllers including Amazon, EBay, todos, files, customers, etc. Basically, this one should be the default swagger docs page that gets rendered when I hit swagger/index.html and it should display all endpoints. Would it be necessary to apply the ApiExplorerSettings tag on all controllers and define a name for it?

    You can add one more OpenAPI documents as below:

    builder.Services.AddOpenApiDocument();
    ...
    if (app.Environment.IsDevelopment())
    {
        // Add OpenAPI 3.0 document serving middleware
        // Available at: http://localhost:<port>/swagger/v1/swagger.json
        app.UseOpenApi();
    
        // Add web UIs to interact with the document
        // Available at: http://localhost:<port>/swagger
        app.UseSwaggerUi3(); // UseSwaggerUI Protected by if (env.IsDevelopment())
    }
    

    The Program.cs file like this:

    enter image description here

    Then, you can display all endpoints in swagger index page(https://localhost:7245/swagger/index.html).

    enter image description here