.NET Nakama

Improving your .NET skills

Enriched Web API Documentation using Swagger/OpenAPI in ASP.NET Core

January 04, 2022 (~25 Minute Read)
API DESIGN WEB API DOCUMENTATION SWAGGER OPENAPI ASP.NET CORE FLUENT VALIDATION

Introduction

Before selecting or attempting any integration with an API, most developers check out its API documentation. Keeping the API documentation up to date to reflect the software changes is challenging and requires time and effort. In the case of Web APIs, we would like to document the following:

  • Authorization Types (e.g., API Key, Bearer token, Authorization code, etc.)
  • Action Methods: The endpoints, HTTP Methods, Headers, etc.
  • Data Contracts: The description of the data to be exchanged between the service and a client. We can show each parameter’s name, type, restrictions, etc.
  • Examples that use the Web API to help consumers start quickly.

Ain't Nobody Got Time for Web API Documentation

It would be nice to have a standardized way of describing Web APIs, to allow both humans and computers to generate, discover and understand its capabilities without requiring access to the source code. Good news, 🎉 this standard exists and is called OpenAPI Specification (OAS), based initially on the Swagger Specification.

A Web API documentation provides the necessary information (e.g., endpoints, data contracts, etc.) to describe our Web API to our consumers. In addition, however, we may want to provide documentation for our source code to help developers improve and maintain it. Therefore, a Code documentation will provide information about our projects, classes, constructors, methods, etc. To automatically generate code documentation from comments, start by reading Wagner and Warren’s (2021) article.

In this article, we will learn about Web API documentation, how we can automatically generate it in ASP .NET Core and how to provide enriched information by offering examples, documentation per different versions, and many more 😉.

OpenAPI in .NET Core

In practice, in an ASP .NET Core project, we use specific Attributes and XML comments to define all the needed information (e.g., HTTP response codes, information messages, etc.) directly to our source code. We can automatically generate a JSON or YAML document (or set of documents) that describes our API by using this information. This generated document(s) is known as OpenAPI definition, which can be used by:

  • API Documentation generation tools (e.g., Swagger UI, Redoc, etc.) to render our OpenAPI definition (e.g., as a web page).
  • Code generation tools (NSwag, Swagger Codegen, etc.) to automatically generate the consumer’s source code in various programming languages.
  • Testing tools to execute API requests and validate responses on the fly.
  • Mock Server tools to provide a mock-fake server to return static or dynamically generated example responses.

So, by using OpenAPI in our Web API projects, we can automatically generate our documentation directly from or source code by maintaining the data annotations, XML comments and examples based on our actual data transfer classes. The two main OpenAPI implementations for .NET are Swashbuckle and NSwag. In the examples of the following sections, we will use the Swashbuckle tools.

Create a New Web API in .NET 6.0 with OpenAPI Enabled

Since the ASP.NET Core 5.0, the Web API templates enable the OpenAPI support by default. The template contains a NuGet dependency on Swashbuckle, register services, and add the necessary middlewares to generate a basic OpenAPI definition file and serve it in a Web UI (via the Swagger UI tool). In the following instructions, we will see how to create a new Web API project with enabled OpenAPI support.

That’s a great start! With only a few clicks, our new API projects support OpenAPI. However, there are things that we can configure and improve to provide more information to our API consumers. For example, we could perform the actions shown in the following figure and list. However, we will see more ways to enrich our API documentation in the following sections.

  • Set the appropriate response media type (e.g., application/json).
  • Provide examples with real-life data (not auto-generated with dummy data).
  • Include additional HTTP status codes. For example, to inform about the possible error HTTP status codes (4xx and 5xx).
Possible actions to improve the default OpenAPI documentation.
Figure 1. - Possible actions to improve the default OpenAPI documentation.

Provide OpenAPI Documentation in Existing Project

Let’s assume that our current project serves API with multiple versions, and we would like to provide OpenAPI Documentation for all versions. For that purpose, we will use the project that we created in .NET Nakama (2021, December).

To provide OpenAPI Documentation, we would start by installing the Swashbuckle.AspNetCore NuGet package. To support API documentation for multiple versions, we need to install the Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer NuGet package.

