Generating Stellar OpenAPI Specs
This guide by our development team shows you how to generate stellar OpenAPI specs.
23.07.2024
Development

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;
}
Copy code
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();
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);
}
}
});
}
Copy code
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.");
Copy code
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 theSwaggerGenOptions
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
{
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
}
Copy code
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();
c.DocumentFilter();
c.OperationFilter();
// ...
}
Copy code
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
{
...
}
Copy code
- 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> GetResponseAsync(...)
{
...
}
Copy code
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 });
}
}
}
Copy code
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"
}
],
Copy code
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:
///
/// Returns the last reading
///
/// The id of the entity.
/// The type of the reading data.
/// A that represents the asynchronous operation.
/// Returns the most recent normalized reading for an entity.
[HttpGet("last")]
[MapToApiVersion("1.0")]
[MapToApiVersion("1.0-internal")]
[ProducesResponseType(typeof(Connect.Api.Models.Reading.V1.Reading), 200)]
public async Task> GetLastReadingAsync([BindRequired] [FromRoute] Guid entityId,
[FromQuery] string? type = "electricity")
Copy code
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
Domain Model and Security Principles of the re.alto API Platform
This article is intended as a guide/introduction for developers/architects using our API platform.
Selling Energy Data: Adopting an API-as-a-Product Mindset
API-as-a-Product: What to consider when marketing your API
New Feature: Guided Onboarding of EVs
This article looks at the benefits of one of our newer features: the guided onboarding of EVs to the re.alto platform.
Real-Time Syncing of API Documentation to ReadMe.io
This guide shows you how to sync API documentation to readme.io in real-time
re.alto Obtains ISO 27001 Certification (Revised 2022 Version)
We’re proud to share that re.alto has successfully completed ISO 27001 certification, this time for the new, revised version of the standard.
New Feature: Charge Sessions API
We’re excited to announce that our EV connectivity platform now has a new added feature available: the Charge Sessions API.
Scaling with Azure Container Apps & Apache Kafka
This article, written by re.alto’s development team, explains how to scale with Azure Container Apps and Apache Kafka.
Containerisation: Introduction & Use Case
An introduction to the technologies used in containerisation (by re.alto's dev team)
Remote EV Charging via Official APIs
Remote Charging via Official APIs (Mercedes Benz / Tesla Connector)
Vehicle IoT: an Alternative to Smart Charge Poles
Vehicle IoT: an alternative to smart charge poles / smart charge points.
Alternative APIs for Dark Sky
Alternative APIs for Dark Sky and the strategic value of weather forecasting data in energy.
The First Open Data List of European Electricity Suppliers
A database of information about electricity suppliers in Europe.
A Guide to Monetising APIs
A look at the potential to monetise APIs (and existing digital assets and data).
What is an API?
The term API is an acronym. It stands for “Application Programming Interface.”