API Series Part 2 - Documentation - Swagger

Code on GitHub.

Swagger acts as both machine and human readable documentation for your APIs but also via the Swagger UI offers you a way of interacting with your APIs easily. We are going to embed a Swagger UI in our APIs that will load when you press F5 making it hassle free to test your API during development and testing.

We are going to add Swashbuckle.AspNetCore to our ASP.NET Core 2.0 API to create this embedded Swagger UI. 

In this series we'll slowly be developing Govrnanza, a lifecycle governance system for managing APIs in an enterprise setting. I have created some first toy APIs for Govrnanza for doing CRUD operations on API, Business Domain, Business Sub Domain and Tag objects. The idea is that the first thing we need to do for lifecycle governance is be able to classify our APIs according to domain, sub domain and tags.

I have the following resources:

  • GET api/v1/apis

  • GET api/v1/apis/{api-name}

  • POST api/v1/apis

  • PUT api/v1/apis

  • DELETE api/v1/apis

  • GET api/v1/business-domains

  • GET api/v1/business-domains/{business-domain-name}

  • POST api/v1/business-domains

  • PUT api/v1/business-domains

  • DELETE api/v1/business-domains

  • GET api/v1/business-domains

  • GET api/v1/business-sub-domains/{business-sub-domain-name}

  • POST api/v1/business-sub-domains

  • POST api/v1/business-sub-domains/{business-sub-domain-name}/move/{business-domain-name}

  • PUT api/v1/business-sub-domains

  • DELETE api/v1/business-sub-domains

Basic Swashbuckle, No Customizations

When we add Swashbuckle.AspNetCore and enable its implementation of Swagger UI with the bare minimum of configuration we get the following:

We can expand each operation and execute it if we want.

GET without parameters

The "Try it out!" button will make a call to the API operation.

BasicSwaggerOneExpanded.PNG

GET with parameters

Just enter the values you need into the parameter fields.

BasicGetWithParameters.PNG

POST/PUT with JSON body

You can see an example value is provided on the right. Just click that example and it will be pasted into the empty text area where you can modify to your needs. The PUT is exactly the same.

BasicPostWithJsonBody.PNG

Executing Operations and Viewing Results

Let's execute the GET api/v1/business-domains and see how the results are displayed. We click the "Try it out!" button to make the call.

So right out of the box without any customization at all, we get the ability to interact with our API and make calls to its operations. What's more, this interactive Swagger UI is always up to date as it is generated at runtime based on your code. It is superb for productivity during development.

Let's look at how to get this basic version up and running.

 

How to Get the Basic Swagger UI Up and Running

The Swashbuckle Github repo has lots of detail so don't forget to spend time there learning all the various nooks and crannies of the project.

Step 1 - Add Swashbuckle.AspNetCore 1.0.0

It has not been updated for ASP.NET Core 2.0 but it is fully compatible.

Step 2 - SwaggerHelper and Startup.cs

In the Startup.cs, we'll need to configure the generation of the swagger document and add the Swagger and Swagger UI middlewares. We'll put all the Swashbuckle code in a separate helper class to keep the Startup.cs clean.


public class Startup
{
    public Startup(IConfiguration configuration)
    {
        Configuration = configuration;
    }

    public IConfiguration Configuration { get; }

    public void ConfigureServices(IServiceCollection services)
    {
        // ... code removed ...
        
        services.AddSwaggerGen(SwaggerHelper.ConfigureSwaggerGen);
	
        // ... code removed ...
    }

    public void Configure(IApplicationBuilder app, IHostingEnvironment env)
    {
        // ... code removed ...
        
        app.UseSwagger(SwaggerHelper.ConfigureSwagger);
        app.UseSwaggerUI(SwaggerHelper.ConfigureSwaggerUI);

        app.UseMvc();
    }
}

I have changed the path to Swagger UI from the default of /Swagger to /api-docs via the RoutePrefix property and changed the route template and endpoint accordingly.


public class SwaggerHelper
{
    public static void ConfigureSwaggerGen(SwaggerGenOptions swaggerGenOptions)
    {
        swaggerGenOptions.SwaggerDoc("v1", new Info());
    }

    public static void ConfigureSwagger(SwaggerOptions swaggerOptions)
    {
        swaggerOptions.RouteTemplate = "api-docs/{documentName}/swagger.json";
    }

    public static void ConfigureSwaggerUI(SwaggerUIOptions swaggerUIOptions)
    {
        swaggerUIOptions.SwaggerEndpoint($"/api-docs/v1/swagger.json", $"V1 Docs");
        swaggerUIOptions.RoutePrefix = "api-docs";
    }
}

Step 3 - Ensure your API actions use explicit HTTP and From bindings

Swashbuckle needs these explicit bindings in order to be able auto-generate the swagger.


[HttpGet("{subDomainName}")]
public async Task<ActionResult> GetAsync([FromRoute]string subDomainName)
{
    var subDomain = await _businessDomainsService.GetSubDomainAsync(subDomainName);
    if (subDomain.Result == GetResult.NotFound)
        return NotFound();

    var externalResult = Map.ToExternal(subDomain.Data);
    return Ok(externalResult);
}

Step 4 - Make it so that it is the Swagger page that loads on start up.

Update the launchSettings.json file so the launchUrl points to api-docs:

launchSettings.PNG

That's it. Your API now has a basic functioning Swagger UI! This would probably be enough if only one developer is going to use your Swagger UI, but when other team members, or members of other teams want to start using it you may find you want to add more information. This is especially true if you have complex JSON payloads for your POST/PUT operations or complex behaviours. 

 

Extending the Basic Swagger UI

Adding Information About the API as a Whole

You may wish to add a description of the API. What it does and why, who owns it, links to other documentation etc. Swashbuckle allows us to add Markdown to the Swagger UI.

We are going to add some detail to the Info object that we instantiated with the line:


swaggerGenOptions.SwaggerDoc("v1", new Info());

This time we are going to add a Title, Version and Description. We are going to add Markdown to the Description property. In addition we'll add contact information via the Contact property.


swaggerGenOptions.SwaggerDoc($"v1",
    new Info
    {
        Title = "Govrnanza Registry",
        Version = $"v1",
        Description = @"REST services for managing your API ecosystem

## Business Domains and Sub Domains ##
Breakdown your business into domains and sub domains in order to better manage your APIs.

## Tags ##
Create tags to help you classify your APIs on multiple dimensions or link APIs that form cross-cutting business processes

## API Management ##
Manage creation, update and deletion of the APIs in your registry. Classify your APIs by business sub domain and add tags for further classification.",
        Contact = new Contact()
        {
            Name = "Govrnanza",
            Url = "https://github.com/Vanlightly/Govrnanza"
        }
    });

Now when we run the solution, the Swagger UI looks like this:

You get the full power of Markdown to add information as you see fit.

 

Adding Per Operation Comments and Remarks

You can add extra information for each operation by including the XML code documentation of your controllers.

First, enable XML documentation from the project properties:

XmlCommentsProjectProperties.PNG

Then add some XML comments to your API operations. Below we use Markdown as we want an HTML list to be displayed.


// PUT api/v1/apis
/// <summary>
/// Creates or updates an API object.
/// </summary>
/// <param name="apiExternal"></param>
/// <returns></returns>
/// <remarks>
/// ### REMARKS ###
/// The following codes are returned
/// - 400 - No sub domain is found that matches the SubDomainName property
/// - 200 - Updated an existing API object
/// - 201 - Created a new API object</remarks>
[HttpPut]
public async Task<ActionResult> PutAsync([FromBody]ApiExternal apiExternal)
{
    // ...
}

Note that when you include a remarks tag in your XML comment, Swashbuckle adds an Implementation Notes section.

Note that the text in the Summary section of the XML comments appears in the list view:

Explicit Operation Responses

You can declare the responses each operation is capable of returning, includes the HTTP response code and data using the ProducesResponseType attribute.


// GET api/v2/apis/checkout
/// >summary<
/// Get the API object for a given API name.
/// >/summary<
/// >param name="apiName"<The name of the requested API>/param<
/// >returns<API object>/returns<
/// >remarks<If no API matches the name given then a 404 is returned.>/remarks<
[HttpGet("{apiName}")]
[ProducesResponseType(typeof(ApiExternal), 200)]
[ProducesResponseType(typeof(void), 404)]
[ProducesResponseType(typeof(void), 500)]
public async Task<ActionResult> GetAsync([FromRoute] string apiName)
{
    var api = await _apiService.GetApiAsync(apiName);
    if (api.Result == GetResult.NotFound)
        return NotFound();

    var tags = await _tagService.GetTagsOfApiAsync(apiName);

    var externalResult = Map.ToExternal(api.Data, tags);
    return Ok(externalResult);
}

