How can I change order the operations are listed in a group in Swashbuckle?

Viewed 18215

I'm using Swashbuckle to generate Swagger UI. It has options for choosing a grouping key (controller by default) and the ordering of the groups, but I would like to choose an order for the operations in a group so that GET appears always before DELETE for example.

I've found how to implement document filters and I can get and order ApiDescriptions by HttpMethod, but changing the order in ApiDescriptions doesn't reflect in the generated Swagger UI and I can't find how to persist the order in swaggerDoc.

SwaggerDocument has a paths property, but the PathItem in it has each HTTP method as a property, so I can't figure how to choose an order of presentation for them. Eventough, when the Swagger UI for my API is generated, different controllers get different method order in the page.

Should I manually reorder the methods implementation in my controller instead?

4 Answers

In order to order the Operations of controller in swagger OpenApi paths json spec you could create a custom Attribute OrderAttribute and then a IDocumentFilter which will reorder the OpenApiPaths.

public class OperationsOrderingFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument openApiDoc, DocumentFilterContext context)
    {
        Dictionary<KeyValuePair<string, OpenApiPathItem>,int> paths = new Dictionary<KeyValuePair<string, OpenApiPathItem>, int>();
        foreach(var path in openApiDoc.Paths)
        {
            OperationOrderAttribute orderAttribute = context.ApiDescriptions.FirstOrDefault(x=>x.RelativePath.Replace("/", string.Empty)
                .Equals( path.Key.Replace("/", string.Empty), StringComparison.InvariantCultureIgnoreCase))?
                .ActionDescriptor?.EndpointMetadata?.FirstOrDefault(x=>x is OperationOrderAttribute) as OperationOrderAttribute;

            if (orderAttribute == null)
                throw new ArgumentNullException("there is no order for operation " + path.Key);

            int order = orderAttribute.Order;
            paths.Add(path, order);
        }

        var orderedPaths = paths.OrderBy(x => x.Value).ToList();
        openApiDoc.Paths.Clear();
        orderedPaths.ForEach(x => openApiDoc.Paths.Add(x.Key.Key, x.Key.Value));
    }

}

then the attribute would be

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
public class OperationOrderAttribute : Attribute
{
    public int Order { get; }

    public OperationOrderAttribute(int order)
    {
        this.Order = order;
    }
}

the registration of the filter in swagger would be

services.AddSwaggerGen(options =>
{
   options.DocumentFilter<OperationsOrderingFilter>();
}

and an example of a controller method with the attribute would be:

[HttpGet]
[OperationOrder(2)]
[Route("api/get")]
public async Task<ActionResult> Get(string model)
{
   ...
}

You have to replace Swashbuckle's index.html with your own version and then add the "operationsSorter" config param within that file. Here are the steps for .NET Core 2.x. (.NET framework should only differ in the way an embedded resource is defined in your VS project).

  • Get a copy of SwashBuckle's original index.html from here: https://github.com/domaindrivendev/Swashbuckle.AspNetCore/blob/master/src/Swashbuckle.AspNetCore.SwaggerUI/index.html

  • Place that copy in some sub-folder of your project.
    You can choose a different file name, I chose: \Resources\Swagger_Custom_index.html

  • Right-click that file, select 'Properties', select 'Configuration Properties' in left pane, under 'Advanced' in right pane find entry 'Build Action' and set it to 'Embedded resource'. Click Ok.

  • In Startup.cs add this block:

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        //...
    
        app.UseSwaggerUI(c =>
        {
            c.IndexStream = () => GetType().GetTypeInfo().Assembly.GetManifestResourceStream("Your.Default.Namespace.Resources.Swagger_Custom_index.html");
        });
    
        app.UseMvc();
    }
    
  • The identifier for the resource in the above GetManifestResourceStream command is composed of:

    1. your default namespace (i.e. 'Your.Default.Namespace')
    2. the sub-path of your resource (i.e. 'Resources')
    3. the filename of your resource (i.e. 'Swagger_Custom_index.html')

    All three parts are concatenated using dots (NO slashes or backslashes here).
    If you don't use a sub-path but have your SwashBuckle index.html in root, just omit part 2.

  • Now edit Swagger_Custom_index.html and insert this block right before the line const ui = SwaggerUIBundle(configObject); close to the end of the file:

        // sort end points by verbs ("operations")
        configObject.operationsSorter = (a, b) => {
            var methodsOrder = ["get", "post", "put", "delete", "patch", "options", "trace"];
            var result = methodsOrder.indexOf(a.get("method")) - methodsOrder.indexOf(b.get("method"));
            if (result === 0) {
                result = a.get("path").localeCompare(b.get("path"));
            }
            return result;
        }
    
        // Build a system
        const ui = SwaggerUIBundle(configObject);
    
Related