Dokumentace REST API pomocí OpenAPI a Swagger UI

03.07.2021

Slovníček

REST API Seznam doporučení nebo pokynů pro návrh API sepsaných v roce 2000 Roy Fieldingem. Většinou se REST API vztahuje k vytváření webových služeb, které komunikují přes HTTP/S protokol a používají JSON formát pro výměnu dat. I přes to, že je REST API velmi rozšířené, nejedná se o standard ani protokol, tudíž se implementace mohou lišit.
OpenAPI OpenAPI je specifikace ve formátu YAML nebo JSON pro popis REST API, která vychází z původní Swagger specifikace. Specifikace je čitelná jak vývojářem tak i strojově a lze podle ní například vygenerovat dokumentaci nebo jí použít i pro vygenerování kódu. Původní Swagger specifikace je z roku 2011 a první OpenAPI vyšlo v roce 2017.
Swagger UI Swagger UI umí na základě OpenAPI dokumentu vygenerovat webovou dokumentaci. Vypadat může například takto.
HATEOAS Zjednodušeně jde o to, že API má posílat ve svých odpovědích i další hyperlinky, které může klient použít. V takovém případě můžeme použít například HAL formát, který hyperlinky podporuje, ale zatím se jedná o RFC draft. Záleží zde na požadavcích a je potřeba uplatnit princip YAGNI. Lze se setkat i s pojmem RESTlike API, jelikož se bez této vlastnosti nejedná o plnohodnotné RESTful API.
Verzování Verzování je technika jak implementovat změny do našeho API, abychom byli zároveň zpětně kompatibilní. Existuje několik přístupů (URI, query parameter, custom header, content negotiation), kdy každý přístup má nějaké výhody a nevýhody.

Obsah

Úvod

Připravíme si REST API (bez HATEOAS a s URI verzováním) a pomocí OpenAPI a Swagger UI Nuget balíčků vytvoříme přes web přístupnou dokumentaci, kterou si pak může zobrazit kdokoliv, kdo bude s API pracovat.

Příprava API

Vytvoříme si WebAPI projekt. WebAPI šablona ve výchozím nastavení již OpenAPI podporuje a nainstaluje se nám doplněk Swashbuckle.AspNetCore.

dotnet new webapi
Do startupu se nám přidala následující konfigurace
services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "rest", Version = "v1" });
});
app.UseSwagger();
app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "rest v1"));

a zároveň máme k dispozici výchozí WeatherForecastController. Pokud API spustíme a přejdeme na stránku /swagger/index.html zobrazí se nám následující interaktivní dokumentace

máme tedy k dispozici velmi užitečný nástroj, nicméně jsme ještě neskončili a dokumentaci si vytuníme 😂🚀

Přidání podpory pro XML komentáře

Vygenerovanou Swagger dokumentaci můžeme obohatit o naše vlastní komentáře, které si ale musíme nejdříve povolit v Project > Properties > Build: Output. Zároveň potlačíme chybovou hlášku CS1591: Missing XML comment for publicly visible type or member, která by se nám jinak zobrazovala u většiny veřejných metod a proměnných.

Dáme swagger službě vědět, že si má načíst WebApi.xml.

services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("v1", new OpenApiInfo { Title = "WebApi", Version = "v1" });
    c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "WebApi.xml"));
});

A teď už si můžeme upravit naší API metodu. Metoda má summary a remarks komentář a také nově přijímá vstupní parametr objekt. Objekt obsahuje proměnnou se summary komentářem a required atributem, který se v dokumentaci také projeví. Do XML komentářů nelze psát speciální znaky a musíme je v případě potřeby nahradit.

/// <summary>
/// Get temperature in Celsius for each date.
/// </summary>
/// URL request example&#x003A; 
/// 
///     /weatherforecast?dates=2020-05-01&amp;dates=2020-05-02
///     
[HttpGet]
public IActionResult Get([FromQuery]ForecastHorizontInputModel model)
{
    // ...
    return Ok(result);
}
public class ForecastHorizontInputModel
{
    /// <summary>Collection of dates. Each date can be between 2020-01-01 and five days in advance.</summary>
    [Required]
    public List<DateTime> Dates { get; set; }
}

Rozdělení dokumentace na více stránek