The Swagger UI will now show new sections for describing the possible responses:

A example JSON document is displayed that shows the data structure that is returned with a 200 response. Further down are also listed the 404 and 500 responses. If you click on the "Model" tab the example JSON is replaced by a list if properties and XML comment of each property (given that you have added comments).

XmlCommentsOfResponseData.PNG

We can added Markdown here also if we want. For example, we can alter the ApiExternal class to include markdown as follows:


/// 
/// API object
/// 
public class ApiExternal
{
    /// 
    /// ## Name - Remarks ## 
    /// The name of API, used as an identifier
    /// 
    public string Name { get; set; }
    /// 
    /// ## Description - Remarks ## 
    /// Short description about the API
    /// 
    public string Description { get; set; }
    /// 
    /// ## SubDomainName - Remarks ## 
    /// The Business Sub Domain which owns this API. A mandatory field.
    /// 
    public string SubDomainName { get; set; }
    /// 
    /// ## Tags - Remarks ## 
    /// List of tags used to classify the API on further dimensions.
    /// 
    public List<string> Tags { get; set; }
}

Now the Swagger UI shows as follows:

XmlCommentsOfResponseDataWithMarkdown.PNG

 

This can be useful for describing complex data.

 

Multiple Versions

You may have multiple versions of your API in your ASP.NET Core 2.0 project. With Swashbuckle you can generate a Swagger file per version. At the time of writing, ASP.NET Core 2.0 has lost the Microsoft.AspNetCore.Mvc.Versioning NuGet package which was useful for versioning. There is an issue open and we should get it for 2.0 very soon, but for now I have made a workaround.

I have simply put the version number in the Route attribute, [Route("api/v1/apis")], instead of relying on the Versioning attribute.

In order to build multiple Swagger documents however, having an ApiVersion attribute is quite useful as we can use reflection to discover the versions programmatically. So I have created an ApiVersion attribute to replace the one lost in Microsoft.AspNetCore.Mvc.Versioning. This is temporary until the versioning package is available.


public class ApiVersionAttribute : Attribute
{
    public List<string> Versions { get; set; }

    public ApiVersionAttribute(string version)
    {
        Versions = new List<string>();
        Versions.Add(version);
    }

    public ApiVersionAttribute(params string[] versions)
    {
        Versions = versions.ToList();
    }
}

Now I just decorate the controllers with the ApiVersion attribute. This will allow us to use reflection to help build Swaggers docs by version.

Let's say I have a mix of version 1 and version 2 controllers


[ApiVersion("2")]
[Route("api/v2/apis")]
public class ApisController : Controller
{
 //...
}

[ApiVersion("1")]
[Route("api/v1/business-domains")]
public class BusinessDomainsController : Controller
{
 //...
}

[ApiVersion("1")]
[Route("api/v1/business-sub-domains")]
public class BusinessSubDomainsController : Controller
{
 //...
}

Now we just need to let Swashbuckle know about the different versions. So we'll go back and make some changes to our ConfigureSwaggerGen method in our SwaggerHelper class.

We get the Web API assembly, and get the version numbers from the APIVersion attribute. For each version number we generate a Swagger document.

In the ApplyDocInclusions method we ensure that the controller operations are correctly placed in the right Swagger file. That is, the ApiController actions go in the V2 document and the BusinessDomainsController and BusinessSubDomainsController in the V1 document. If you do not include these inclusions, then all actions will go in each document.


public static void ConfigureSwaggerGen(SwaggerGenOptions swaggerGenOptions)
{
    var webApiAssembly = Assembly.GetEntryAssembly();
    AddSwaggerDocPerVersion(swaggerGenOptions, webApiAssembly);
    ApplyDocInclusions(swaggerGenOptions);
    IncludeXmlComments(swaggerGenOptions);
}

private static void AddSwaggerDocPerVersion(SwaggerGenOptions swaggerGenOptions, Assembly webApiAssembly)
{
    var apiVersions = GetApiVersions(webApiAssembly);
    foreach (var apiVersion in apiVersions)
    {
        swaggerGenOptions.SwaggerDoc($"v{apiVersion}",
            new Info
            {
                Title = "Govrnanza Registry",
                Version = $"v{apiVersion}",
                Description = @"REST services for managing your API ecosystem

## Business Domains and Sub Domains ##
Breakdown your business into domains and sub domains in order to better manage your APIs.

## Tags ##
Create tags to help you classify your APIs on multiple dimensions or link APIs that form cross-cutting business processes

## API Management ##
Manage creation, update and deletion of the APIs in your registry. Classify your APIs by business sub domain and add tags for further classification.",
                Contact = new Contact()
                {
                    Name = "Govrnanza",
                    Url = "https://github.com/Vanlightly/Govrnanza"
                }
            });
    }
}

