domaindrivendev / Swashbuckle.AspNetCore

Swagger tools for documenting API's built on ASP.NET Core
MIT License
5.25k stars 1.31k forks source link

Cannot sort controllers #2772

Closed alekdavis closed 6 months ago

alekdavis commented 7 months ago

I have 3 controllers implemented in 2 projects (one is a shared library, another is an ASP.NET Core application implementing a REST API):

In the shared (PingController) library:

In the REST API (Demo) application:

The Demo application references the PingController library, so there are 3 controllers in the REST API. I want them to be sorted by (1) controller name, (2) relative route, and (3) HTTP verb/method as:

In Swagger initialization I use:

options.OrderActionsBy(
    apiDesc => 
        $"{apiDesc.ActionDescriptor.RouteValues["controller"]}_{apiDesc.RelativePath}_{apiDesc.HttpMethod}"
);

However, while the endpoint methods within the controllers are sorted fine, the controllers are displayed as:

image

Not sure if this is a bug or if there is something I'm not doing right, but I'm following the example that should be working.

Environment:

changweihua commented 7 months ago

You can try this :

options.DocumentFilter<ApiControllerTagDescriptions>();

public class ApiControllerTagDescriptions : IDocumentFilter { public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context) { swaggerDoc.Tags = new List { new OpenApiTag{ Name = "HealthChecks", Description = "HealthChecks" }, new OpenApiTag{ Name = "Ping", Description = "Ping" }, new OpenApiTag{ Name = "Samples", Description = "Samples" } }; } }

Hope it's useful !

alekdavis commented 7 months ago

@changweihua A problem with this approach is that I would need to redefine the ApiControllerTagDescriptions class for every microservice project (since the names of the controllers would be different). Then you'd need to always remember to keep them in sync and once someone decides to rename a controller, add a new one, or remove an existing one, they'd need to remember to update this class (and I'm sure it will be forgotten). Also, I'm building a common framework that does the startup initialization including Swagger setup, and this framework would need to receive the controller names from the project using it, and it'll be too clunky. Don't want to have such tight coupling between the models. I have a feeling that the OrderActionsBy should be working and if it's not because of a bug, maybe someone can see if it can get fixed. Not sorted controllers is not the end of the world, but if would be nice if this can get fixed.

pinkfloydx33 commented 6 months ago

I think OrderActionsBy only works within a controller/tag group and that the top-level is sorted by controller or tag name. You can also try TagActionsBy instead of a filter, but be advised that a single tag will replace the controller name, and multiple tags will cause the controller to be duplicated in the UI.

I think the docs are out of date w/r/t how swagger-ui actually does it's sorting as it says it sorts both groups and within groups and that it'd effect the group order, yet I too have seen this doesn't jive and had to customize the behavior (see below). Reading through issues and commits on the swagger-ui github, I think it may have once worked as expected but that they changed the default behavior at some point.

If you are only worried about your UI (and not someone else's who has loaded your spec, nor the order in third party tools you pass the spec to) you can use additional properties on the SwaggerUIOptions.ConfigObject to control how the UI does the sorting

// controller or tag level (top level group in UI) 
options.ConfigObject.AdditionalItems["tagsSorter"] = "alpha";
// within a controller, operations are the endpoints. You may not need this one 
options.ConfigObject.AdditionalItems["operationsSorter"] = "alpha";

You can also supply a javascript function if embedding the UI yourself which tells me there might be a way to do it here if you use a stringified function similar to the interceptor properties, or perhaps inject a JS file with the function and pass the function's name via the property. That's just a guess though, never tried it myself.

You can see the built-in sorters here

alekdavis commented 6 months ago

@pinkfloydx33 How and where do you call options.ConfigObject.AdditionalItems? ConfigOptions does not seem to appear inside of the Swagger config call:


services.AddSwaggerGen(options => 
{options => 
{
    options.SwaggerDoc(...);
    options.EnableAnnotaztions();
    ...
}
});
pinkfloydx33 commented 6 months ago

It's on the configuration for SwaggerUIOptions which you can either use when calling UseSwaggerUI/MapSwaggerUI or on its own as services.Configure<SwaggerUIOptions>(o =>...)

alekdavis commented 6 months ago

Ah, sweet. I can't believe it, but it worked!!! Thank you so much. If anyone is interested, the code is:

services.Configure<SwaggerUIOptions>(options =>
{
    // Controller or tag level (top level group in UI) 
    options.ConfigObject.AdditionalItems["tagsSorter"] = "alpha";

    // Within a controller, operations are the endpoints. You may not need this one 
    options.ConfigObject.AdditionalItems["operationsSorter"] = "alpha";
});