Pokud má naše API hodně endpointů, může být přehlednější rozdělit Open API dokumentaci na více stránek. Toho dosáhneme pomocí ApiExplorerSettings atributu a úpravy ve startup.cs. Pro příklad přidám nový EarthquakeAlertController, který bude mít samostatnou Swagger UI stránku.

// WeatherForecastController.cs
[ApiController] 
[Route("[controller]")]
[ApiExplorerSettings(GroupName = "weather")]
public class WeatherForecastController : ControllerBase
// EarthquakeAlertController.cs
[ApiController]
[Route("[controller]")]
[ApiExplorerSettings(GroupName = "earthquake")]
public class EarthquakeAlertController : ControllerBase
Startup.cs
// ConfigureServices
services.AddSwaggerGen(c =>
{
    c.SwaggerDoc("weather", new OpenApiInfo { Title = "Weather Forecast", Version = "v1" });
    c.SwaggerDoc("earthquake", new OpenApiInfo { Title = "Earthquake Alert", Version = "v1" });
    c.IncludeXmlComments(Path.Combine(AppContext.BaseDirectory, "WebApi.xml"));
});
// Configure
app.UseSwagger();
app.UseSwaggerUI(c =>
{
    c.SwaggerEndpoint("/swagger/weather/swagger.json", "Weather API");
    c.SwaggerEndpoint("/swagger/earthquake/swagger.json", "Earthquake API");
});

Group name, Swagger Doc name a Swagger Endpoint name v cestě se musí rovnat. Mezi kontrolerama pak můžeme v dokumentaci snadno přepínat.

Verzování

Naše API se může rozvíjet a může nastat potřeba implementovat tzv. breaking změnu, která by mohla znefunkčnit případného API klienta (například zrušení endpointu, změna vstupního parametru, změna odpovědi, ...). Pokud klient není pod naší kontrolou, měli bychom mít pro takové případy připravenou verzovací strategii. Verzovacích strategií je několik a každá má své pro a proti. V tomto případě budu specifikovat verzi přímo v URL adrese (URI neboli také URL path based verzování). Dokumentace bude tedy rozdělená podle jednotlivých verzí nikoliv nutně podle jednotlivých kontrolerů (jako jsme ručně nastavili výše).

Nastavení

Vytvoříme si nový projekt

dotnet new demoapi

Přidáme podporu pro verzování v podobě Nuget balíčku Microsoft.AspNetCore.Mvc.Versioning, který zaregistrujeme jako middleware ve Startup.cs.

dotnet add package Microsoft.AspNetCore.Mvc.Versioning
public void ConfigureServices(IServiceCollection services)
{
  //...
  services.AddApiVersioning(options =>
  {
    // Add response headers "api-supported-versions" and "api-deprecated-versions"
    options.ReportApiVersions = true;
  });

  services.AddVersionedApiExplorer(options =>
  {
    // Setup format such as v1.0
    options.GroupNameFormat = "'v'VV";

    // Set to true when URL path based versioning is in use
    options.SubstituteApiVersionInUrl = true;
  });
  //...
}
Zkopírujeme WeatherForecastController.cs a rozdělíme API na dvě verze
[ApiController]
[ApiVersion("1.0")]
[Route("api/v{version:apiVersion}/WeatherForecast")]
public class WeatherForecastController : ControllerBase
[ApiController]
[ApiVersion("2.0")]
[Route("api/v{version:apiVersion}/WeatherForecast")]
public class WeatherForecastV2Controller : ControllerBase

Při vytváření nové verze není potřeba duplikovat všechny metody (tedy i ty, které se nezměnily), ale lze použít atribut MapToApiVersion, pomocí kterého si můžeme nadefinovat, které metody jsou součástí, kterých API verzí (metoda tedy může být součástí předchozí i nové verze). Nyní nám budou fungovat následující odkazy:

  • /api/v1.0/WeatherForecast
  • /api/v2.0/WeatherForecast

Promítnutí do dokumentace

O verzování musíme dát vědět Swagger a SwaggerUI knihovnám a pro tento účel jsem si vypůjčil třídy z následujícího blogu, na který jsem při psaní tohoto článku narazil a pouze jsem je drobně upravil. Vytvoříme tedy následující třídy: