API Series Part 2b - Add Non-Intrusive Markdown to Swagger UI

Code on GitHub.

In the last post we added a short description of the API using markdown. The Description property of the Info class gets rendered at the top of the page above the list of actions.

In this post we are going to take that a bit further. One problem with putting documentation in your Swagger docs is that if you put a lot of detail there then you are forced to scroll down every time you view the Swagger UI of your API. This can get pretty annoying quick. So we'll look at a trick for adding as much detail as you want but avoiding the scrolling issue.

First of all let's move the markdown we put in our SwaggerHelper.cs and put it in a markdown file.

Remember to set the Copy to Output as Copy Always so the markdown gets copied to the bin folder and is published on deployment.

We can create a different md file per API version. Then we load the markdown into the Description property.

public class ApiVersionDescriptions
{
    private Dictionary<string, string> _descriptions;

    public ApiVersionDescriptions()
    {
        _descriptions = new Dictionary<string, string>();
    }

    public void AddDescription(string version, string description)
    {
        if (!_descriptions.ContainsKey(version))
            _descriptions.Add(version, description);
    }

    public string GetDescription(string version)
    {
        if(_descriptions.ContainsKey(version))
            return _descriptions[version];

        return string.Empty;
    }
}

Now in our SwaggerHelper.cs we remove the big chunk of markdown and load the markdown from the file. Load one md file per API version.

private static void AddSwaggerDocPerVersion(SwaggerGenOptions swaggerGenOptions, Assembly webApiAssembly)
{
    var apiVersionDescriptions = new ApiVersionDescriptions();
    apiVersionDescriptions.AddDescription("1", File.ReadAllText("Docs\\ApiVersion1Description.md"));

    var apiVersions = GetApiVersions(webApiAssembly);
    foreach (var apiVersion in apiVersions)
    {
        swaggerGenOptions.SwaggerDoc($"v{apiVersion}",
            new Info
            {
                Title = "Govrnanza Registry",
                Version = $"v{apiVersion}",
                Description = apiVersionDescriptions.GetDescription(apiVersion),
                Contact = new Contact()
                {
                    Name = "Govrnanza",
                    Url = "https://github.com/Vanlightly/Govrnanza"
                }
            });
    }
}

This produces the follow Swagger UI page:

Without too much text, we only just see the first operation at the bottom of the page. If we add more documentation we would end up scrolling every time. Markdown offers no collapsable content. So instead what we'll do is inject a JQuery script to collapse the content.

We add a JavaScript file to our wwwroot folder.

CustomisedSwaggerJS.PNG

The following JQuery collapses the content under the H2 headings on load, and toggles the content when clicking on an H2 heading inside the HTML rendered from the Description property.

jQuery(function ($) {
    $('.info_description > h2').prepend("+/- ");

    // hide all contents of each heading
    var firstElement = $('.info_description > h2 :first');
    var currentElement = firstElement.next();
    while (currentElement.attr('class') != 'info_name' && currentElement.prop("tagName") != undefined) {
        if (currentElement.prop("tagName") != 'H2') {
            currentElement.stop(true, true).slideToggle(10);
            currentElement.hide();
        }

        currentElement = currentElement.next();
    }

    // toggle contents of the heading clicked
    $('.info_description &gt; h2').click(function () {
        var currentElement = $(this).next();
        while (currentElement.prop("tagName") != 'H2' && currentElement.next().attr('class') != 'info_name' && currentElement.prop("tagName") != undefined) {
            currentElement.stop(true, true).slideToggle(10);
            currentElement = currentElement.next();
        }
    });
})

Now we just need to inject that script.

internal static void ConfigureSwaggerUI(SwaggerUIOptions swaggerUIOptions)
{
    var webApiAssembly = Assembly.GetEntryAssembly();
    var apiVersions = GetApiVersions(webApiAssembly);
    foreach (var apiVersion in apiVersions)
    {
        swaggerUIOptions.SwaggerEndpoint($"v{apiVersion}/swagger.json", $"V{apiVersion} Docs");
    }
    swaggerUIOptions.RoutePrefix = "api-docs";
    swaggerUIOptions.ShowRequestHeaders();
    swaggerUIOptions.ShowJsonEditor();
    swaggerUIOptions.InjectStylesheet("theme-feeling-blue-v2.css");
    swaggerUIOptions.InjectOnCompleteJavaScript("CustomisedSwagger.js");
}

Now we see each H2 heading prepended with +/- to indicate it is collapsible/expandible.

Now we see that the documentation is not obstrusive at all. Just click a heading to expand it.

This technique means you can write a readme.md oriented towards users of your API and keep it from getting in the way when you just want to go straight to an operation down the page.

Link to the API Series Introduction