Installing the Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer NuGet package.
Figure 2. - Installing the Microsoft.AspNetCore.Mvc.Versioning.ApiExplorer NuGet package.

Our next step is to register some services and add some middlewares. I have created some extension methods that group all the necessary actions for that purpose. You can find the source code of the extensions and examples in GitHub.

So, we can download, modify and use the following extensions in our Program.cs (or in the System.cs in previous .NET versions). In addition, we can see an example of the Program.cs file here.

The AddApiVersioningConfigured extension (can be found in ConfigureApiVersioning.cs) has been updated (in comparison with the one provided in article .NET Nakama (2021, December) to support versioning on our documentation.

// Configure the API versioning properties of the project.
builder.Services.AddApiVersioningConfigured();

Then, we should use the AddSwaggerSwashbuckleConfigured extension (found in ConfigureSwaggerSwashbuckle.cs file) in our Program.cs file to configure the Swagger generator based on our needs. In the following sections, we will see in detail several enrichment scenarios.

// Add a Swagger generator and Automatic Request and Response annotations:
builder.Services.AddSwaggerSwashbuckleConfigured();

In the ConfigureSwaggerSwashbuckleOptions.cs file, we can configure the basic information (e.g., title, description, license, etc.) about our API (see Figure 3).

var info = new OpenApiInfo()
{
    Title = "Web API Documentation Tutorial",
    Version = description.ApiVersion.ToString(),
    Description = "A tutorial project to provide documentation for our existing APIs.",
    Contact = new OpenApiContact() { Name = "Ioannis Kyriakidis", Email = "[email protected]" },
    License = new OpenApiLicense() { Name = "MIT License", Url = new Uri("https://opensource.org/licenses/MIT") }
};

Add the UseSwagger() middleware in our Program.cs file to serve the generated OpenAPI definition(s) as JSON files and the UseSwaggerUI() middleware to server the Swagger-UI for all discovered API versions. In the following example, we serve the API documentation only in the development environment. However, we can decide which environments to provide the documentation based on our API audience. Remember that we could only generate the JSON files and serve them (e.g., with Swagger UI) in a separate project.

if (app.Environment.IsDevelopment())
{
    // Enable middleware to serve the generated OpenAPI definition as JSON files.
    app.UseSwagger();


    // Enable middleware to serve Swagger-UI (HTML, JS, CSS, etc.) by specifying the Swagger JSON endpoint(s).
    var descriptionProvider = app.Services.GetRequiredService<IApiVersionDescriptionProvider>();
    app.UseSwaggerUI(options =>
    {
        // Build a swagger endpoint for each discovered API version
        foreach (var description in descriptionProvider.ApiVersionDescriptions)
        {
            options.SwaggerEndpoint($"{description.GroupName}/swagger.json", description.GroupName.ToUpperInvariant());
        }
    });
}

Finally, if we want to see the SwaggerUI when start debugging, we will have to set the "launchUrl": "swagger" in the launchSettings.json file.

"TutorialWebApiDocumentation": {
  "commandName": "Project",
  "dotnetRunMessages": true,
  "launchBrowser": true,
  "launchUrl": "swagger",
  "applicationUrl": "https://localhost:44351;http://localhost:34885",
  "environmentVariables": {
    "ASPNETCORE_ENVIRONMENT": "Development"
  }
},

The following figure shows a Swagger UI example for an API with two versions containing essential information.

A Swagger UI example with essential information.
Figure 3. - A Swagger UI example with essential information.

Enrich Documentation via XML Comments and Attributes

The structure of the extracted XML documentation is defined in C# by using XML documentation comments. The documentation comments support several XML tags, such as summary, return description, exceptions, list of information, etc. In this article, we will use some of them. For more information about the recommended XML tags for C#, read Wagner B., et al. (2021) article.

Generate and Read Documentation Comments (XML)

To enable the documentation file generation, we should set the GenerateDocumentationFile option to True. Then, the compiler will find all comment fields with XML tags in our source code and create an XML document.

However, when this option is enabled, the compiler will generate CS1591 warnings for any public members in our project without XML documentation comments. We can exclude these warnings by including them in the NoWarn option.

So, to enable the GenerateDocumentationFile option and stop the CS1591 warnings we should:

  • Right-click the project in Solution Explorer and select Edit Project File.
  • Add the following PropertyGroup section (or include the options in an existing PropertyGroup).
<PropertyGroup>
    <GenerateDocumentationFile>True</GenerateDocumentationFile>
    <NoWarn>$(NoWarn);1591</NoWarn>
</PropertyGroup>

Next, we need to include the XML documentation comments in the OpenAPI definition file. For that purpose, we should use the IncludeXmlComments method in the ConfigureSwaggerSwashbuckle.cs file as shown in the following code.

// Set the comments path for the XmlComments file.
string xmlFile = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
string xmlPath = Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);

