Generating Stellar OpenAPI Specs

23.07.2024

Nowadays, OpenAPI Specifications are the de facto standard for describing and documenting APIs, enabling easy interoperability between API providers and API clients and consumers.

Producing a high quality OpenAPI spec will have a significant impact on the success of your API as it is the basis for a lot of tools that sit higher in the stack like:

  • swagger UI
  • developer portals
  • autogenerated API clients (language agnostic)
 

In the .NET world, the most common way to generate an API spec is by using swagger. When properly configured, swagger supports versioning and automatic documentation based on the autogenerated XML docs.

Hence the better and more complete the XML docs, the better the resulting documentation and specification of the API.

In the next sections, we will be putting it all together from a 0 to a hero API spec that is:

  • versioned
  • serves multiple audiences
  • well documented
  • automation ready
 

Enable Versioning

The first step is fairly simple. We enable versioning and configure swagger:

				
					public static IServiceCollection AddVersioning(this IServiceCollection services)
{
    services.AddApiVersioning(options =>
       {
            options.DefaultApiVersion = new ApiVersion(0, 0);
            options.AssumeDefaultVersionWhenUnspecified = true;
            options.ReportApiVersions = true;
        })
        .AddApiExplorer(options =>
         {
             options.GroupNameFormat = "'v'VVV";
             options.SubstituteApiVersionInUrl = true;
         });

    return services;
}
				
			

The configuration part is as generic as possible to be reusable in multiple different APIs:

				
					public static IServiceCollection AddSwagger(this IServiceCollection services,
																						string apiTitle, string apiDescription)
{
    services.AddSingleton(new SwaggerApiMetadata(apiTitle, apiDescription));
    services.ConfigureOptions<ConfigureSwaggerOptions>();

    return services.AddSwaggerGen(c: SwaggerGenOptions =>
    {
        //* ... */
            
        var xmlDocumentationFilePaths = Directory.GetFiles(AppContext.BaseDirectory,
                "*.xml", SearchOption.TopDirectoryOnly).ToList();

        foreach (var fileName in xmlDocumentationFilePaths)
        {
            var xmlFilePath = Path.Combine(AppContext.BaseDirectory, fileName);
            if (File.Exists(xmlFilePath))
            {
                c.IncludeXmlComments(xmlFilePath, includeControllerXmlComments: true);
            }
        }
    });
}
				
			

The above are used like this from an API project:

				
					builder.Services.AddVersioning(); 
builder.Services.AddSwagger("Connect Readings API", 
														"Provides endpoints to get entity readings.");
				
			

Now let’s unpack what is going on.

  • In lines 4 and 5, the SwaggerApiMetadata record is needed to be able to encapsulate and then inject the API properties specified at compile time (the title and description) in the SwaggerGenOptions configurator at runtime.
  • Just before the code in line 7 will execute, the ConfigureSwaggerOptions configurator will run and will set the metadata for each version defined in the APIs. The configurator looks something like this:
				
					class ConfigureSwaggerOptions(IApiVersionDescriptionProvider provider,
 SwaggerApiMetadata apiMetadata) : IConfigureOptions<SwaggerGenOptions>
{
    public void Configure(SwaggerGenOptions options)
    {
        foreach (var versionDescription in provider.ApiVersionDescriptions)
        {
            options.SwaggerDoc(versionDescription.GroupName, 
											            new OpenApiInfo
													        {
												            Title = apiMetadata.Title,
												            Version = versionDescription.ApiVersion.ToString(),
												            Description = apiMetadata.Description
													        });
        }
    }
    //shortened for brevity
}
				
			

Line 11 to 21 will pick up on all XML doc files that are found and loaded into swagger.

Line 9 is a placeholder for features detailed in the next section.

Customising Swagger Generation

There are times when you want to make programmatic changes to the way the final OpenAPI spec json file is generated. For example, exposing in the OpenAPI spec an HTTP header that is used by all endpoints, which in turn will show up on the UI to be easily filled out by the user.

For this, we can use swagger filters. Think of filters like .NET middleware but for swagger.

				
					services.AddSwaggerGen(c: SwaggerGenOptions =>
{
   c.DocumentFilter<AddServerFilter>();
   c.DocumentFilter<AddSecuritySchemaFilter>();
   c.OperationFilter<AddCommonHeadersFilter>();
   // ...
}
				
			

Document filter applies to the whole document while the operation filters are applied for each endpoint path.

Everything so far can be nicely packaged in a shared nuget library to be reused across multiple API services.

Multiple API Audiences

It could often be the case where you might have in the same API service both public endpoints that are exposed to partners via a gateway and private, internal ones that are only used by your team to carry out admin tasks or what not.

The internal endpoints will be part of the same logical API version, ex: v1, v2, v3 etc. but should be hidden from the public spec that the partners really see, and on the other hand still be visible in the internal swagger UI where your team (developers) can have access.

Considering the infrastructure pieces from previous sections are in place there are only two things left to do:

  • define internal versions of the API
				
					[ApiController]
[ApiVersion("1.0")]
[ApiVersion("1.0-internal")]
[Produces("application/json")]
[Route("v{version:apiVersion}/entities")]
public sealed class MyController : ApiController
{
...
}
				
			
  • map endpoints to the either public, private or both type of versions
				
					[HttpGet("last")]
[MapToApiVersion("1.0")]
[MapToApiVersion("1.0-internal")]
[ProducesResponseType(typeof(MyResponse), 200)]
public async Task<ActionResult<Myresponse>> GetResponseAsync(...)
{
...
}
				
			

Strictly speaking there are two asp.net versions defined but only one logical version (1.0). If you leave out one version, that endpoint will simply not appear in the generated spec for that asp.net version.

This will generate a swagger UI that will allow us to pick which definition to use:

Fine Tuning

Usually you will need to fine tune the specs based on the audience. For example, if we want to expose the spec in a developer portal that will probably have a different host server than the localhost version in swagger UI. The ideal place to make these conditional tweaks is in swagger filters.

For instance, the AddServerFilter from the previous section can look something like this:

				
					public class AddServerFilter : IDocumentFilter
{
    public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
    {
        var url = "https://platform.domain.com/api"; //

        if (!context.DocumentName.Contains("-internal"))
        {
            swaggerDoc.Servers.Add(new OpenApiServer { Url = url });
        }
    }
}
				
			

This will add the following attribute in the resulting OpenAPI spec, that will be later picked up by developer portal tooling and UI.

				
					"servers": [
    {
      "url": "https://platform.domain.com/api"
    }
  ],
				
			

Mapping XML Docs to Swagger UI

The quality of the resulting OpenAPI spec is determined from the quality on the XML docs, attributes and signature and data types of the API endpoints. The more complete they are, the easier it is to use swagger UI or any other similar tool based on OpenAPI specifications.

Take the following code as a source:

				
					/// <summary>
/// Returns the last reading
/// </summary>
/// <param name="entityId" example="5cae0e62-329b-4e9e-b70f-6162e1705538">The id of the entity.</param>
/// <param name="type">The type of the reading data.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous operation.</returns>
/// <remarks>Returns the most recent normalized reading for an entity.</remarks>
[HttpGet("last")]
[MapToApiVersion("1.0")]
[MapToApiVersion("1.0-internal")]
[ProducesResponseType(typeof(Connect.Api.Models.Reading.V1.Reading), 200)]
public async Task<ActionResult<Connect.Api.Models.Reading.V1.Reading>> GetLastReadingAsync([BindRequired] [FromRoute] Guid entityId,
        [FromQuery] string? type = "electricity")
				
			

This will generate the following swagger UI. Notice the entityId example and default value for type. They are both pre populated and ready to use. Notice the remarks section which is totally optional and not added by default. Once present, it will give the API endpoint a more detailed description.

Other tools provide similar capabilities and will offer the clients of the API a greatly improved experience.

Using the same XML docs in the models that are used as part of the endpoint interface, we obtain a nicely documented schema. Notice all the descriptions in the response schema below:

Next Steps

At this point, we have a well documented spec with multiple versions for both internal and public clients that is fine-tuned to work locally but also for publishing to a publicly accessible location from where partners can try out our API.

The next step will be to put in place some automation that will do this publishing automatically and keep it in sync with the code throughout the lifetime of the API.

 

Coming up: Our next technical article will look at real time syncing of API documentation to Readme.io.

Explore more

API

What is an API?

API hub What is an API? The term API is an acronym, and it stands for “Application Programming Interface.” An API is a vital building block in any digital transformation strategy, and one of the most valuable in achieving scale, reach and innovation. Behind every mobile app and online experience,

Read More »

API

API

A guide to monetising APIs

API hub A guide to monetising APIs In this guide learn about monetising energy APIs, and their commercial value as a new channel for monetising existing digital assets and data. APIs aren’t new. In fact, they’ve been around for quite a while, embraced fully by industries from all new digital

Read More »

API

APIs in energy

API hub APIs in energy Digitalisation in the energy sector. Unlike other industries where digitisation is the norm, the energy sector is a child by comparison.  In many countries, electricity is still purchased via a sales representative using a paper contract. Many energy retailers, (renewable) energy producers or grid operators

Read More »

API

API

re.alto Talks Part II

API hub re.alto Talks, Part I: Realising the energy transition in times of change This webinar is Part One of a three-part series on “Realising the energy transition in times of change”. https://youtu.be/YBdnui2y904 Explore more

Read More »

API

API

re.alto Talks Part III

API hub re.alto Talks, Part III: The benefits of an API marketplace in energy This webinar is Part Three of a three-part series on “Realising the energy transition in times of change”. https://youtu.be/C2IRj699eWg Explore more

Read More »

API

API

re.alto API overview

API hub re.alto API overview re.alto energy – Technical Setup for Existing APIs On the re.alto platform, individual Users can search for, and subscribe to a Provider’s API products. These subscriptions are monitored, tracked, and (if monetised) billed and settled individually by the re.alto platform.   Each subscription made by

Read More »

API

Frequently asked questions

API hub re.alto Marketplace FAQ What is the re.alto API marketplace? The re.alto marketplace is a marketplace for digital energy products and services, delivered via APIs. You can register as a provider or a consumer/user. As a provider, your digital products via APIs are uploaded to the re.alto marketplace, where

Read More »

API

Energy Quantified and re.alto case study

API hub Energy Quantified and re.alto The API-led approach to digital scale and industry growth As decentralisation of the energy market drives the rise of a host of smaller industry players, easy access to digital products at volume is now an essential factor for the rapid scalability desired by those

Read More »

API

APIs are everywhere – short animation

API hub APIs are everywhere – short video animation We are in living in an increasingly API-centric world. APIs are everywhere you look – and you might not even realise it. Need evidence? Gartner considers API management tools an essential component of the unrealized hybrid integration platform (HIP), currently an

Read More »

API

Adopting an API as a product mindset with APIs

API hub Adopting an ‘API as a product’ mindset with APIs APIs have enormous potential to open companies up to new revenue streams, unlock new markets and extract value from existing assets. To fully realise this potential however, APIs need to be lifted out of the sole domain of the

Read More »

API

Three things you may not know about APIs

API hub Three things you may not know about APIs API. Application Programming Interface. It is the communications channel between two web-based applications, allows the exchange of data without any connecting physical infrastructure. APIs lie at the very heart of digital transformation. According to the 2020 State of the API

Read More »

API

Alternative APIs for Dark sky

API hub Alternative APIs for Dark sky and the strategic value of weather forecasting data in energy In this article you’ll be introduced to weather data use cases and the importance of weather data within the renewable energy and digital landscape.  We also do a deeper dive into alternative APIs

Read More »

API

Dev

Dev

Scaling with Azure Container Apps and Apache Kafka

API hub Scaling with Azure Container Apps & Apache Kafka 11.06.2024 This article, written by re.alto’s development team, explains how to scale with Azure Container Apps and Apache Kafka. While such documentation already exists for use with Microsoft products, our development team did not find any similar documentation on how

Read More »