private static void ApplyDocInclusions(SwaggerGenOptions swaggerGenOptions)
{
    swaggerGenOptions.DocInclusionPredicate((docName, apiDesc) =>
    {
        var versions = apiDesc.ControllerAttributes()
            .OfType<ApiVersionAttribute>()
            .SelectMany(attr => attr.Versions);

        return versions.Any(v => $"v{v.ToString()}" == docName);
    });
}

private static IEnumerable<string> GetApiVersions(Assembly webApiAssembly)
{
    var apiVersion = webApiAssembly.DefinedTypes
        .Where(x => x.IsSubclassOf(typeof(Controller)) && x.GetCustomAttributes<ApiVersionAttribute>().Any())
        .Select(y => y.GetCustomAttribute<ApiVersionAttribute>())
        .SelectMany(v => v.Versions)
        .Distinct()
        .OrderBy(x => x);

    return apiVersion.Select(x => x.ToString());
}

We also need to change our original code in our ConfigureSwaggerUI method in the SwaggerHelper class. We need to generate a Swagger UI endpoint per version number.

We replace:


public static void ConfigureSwaggerUI(SwaggerUIOptions swaggerUIOptions)
{
    swaggerUIOptions.SwaggerEndpoint($"/api-docs/v1/swagger.json", $"V1 Docs");
}

With the code:


public static void ConfigureSwaggerUI(SwaggerUIOptions swaggerUIOptions)
{
    var webApiAssembly = Assembly.GetEntryAssembly();
    var apiVersions = GetApiVersions(webApiAssembly);
    foreach (var apiVersion in apiVersions)
    {
        swaggerUIOptions.SwaggerEndpoint($"/api-docs/v{apiVersion}/swagger.json", $"V{apiVersion} Docs");
    }
    swaggerUIOptions.RoutePrefix = "api-docs";
}

Now let's run it and see the Swagger docs generated.

V1 has the BusinessDomains and BusinessSubDomains resources.

V2 has the APIs resource.

 

Change the Look and Feel

Swashbuckle let's you use a custom CSS file so you can customise the look and feel of the Swagger UI. For example you could change the colours to the corporate colours of your company.

I am not a front-end guy so I took a css from this Github repo called theme-feeling-blue-V2.css. There are CSS for Swagger V2 and V3 but Swashbuckle generates V2.

To inject this CSS file I did the following:

Add a folder called api-docs in the wwwroot folder and add the css file to it.

wwwroot-css.PNG

 

We need to be able to serve static files so we need to ensure that we add the static files middleware in our Startup.cs


public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
    // ... 

    app.UseStaticFiles();
    app.UseSwagger(SwaggerHelper.ConfigureSwagger);
    app.UseSwaggerUI(SwaggerHelper.ConfigureSwaggerUI);

    app.UseMvc();
}

Finally we need to the following line to our ConfigureSwaggerUI method in our SwaggerHelper class:


swaggerUIOptions.InjectStylesheet("theme-feeling-blue-v2.css");

Now when we start up the application we see a blue themed Swagger UI.

 

Conclusion

Swashbuckle makes it super simple to add a Swagger UI page to your API. Having this page during development is invaluable and makes consuming APIs as a developer, tester, functional analyst etc really easy. When you add extra information with Markdown, it can make complex APIs easier to understand and use. Also, putting the documentation with the code means that it is easier to keep documentation up to date. When documentation is kept in a wiki in the corner, bad habits can prevail and the wiki ends up where documentation goes to die.

Whether or not you want the embedded Swagger UI in production is another matter. If you don't want to then just execute the UseSwagger and UseSwaggerUI methods in the Startup.cs Configure method oncidtionally based on environment.

There's more to Swashbuckle than we covered here, head over to the repo to get more details. We'll be returning to Swashbuckle in the future when we add security to the API. Swagger UI offers OAuth 2.0 integration and we'll need that when we add IdentityServer4.

Link to the API Series Introduction