Documenting ASP.NET REST APIs with Swagger / Swashbuckle

Introduction

Swagger is a way to document a REST API, and tools can automate much of this process (but not all of it).  At the centre of things is a JSON file that describes the API.  You can create this file by hand or use a tool to generate e.g. from the API source code.  There are also tools that read the file to do useful things with it, such as create online documentation for the API or create clients that talk to the API.

Swashbuckle is a tool that can create a Swagger file for a REST API written in C# on ASP.NET.  There’s a different version for ASP.NET Core.  There are also other options for C# APIs such as NSwag.

This post isn’t intended to give you all the details, although it will give some details that I couldn’t find elsewhere.  I hope that it will give you the big picture clearly enough that the details that you find elsewhere make sense.  It’s also quite long, but I hope still understandable.

Swashbuckling probably comes from the words swash and buckler.  A swash is a long knife or short sword, and a buckler is a small shield like this:

A small round metal shield, with a diameter of amount 3 times the height of your hand
Image credit

What does it do for you?

You get extra end points for your API, that do the following useful jobs:

  • Serve the Swagger that describes the API
  • Provide a UI that describes the API i.e. it turns the Swagger into nice-looking HTML

A Swagger file might look a bit like this:

An excerpt of some Swagger
Image credit

The UI that Swashbuckle can build from that could look a bit like this:

Example
Image credit

The UI lets a user explore the API’s documentation in a nice way.  In some circumstances  you can set things up so that the user can log into their account with you and use the UI to make API calls against their account.  I.e. the UI becomes an active tool as well as a passive body of information.

The URLs for the APIs mentioned above default to:

  • my_API_root_URL/swagger/docs/v1 => Swagger
  • my_API_root_URL/swagger => UI

How do you set it up?

There are three main steps, but the details can vary.  For more details, see the instructions on the Swashbuckle site:

  1. Install the required NuGet package[s] into your API project.
  2. Turn on Swagger generation and optionally turn on the UI, by calling Swashbuckle extension methods on an instance of HttpConfiguration in your API project. You can call extra extension methods to customise its behaviour if you want to, but there are two fundamental ones that turn things on.
  3. Set your API project to generate an XML file.

When you next build your API project, it will also build your API’s documentation APIs.

I won’t go into how you wire up your API’s security mechanism into the documentation UI; I suggest that you look at the Swashbuckle site.

Where does the information come from?

There are two sources of information for Swashbuckle:

  1. An XML file generated by the compiler (see How do you set it up? above);
  2. Comments and attributes associated with actions (i.e. the public methods in the controllers, that respond to the incoming HTTP requests), which are read via reflection.

Not all comments are read – just XML comments.  These are comments that sit immediately above the actions, where the comment starts with /// rather than // or /*, and the comment text is enclosed in a pair of XML start / end tags.  The XML comments it reads are:

  • Action summary
  • Action remarks
  • Parameter summary
  • Type summary
  • Property summary

The attributes read by Swashbuckle and used when producing output by default are:

  • SwaggerOperation(“human readable version of the method name”)
  • SwaggerResponse(HTTP status code) – for non-success codes
  • SwaggerResponse(HTTP status code, “text description of returned stuff”, data type of returned stuff) – for success
  • HttpGet, HttpPost etc.
  • ApiExplorer(IgnoreApi = bool)
  • Obsolete(“message to caller about what they should use instead”)

All attributes are available to you should you go for more extreme customising (see below), but default Swashbuckle behaviour is influenced by only the attributes above.

Basic customising

There are several ways to influence Swashbuckle behaviour relatively simply and painlessly:

  • Changing the content of the XML comments and attributes on the methods – see IncludeXmlComments below;
  • Calling configuration methods;
  • Using a stylesheet and/or JavaScript file to tweak the output UI.

Configuration methods

There are two separate steps in Swashbuckle – you must do the first, and you can choose to also do the second or not.  The first step generates Swagger from your API’s source code.  The second step uses this Swagger to create the online help UI.  The two steps have their own configuration options, via separate config methods.  You wire things up like this:

GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
   c.SwaggerConfigMethod1();
   c.SwaggerConfigMethod2();
   …
})
.EnableSwaggerUi(d =>
{
    d.SwaggerUiConfigMethod1();
    d.SwaggerUiConfigMethod2();
    …
});

