C#

Swagger in AspNetCore WebApi

A walkthrough on adding description and examples to an endpoint with Swagger in AspNetCore WebApi project

  • #api
  • #docs
  • #swagger

In this blog, I have condensed the correct steps to enrich the Swagger page by modifying the description and adding examples, starting off with a sample project.

Version Check

The versions of the technologies used for demonstration are as follows:

IndexTechnologyVersion
1.dotnet CLI7.0.304
2.Target Frameworknet6.0
3.Swashbuckle.AspNetCore6.5.0
4.Swashbuckle.AspNetCore.Annotations6.5.0
5.Swashbuckle.AspNetCore.Filters7.0.12

Setting up

First, we will use the weather API template provided by .NET console.

dotnet new webapi -o WeatherApi

Installing Dependencies

After the project has been generated, verify the Swashbuckle.AspNetCore package has already been installed either by looking at the .csproj file or NuGet explorer in Visual Studio.

There are two more Swagger dependencies that we'll need.

  • Swashbuckle.AspNetCore.Annotations
  • Swashbuckle.AspNetCore.Filters

As a side note, if we are using an older version of Swagger, we might need to install different packages listed above. The different versioning of the packages tripped me off a few times already. Here is the reference table for the Filters package to be installed.

Swagger UI

In the WeatherForecastController.cs, there should be an existing endpoint similar to this.

WeatherForecastController.cs
[HttpGet(Name = "GetWeatherForecast")]
public IEnumerable<WeatherForecast> Get()
{
    return Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray();
}

The resulting Swagger page looks like this. As we can see, there is an example response being displayed.

The default Swagger page

We can change the method signature to async and the Swagger page will still show the same content.

WeatherForecastController.cs
[HttpGet(Name = "GetWeatherForecast")]
public async Task<IEnumerable<WeatherForecast>> Get()
{
    // Pretending there is some async code execution
    return await Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
    {
        Date = DateTime.Now.AddDays(index),
        TemperatureC = Random.Shared.Next(-20, 55),
        Summary = Summaries[Random.Shared.Next(Summaries.Length)]
    })
    .ToArray());
}

Modifying Response Description

To modify the description text from the default Success for HTTP Code 200 to something else, we need to use the Swashbuckle.AspNetCore.Annotations library.

Head over to Program.cs and modify the AddSwaggerGen method call as follows to enable the feature.

Program.cs
- builder.Services.AddSwaggerGen();
+ builder.Services.AddSwaggerGen(opt =>
+ {
+     opt.EnableAnnotations();
+ });

Next, go back to the endpoint and add the annotation for HTTP codes that are applicable.

WeatherForecastController.cs
+ using System.Net;
+ using Swashbuckle.AspNetCore.Annotations;

  [HttpGet(Name = "GetWeatherForecast")]
+ [SwaggerResponse((int)HttpStatusCode.OK, "Successfully retrieved info.")]
+ [SwaggerResponse((int)HttpStatusCode.NotFound, "Info not found.")]
  public async Task<IEnumerable<WeatherForecast>> Get()
  {
      // Pretending there is some async code execution

Description of status code changed

The descriptions of each HTTP codes are now modified according to what was specified in the annotation.

Adding Examples

To add examples, we will need to enable the Swagger to support this in the Program.cs as well.

Program.cs
+ using System.Reflection;
+ using Swashbuckle.AspNetCore.Filters;

  builder.Services.AddSwaggerGen(opt =>
  {
      opt.EnableAnnotations();
+     opt.ExampleFilters();
  });

+ builder.Services.AddSwaggerExamplesFromAssemblies(Assembly.GetEntryAssembly());

After that, create an example file named WeatherForecastExample.cs and populate the file with the following contents. Note that this is the code which you will need to exclude from code coverage too.

WeatherForecastExample.cs
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using Swashbuckle.AspNetCore.Filters;

[ExcludeFromCodeCoverage]
public class WeatherForecastSingleExample : IExamplesProvider<IEnumerable<WeatherForecast>>
{
    public IEnumerable<WeatherForecast> GetExamples()
    {
        return new[]
        {
            new WeatherForecast
            {
                Date = DateTime.Now,
                Summary = "Chilly",
                TemperatureC = 32,
            },
            new WeatherForecast
            {
                Date = DateTime.Now,
                Summary = "Sweltering",
                TemperatureC = 40,
            },
        };
    }
}

Navigate back to the controller file and add the following annotation to the endpoint.

WeatherForecastController.cs
+ using Swashbuckle.AspNetCore.Filters;

  [HttpGet(Name = "GetWeatherForecast")]
  [SwaggerResponse((int)HttpStatusCode.OK, "Successfully retrieved info.")]
  [SwaggerResponse((int)HttpStatusCode.NotFound, "Info not found.")]
+ [SwaggerResponseExample((int)HttpStatusCode.OK, typeof(WeatherForecastExample))]
  public async Task<IEnumerable<WeatherForecast>> Get()
  {
      // Pretending there is some async code execution

Added response to Swagger

The examples on the Swagger page for that endpoint will be updated to match the example file.

Using IActionResult

If we try to wrap the endpoint response in an IActionResult, the example response will disappear. I think that Swagger can't serialize the response because the IActionResult response has already serialized the actual object.

WeatherForecastController.cs
  [HttpGet(Name = "GetWeatherForecast")]
  [SwaggerResponse((int)HttpStatusCode.OK, "Successfully retrieved info.")]
  [SwaggerResponse((int)HttpStatusCode.NotFound, "Info not found.")]
  [SwaggerResponseExample((int)HttpStatusCode.OK, typeof(WeatherForecastExample))]
- public async Task<IEnumerable<WeatherForecast>> Get()
+ public async Task<IActionResult> Get()
  {
      // Pretending there is some async code execution
-     return await Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
+     return this.Ok(await Task.FromResult(Enumerable.Range(1, 5).Select(index => new WeatherForecast
      {
          Date = DateTime.Now.AddDays(index),
          TemperatureC = Random.Shared.Next(-20, 55),
          Summary = Summaries[Random.Shared.Next(Summaries.Length)]
      })
-     .ToArray());
+     .ToArray()));
  }

The Swagger page will now looks like this.

Example missing due to IActionResult

To fix that, add an additional annotation to the endpoint as follows.

WeatherForecastController.cs
  [SwaggerResponse((int)HttpStatusCode.NotFound, "Info not found.")]
  [SwaggerResponseExample((int)HttpStatusCode.OK, typeof(WeatherForecastExample))]
+ [ProducesResponseType(typeof(WeatherForecastExample), (int)HttpStatusCode.OK)]
  public async Task<IActionResult> Get()
  {
      // Some async code execution

The example should show up again in the Swagger page.

References

Kirsten. Swagger not generating model for object wrapped by IActionResult.https://stackoverflow.com/questions/53105513/swagger-not-generating-model-for-object-wrapped-by-iactionresult
Frear, M. Swagger.AspNetCore.Filters. Retrieved 2024, March 26 from https://github.com/mattfrear/Swashbuckle.AspNetCore.Filters
Rob. ASP.net Core - SwaggerResponseExample not outputting specified example.https://stackoverflow.com/questions/61896978/asp-net-core-swaggerresponseexample-not-outputting-specified-example
Singhal, N. Multiple Request/Response examples for Swagger UI in ASP.NET core.ย Medium. https://medium.com/@niteshsinghal85/multiple-request-response-examples-for-swagger-ui-in-asp-net-core-864c0bdc6619
Swagger Petstore.ย Swagger. Retrieved 2024, March 26 from https://petstore.swagger.io/#/