Finally, we should include the XML comments in our controller actions using triple slashes. For example, we can add a summary section to describe the performed action. Figure 4 presents a part of the Swagger UI that shows the API endpoint summary.

/// <summary>
/// Get a list with all "<see cref="SampleResponse"/>" items.
/// </summary>
[HttpGet]
public IEnumerable<SampleResponse> Get()
{
    // …action code
}
Example of the Swagger UI with XML comments (summary tag).
Figure 4. - Example of the Swagger UI with XML comments (summary tag).

API Responses (HTTP Codes and Types)

Any consumer would need beneficial information, such as the possible HTTP status codes and their response body. In Figure 5, we can see an example where the API endpoint could return its five possible HTTP status codes (200, 400, 409, 500, and 503). To enrich the response metadata for a given action method, we should:

  • Install the Swashbuckle.AspNetCore.Annotations NuGet package.
  • Update the controller actions to specify the possible response codes and their response types (if any) by using the response tag and the SwaggerResponse attribute.

In the following code example, we set the response description of the success HTTP status code in the response tag. In addition, we are setting all possible HTTP status codes and response types (e.g., IEnumerable<SampleResponse>) for the successful response.

/// <summary>
/// Get a list with all "<see cref="SampleResponse"/>" items.
/// </summary>
/// <response code="200">Returns a list with the available sample responses.</response>
[HttpGet]
[SwaggerResponse(StatusCodes.Status200OK, Type = typeof(IEnumerable<SampleResponse>))]
[SwaggerResponse(StatusCodes.Status400BadRequest)]
[SwaggerResponse(StatusCodes.Status409Conflict)]
[SwaggerResponse(StatusCodes.Status500InternalServerError)]
[SwaggerResponse(StatusCodes.Status503ServiceUnavailable)]
public IEnumerable<SampleResponse> Get()
{
    // ...
}
Swagger UI example with multiple response HTTP status codes.
Figure 5. - Swagger UI example with multiple response HTTP status codes.

Define Media Types (Consumed and Produced)

To define the appropriate consume and produce media types, we can decorate our controller with the [Consumes] and [Produces] attributes. For example, if we use the application/json, we can use the aforementioned attributes to decorate our controller, as shown in the following code example. Figure 6 shows the effect of the [Produces] attribute in Swagger UI.

[ApiController]
// ...other attributes
[Consumes("application/json")]
[Produces("application/json")]
public class HelloWorldController : ControllerBase
{
    // …controller's code
}
The effect of using the [Produces] attribute in Swagger UI.
Figure 6. - The effect of using the [Produces] attribute in Swagger UI.

Enrich Documentation via Filters

The Swashbuckle.AspNetCore.Filters NuGet package provides several functionalities that significantly improve our API documentation. For example, we can create valuable request and response examples with valid data, including security requirements, custom request and response headers, etc. In addition, we can manually test our API using these features just by using the Swagger UI without modifying the auto-generated request.

API Examples (Request and Response)

To provide request and response examples with valuable and valid data, we should:

  • Install the Swashbuckle.AspNetCore.Filters NuGet package.
  • Enable the automatic annotation of the [SwaggerRequestExample] and [SwaggerResponseExample] in the ConfigureSwaggerSwashbuckle.cs file. For that purpose, we should:
    • Use the options.ExampleFilters(); in the AddSwaggerGen(options).
    • Read the examples for the current assembly by registering the AddSwaggerExamplesFromAssemblies.
services.AddSwaggerGen(options => {
    options.ExampleFilters();
    // ... other stuff
});
services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly());

Then we can implement the IExamplesProvider interface for our data transfer classes (request and response). In the following source code example, we return an example for the SampleRequest class, shown in Figure 7.

using Swashbuckle.AspNetCore.Filters;
using TutorialWebApiDocumentation.V1.DTOs;

namespace TutorialWebApiDocumentation.V1.Examples
{
    public class SampleRequestExample : IExamplesProvider<SampleRequest>
    {
        public SampleRequest GetExamples()
        {
            return new SampleRequest()
            {
                Id = 2,
                Name = "Hello DotNetNakama",
            };
        }
    }
}
Swagger UI example for a request DTO.
Figure 7. - Swagger UI example for a request DTO.
Swagger UI example for a response DTO.
Figure 8. - Swagger UI example for a response DTO.

Input-Validation in API Documentation (Data Annotations and Fluent)

If we use System.ComponentModel.DataAnnotations attributes to validate our DTOs, then the validations are recognized and automatically included in the API documentation. However, we should perform the following steps if we are using FluentValidation for our DTOs.

services.AddFluentValidationRulesToSwagger();
Swagger UI example when using DataAnnotations VS FluentValidation.
Figure 9. - Swagger UI example when using DataAnnotations VS FluentValidation.

Security Information Scheme

To provide security information about the authorization scheme we are using (e.g., JWT Bearer), we can define it by using the following source code in the ConfigureSwaggerSwashbuckle.cs file. In this way, the Authorize button will be shown (Figure 10), and we can use it to specify the appropriate values (e.g., the bearer token in Figure 11).

options.OperationFilter<SecurityRequirementsOperationFilter>(true, "Bearer");
options.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
{ 
    Description = "Standard Authorization header using the Bearer scheme (JWT). Example: \"bearer {token}\"",
    Name = "Authorization",
    In = ParameterLocation.Header,
    Type = SecuritySchemeType.ApiKey,
    Scheme = "Bearer"
});
Swagger UI example with an Authorize button.
Figure 10. - Swagger UI example with an Authorize button.
Swagger UI example to set the JWT bearer token.
Figure 11. - Swagger UI example to set the JWT bearer token.

Mark Endpoints that Require Authorization

Our API endpoints may require authorization (using the [Authorize] attribute) or allow anonymous requests. As we can understand, it would be helpful to distinguish these cases in our Swagger UI. For that purpose, we can show the (Auth) text next to the endpoint’s summary to quickly see which of them require authorization (Figure 12). We can perform that by using the following OperationFilter in the ConfigureSwaggerSwashbuckle.cs file as shown below:

options.OperationFilter<AppendAuthorizeToSummaryOperationFilter>();
Swagger UI example with authorization indicator.
Figure 12. - Swagger UI example with authorization indicator.

Summary

An Web API documentation provides the necessary information (e.g., endpoints, data contracts, etc.) to describe our Web API to our consumers. However, keeping an up to date Web API documentation is challenging and requires time and effort. Therefore, an easy and automatic process as much as possible would be a great help.

The OpenAPI Specification provides a standardized way of describing Web APIs to allow both humans and computers to generate, discover and understand the API capabilities. In an ASP .NET Core project, we use specific Attributes and XML comments to define all the needed information (e.g., HTTP response codes, information messages, etc.) directly to our source code. Thus, we can provide up-to-date documentation easily as we keep our code up to date.

The essential OpenAPI tools that we would need are a) a tool to generate the OpenAPI definition and b) a tool to generate the API documentation (as a web page, PDF, etc.). This article showed how to use the Swashbuckle tools to create API documentation in an ASP.NET Core project (new or existing) with enriched information.

When creating a Web API Documentation, our goal should be to provide all the information that a consumer would need to communicate with our Web API (without having access to our code). This way, we would reduce the time to a first hello world (TTFHW) call (i.e., the time to integrate with our Web API). So let’s think about our consumers and create beautiful and valuable Web API documentation for them.

References

If you liked this article (or not), do not hesitate to leave comments, questions, suggestions, complaints, or just say Hi in the section below. Don't be a stranger 😉!

Dont't forget to follow my feed and be a .NET Nakama. Have a nice day 😁.