Swagger config methods

The class SwaggerDocsConfig defines several different options for SwaggerConfigMethodx, such as

  • IncludeXmlComments(pathToXMLfile)
  • DescribeAllEnumsAsString()
  • MapType<T>(Func<Schema> factory)
  • GroupActionsBy(Func<ApiDescription, string> keySelector)

If you have set your project to put the output XML into bin\A.xml, then you will probably want to pass this as argument to IncludeXmlComments: string.Format(@”{0}\bin\A.xml”, System.AppDomain.CurrentDomain.BaseDirectory).

The methods let you do things like:

  • Throw away actions and properties because they’re obsolete.
  • Change how things appear in the output – e.g. using the labels for enums rather than the corresponding ints, mapping primitive or array types through to different Swagger schemas.
  • Change how things are grouped and ordered in the output.

Swagger UI config methods

The class SwaggerUiConfig defines several different options for SwaggerUiConfigMethodx, such as:

  • DocumentTitle(string title)
  • InjectStylesheet(typeof(SwaggerConfig).Assembly, “path to style sheet”)
  • InjectJavaScript(typeof(SwaggerConfig).Assembly, “path to JS file”)

Using a stylesheet or JavaScript file can be annoying, because it can be fiddly to work out the correct path.  Assuming your project is called A.B, which has a folder called Content/Swagger, that contains a style sheet called X.css then the path to use is A.B.Content.Swagger.X.css.  You also need to set the stylesheet’s properties to Embedded Resource and Copy if Newer.

My API has versions and/or partitions

If your API has more than one version, e.g. v1, v2 etc, then you will probably want to reflect that in the documentation.  To help with this there is a configuration method for the Swagger generation, and another one for the UI.

The configuration method for Swagger generation is

MultipleApiVersions(Func<ApiDescription, string, bool> versionResolver, Action<VersionInfoBuilder> infoBuilder)

The job of versionResolver is to say whether (the description of) a given API end point should be included in the release tagged with a particular version string.  It could e.g. look for “/v1/” in the end point’s URL to say whether it belongs to the release tagged with “v1”.

The job of infoBuilder is to create a list of all valid releases for the API.  It might look something like this:

(vc) =>
{
   vc.Version("v1", "My amazing API v1");
   vc.Version("v1.5", "My amazing API v1.5");
}

You could use the same mechanism if your API falls into separate partitions.  For instance, one part that is available to users who have authorised themselves as belonging to set of users A, and another part for set of users B.

This is probably more involved than simply examining a URL to see if it contains a given string.  You will probably have to traverse bits of the ApiDescription (which you might also need to customise – see Problems and workarounds below).

If you use the multiple API versions mechanism for partitions, and you also have v1, v2 etc. for those partitions, then you will need to use a list of versions that is all the combinations (partition A v1, partition B v1, partition A v2 etc.) in the infoBuilder and the versionResolver.

The configuration method for the UI is EnableDiscoveryUrlSelector().  This adds a drop-down list to the header of the UI, that lets the user switch between versions without needing to know the URLs to the documentation for the different versions.

More extreme customising

Sometimes the customisation options above aren’t enough and you need to resort to more extreme measures.  Swashbuckle has three extension points called filters (which let you change parts of the Swagger generation process) and you can implement your own Swagger generator / provider if you want to be even more radical.  One approach you could take with your custom Swagger generator is to use the default generator and then customise its output.  The example given in the Swashbuckle documentation is to wrap a caching layer around the default Swagger generator.

You set these extensions up via the SwaggerConfig class:

GlobalConfiguration.Configuration
.EnableSwagger(c =>
{
   c.SchemaFilter<Schema filter class 1>();
   c.SchemaFilter<Schema filter class 2>();

   c.OperationFilter<Operation filter class 1>();
   c.OperationFilter<Operation filter class 2>();

   c.DocumentFilter<Document filter class 1>();
   c.DocumentFilter<Document filter class 2>();

   c.CustomProvider((defaultProvider) => {return new CustomSwaggerProviderClass(defaultProvider);});
}

Schema filters

Schema filters are a way of changing how C# types will be translated into Swagger-compliant JSON schemas in the output.  The MapType method within EnableSwagger might be enough if the output is a primitive or array type, but beyond that you might need a schema filter.  The interface for a schema filter is

void Apply(Schema outputSchema, SchemaRegistry schemaRegistry, Type inputType)

It maps from the input type to the output schema, and the schema registry is available in case other mappings need to be used as part of the process.

Operation filters

Operation filters let you change how a given operation (HTTP verb / URL pair) appears in the output.  The interface for an operation filter is:

void Apply(Operation operation, SchemaRegistry schemaRegistry, ApiDescription apiDescription);

It lets you change the output operation based on the corresponding input API description, and the schema registry is available in case it’s useful, or in case the filter needs to introduce new schemas.

Document filters

Document filters let you change the entire Swagger document.  The interface for a document filter is:

void Apply(SwaggerDocument swaggerDoc, SchemaRegistry schemaRegistry, IApiExplorer apiExplorer);

It lets you change the output swagger document based on the API explorer (that lets you traverse all the APIs’ descriptions) and the schema registry.

Custom providers

A custom provider lets you completely replace the default code that generates Swagger.  As the custom provider’s constructor is passed the default provider, the custom provider could act as a wrapper around the default provider.  Note that all the filters affect the default provider and not a custom provider, although if the custom provider acts as a wrapper to the default provider then the custom provider will still see the effects of the filters, albeit indirectly via the default provider.

The interface for a custom provider is:

Constructor(ISwaggerProvider defaultProvider)
SwaggerDocument GetSwagger (string rootUrl, string apiVersion);

Order that the custom provider and filters are called

If you have a custom provider, and two (or more) of each of the different kinds of filter, and the custom provider calls the default provider, then the order of things is:

  1. Custom provider is instantiated.
  2. Custom provider’s GetSwagger method is called, which in turn calls the default provider.
  3. In the default provider
    1. For each operation:
      1. For each type involved in the operation:
        1. Schema filter 1 (i.e. the schema filter passed first to the SchemaFilter config method) is passed the type.
        2. Schema filter 2 is passed the type.
      2. Operation filter 1 (the operation filter passed first to the OperationFilter config method) is passed the operation.
      3. Operation filter 2 is passed the operation.
    2. Document filter 1 (the document filter passed first to the DocumentFilter config method) is passed the whole document.
    3. Document filter 2 is passed the whole document.

This means that e.g. schema filter 2 will see the results of schema filter 1 etc.

Problems and work-arounds

Security

I haven’t been able to get the secure access working with the security set-up I needed.  There might be work-arounds that I haven’t got to yet, but so far no joy.

ApiExplorer

The Swashbuckle code gets a lot of its information via reflection.  It doesn’t use the C# Reflection library directly, instead it uses ApiExplorer.  The ApiExplorer returns a list of ApiDescription objects, and these crop up in the various Swashbuckle classes.

If, for any reason, you don’t like the default ApiExplorer behaviour (see Duplicate URLs and Custom Routing, below, for an example) then life gets interesting / tricky.  ApiExplorer has some private setters for key properties, and so you can’t directly create the objects you want.

Instead you have to manipulate the inputs to the method that produces the ApiDescription objects, so that it has no choice but to create the outputs you want.  The inputs are HttpControllerDescriptor objects, and the method that takes them is ExploreRouteControllers.  Unfortunately this is a private method, so you need to call this via reflection.

See a useful Stack Overflow answer for details of replacing ApiExplorer in this way.

Duplicate URLs and Custom Routing

In our API, we divide up users into groups based on the kind of credentials they present when they authenticate.  The different groups of users will expect different behaviour from a request sent to a given URL.  We have chosen to implement this by routing requests using such URLs to different controllers, using the custom routing feature in ASP.NET.

By default, the combination of ApiExplorer and Swashbuckle can’t cope with this.  The controllers that appear to clash are missed out entirely from the output.  The way around this is to use a custom ApiExplorer, and to partition the API by user group.  To support this, operation filters based on some useful example filters in GitHub (for Asp.Net Core) label each operation with the group it belongs to, the version resolver uses this to group the output, and a document filter makes sure that the operations are in the correct order within a group.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s