Documenter une API Web ASP.NET Core avec Swagger

Cet article est un aide-mémoire concernant les fonctions principales de Swagger UI. La documentation complête se trouve sur le repository GitHub du package Swashbuckle.AspNetCore qui est le package NuGet permettant d’installer Swagger sur une application ASP.NET Core.

Swagger est un outil permettant de documenter un API Web en présentant les différentes fonctions sous forme d’une page web. On peut ainsi utiliser l’interface web pour requêter les différentes fonctions de l’API.
Dans cet article, on va présenter les éléments de paramétrages principaux pour paramétrer Swagger dans le cas d’une API Web ASP.NET Core.

L’exemple d’API Web utilisé dans cet article se trouve dans la branche swagger du repository GitHub https://github.com/msoft/webapi_example.

Installation et configuration de Swagger

En commençant “from scratch”, pour créer une API Web ASP.NET Core, on peut exécuter la commande suivante après avoir installé la CLI .NET Core:

user@debian:~/% dotnet new webapi --name <nom du projet>

Pour installer Swagger dans une application ASP.NET Core et requêter facilement une API Web, il faut ajouter le package NuGet Swashbuckle.AspNetCore en exécutant la commande suivante:

user@debian:~/% dotnet add <chemin du fichier .csproj> package swashbuckle.aspnetcore

Dans le cas de l’exemple sur le repository GitHub https://github.com/msoft/webapi_example, il faut exécuter la commande:

user@debian:~/% dotnet add webapi_example/webapi_example.csproj package swashbuckle.aspnetcore

Pour configurer Swashbuckle, il faut ajouter les lignes suivantes dans le fichier StartUp.cs:

public void ConfigureServices(IServiceCollection services) 
{ 
    // ... 

    services.AddSwaggerGen(c => 
    { 
        c.SwaggerDoc("v1", new Info { Title = "Pizza API",  Version = "v1"}); 
    }); 
} 

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 
    // ... 

    app.UseSwagger(); 
    app.UseSwaggerUI(c => 
    { 
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Pizza API V1"); 
    }); 

    // ... 
} 

Dans l’extrait précédent:

  • services.AddSwaggerGen() permet de rajouter un service qui va analyser le code pour générer la description de l’API sous forme d’un document JSON (i.e. SwaggerDocument).
  • app.UseSwagger() va rajouter un middleware pour exposer le contenu du SwaggerDocument (contenant la description de l’API dans un document JSON). En pratique ce middleware va répondre quand une requête est faite à l’adresse http://localhost:5000/swagger/v1/swagger.json (adresse par défaut).
  • app.UseSwaggerUI() permet de rajouter un middleware pour présenter le SwaggerDocument sous forme d’une interface web. L’ajout de ce middleware est facultatif. On peut utiliser seulement le middleware Swagger, générer le SwaggerDocument et copier le code JSON dans https://editor.swagger.io/ pour l’utiliser.

Dans l’exemple, on peut lancer la compilation et l’exécution en exécutant successivement les lignes suivantes:

user@debian:~/% cd webapi_example
user@debian:~/webapi_example/% dotnet build
user@debian:~/webapi_example/% dotnet run

Par défaut:

  • La description JSON de l’API se trouve à l’adresse: http://localhost:5000/swagger/v1/swagger.json.
  • On peut accéder à l’interface de Swagger à l’adresse: http://localhost:5000/swagger/index.html.
Installation de .NET Core sur Linux

Pour installer .NET Core sur Debian, il faut suivre les étapes suivantes:

Améliorer la présentation de la description JSON

Quand on requête le description JSON à l’adresse http://localhost:5000/swagger/v1/swagger.json, le document est présenté de façon compacte. On peut améliorer la présentation du document en ajoutant la configuration suivante dans le fichier StartUp.cs:

public void ConfigureServices(IServiceCollection services) 
{ 
    // ... 

    services.AddMvc() 
        .AddJsonOptions(options => 
        { 
            options.SerializerSettings.Formatting = Formatting.Indented; 
        }); 
}

Configurer un endpoint

On peut spécifier un endpoint particulier pour accéder à Swagger en ajoutant les éléments de configuration suivants:

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 
    // ... 

    app.UseSwaggerUI(c => 
    { 
        c.RoutePrefix = "pizza-api-docs"
    }); 
}

Dans cet exemple, Swagger sera ainsi accessible à l’adresse: http://localhost:5000/pizza-api-docs.

Ajouter des informations globales

Les éléments de configuration suivants permettent d’ajouter des informations globales sur l’API.

Ajouter un titre HTML

Ce titre correspond au titre de la page HTML. Il apparaîtra dans l’onglet du browser. Pour l’ajouter, il faut rajouter la ligne suivante:

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 
    // ... 

    app.UseSwaggerUI(c => 
    { 
        c.SwaggerEndpoint("/swagger/v1/swagger.json", "Pizza API V1"); 
        c.DocumentTitle = "Custom HTML title"; 
    }); 
} 

Sans préciser davantage d’éléments de configuration, les informations sont présentées de cette façon sur l’interface web de Swagger:


Enrichir les informations globales

On peut ajouter d’autres informations globales, par exemple:

public void ConfigureServices(IServiceCollection services) 
{ 
    services.AddSwaggerGen(c => 
    { 
        c.SwaggerDoc("v1", new Info {  
            Title = "Pizza API",  
            Version = "v1", 
            Description = "API for pizza", 
            TermsOfService = "Terms of Service", 
            Contact = new Contact 
            { 
                Name = "Developer Name", 
                Email = "developer.name@example.com" 
            }, 
            License = new License 
            { 
                Name = "Apache 2.0", 
                Url = "http://www.apache.org/licenses/LICENSE-2.0.html" 
            }
        }); 
    }); 
}

En rajoutant ces informations, on obtient l’affichage suivant:


Prendre en compte les commentaires XML du projet

Swagger peut afficher les commentaires XML du projet, à condition qu’ils soient générés. Avec ASP.NET Core, pour générer les commentaires XML, il faut éditer le fichier .csproj de l’application ASP.NET Core et ajouter la ligne suivante correspondant au nœud XML <DocumentationFile>:

<PropertyGroup> 
<TargetFramework>netcoreapp2.2</TargetFramework> 
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel> 
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\<nom du fichier>.xml</DocumentationFile>
</PropertyGroup> 

Ensuite, il faut indiquer le chemin du fichier XML généré à Swagger dans le fichier StartUp.cs du projet:

public void ConfigureServices(IServiceCollection services) 
{ 
    // ...

    services.AddSwaggerGen(c => 
    {
        // ...

        var filePath = Path.Combine(System.AppContext.BaseDirectory, "<nom du fichier>.xml"); 
        c.IncludeXmlComments(filePath);
    });
} 

Ainsi, si on ajoute des informations dans les commentaires d’une action d’un controller, par exemple au niveau de l’action FindFlavour() du controller PizzaFlavourController:

/// <summary> 
/// Find flavour using flavour name 
/// </summary> 
/// <remarks>Usefull remark</remarks> 
/// <response code="200">Flavour retreived</response> 
/// <response code="400">Flavour not found</response> 
/// <response code="500">Bad request</response>
[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
[ProducesResponseType(typeof(IEnumerable<string>), 200)] 
[ProducesResponseType(typeof(string), 400)] 
[ProducesResponseType(500)] 
public ActionResult<IEnumerable<string>> FindFlavour(string flavourName) 
{ 
    // ... 
} 

On peut enrichir les informations relatives à la fonction de l’API:


Ajouter des informations sur les actions d’un controller

Pour que les actions d’un controller soient visibles dans Swagger, il faut utiliser les attributs permettant de définir les routes sur le controller (cf. RouteAttribute) et sur les actions (cf. HttpGetAttribute, HttpPostAttribute, HttpDeleteAttribute et HttpPutAttribute).

Par exemple en rajoutant ces attributs dans le controller PizzaFlavourController:

[Route("api/[controller]")] 
[ApiController] 
public class PizzaFlavourController : ControllerBase 
{ 
    [HttpGet] 
    public ActionResult<IEnumerable<string>> GetFlavourNames() 
    { 
        // ... 
    } 
    
    [HttpGet("{flavourName}")] 
    public ActionResult<IEnumerable<string>> FindFlavour(string flavourName) 
    { 
        // ...
    } 

    [HttpPost] 
    public ActionResult<int> Post([FromBody, BindRequired]AddPizzaFlavourRequest request) 
    { 
        // ...
    } 
} 

Avec cet exemple, on obtient:


Ajouter les “operation IDs”

On peut ajouter des identifiants relatifs aux actions avec le paramétrage suivant dans le fichier StartUp.cs du projet:

public void Configure(IApplicationBuilder app, IHostingEnvironment env) 
{ 
    // ...

    app.UseSwaggerUI(c => {
        // ...

        c.DisplayOperationId();
    });
} 

Sans davantage d’éléments de configuration, l’affichage indique les noms des actions:


En utilisant la propriété Name des attributs HttpGetAttribute, HttpPostAttribute, HttpPutAttribute et HttpDeleteAttribute, on peut préciser un nom particulier différent du nom de l’action.

Par exemple:

[HttpGet("{flavourName}", Name = "FindFlavourUsingFlavourName")] 
public ActionResult<IEnumerable<string>> FindFlavour(string flavourName) 
{ 
    // ... 
} 
 
[HttpPost(Name = "AddPizzaFlavour")] 
public ActionResult<int> Post([FromBody, BindRequired]AddPizzaFlavourRequest request) 
{ 
    // ... 
}

[HttpGet] 
public ActionResult<IEnumerable<string>> GetFlavourNames() 
{ 
    // ... 
} 

On obtient ainsi:


Liste des réponses possibles

En utilisant l’attribut ProducesResponseTypeAttribute, on peut indiquer toutes les réponses possibles d’une action. Swagger peut prendre en compte ces réponses dans la description d’une action.

Par exemple en rajoutant cet attribut dans l’action suivante:

[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
[ProducesResponseType(typeof(IEnumerable<string>), 200)] 
[ProducesResponseType(typeof(string), 400)] 
[ProducesResponseType(500)]
public ActionResult<IEnumerable<string>> FindFlavour(string flavourName) 
{ 
    // ... 
} 

On obtient l’affichage suivant:


Attributs FromQueryAttribute et FromBodyAttribute

Les attributs FromQueryAttribute et FromBodyAttribute permettent d’indiquer explicitement si le paramètre d’une action doit se trouver dans l’URL de la requête ou dans le corps d’un message HTTP. Ces attributs sont pris en compte par Swagger dans sa description.

Par exemple, si on utilise ces paramètres de la façon suivante:

[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
public ActionResult<IEnumerable<string>> FindFlavour([FromQuery]string flavourName) 
{ 
    // ... 
} 
 
[HttpPost(Name = "AddPizzaFlavour")] 
public ActionResult<int> Post([FromBody]AddPizzaFlavourRequest request) 
{ 
    // ... 
} 

On obtient l’affichage suivant dans Swagger:

Indiquer un paramètre obligatoire

On peut utiliser l’attribut BindRequiredAttribute sur le paramètre d’une action ou l’attribut RequiredAttribute sur les propriétés d’une DTO pour indiquer explicitement que le paramètre est obligatoire.

Par exemple en utilisant l’attribut BindRequiredAttribute pour les actions suivantes:

[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
public ActionResult<IEnumerable<string>> FindFlavour([FromQuery, BindRequired]string flavourName) 
{ 
    // ... 
} 
 
[HttpPost(Name = "AddPizzaFlavour")] 
public ActionResult<int> Post([FromBody, BindRequired]AddPizzaFlavourRequest request) 
{ 
    // ... 
} 

On obtient l’affichage:

Indiquer des metadonnées avec des attributs Swagger

Avec les méthodes précédentes, on a précisé les métadonnées d’une API en utilisant:

Il est possible de préciser ces informations en utilisant des attributes spécifiques à Swagger. Les informations seront reconnues et utilisées pour enrichir les métadonnées de l’API de la même façon qu’avec la méthode précédente.

L’assembly contenant les attributs spécifiques à Swagger se trouve dans la package NuGet Swashbuckle.AspNetCore.Annotations.

Pour installer ce package, il faut exécuter la commande suivante:

user@debian:~/% dotnet add <chemin du fichier .csproj> package swashbuckle.aspnetcore.annotations 

Dans le cas de l’exemple sur le repository GitHub https://github.com/msoft/webapi_example, il faut exécuter la commande:

user@debian:~/% dotnet add webapi_example/webapi_example.csproj package swashbuckle.aspnetcore.annotations

Pour utiliser le package installé, il faut l’activer dans le fichier StartUp.cs du projet en ajoutant les lignes:

public void ConfigureServices(IServiceCollection services) 
{ 
    // ...
    
    services.AddSwaggerGen(c => 
    { 
        // ... 
        c.EnableAnnotations(); 
    }); 
} 

Les attributs spécifiques à Swagger utilisés par la suite suivants, se trouvent dans le namespace Swashbuckle.AspNetCore.Annotations, il faut les utiliser en précisant dans l’entête du fichier .cs:

using Swashbuckle.AspNetCore.Annotations; 

SwaggerOperationAttribute

Cet attribut est l’équivalent des commentaires XML pour préciser des informations concernant une action.

Par exemple, en utilisant SwaggerOperationAttribute dans le code suivant:

[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
[SwaggerOperation( 
    Summary = "Returns the ingredients from a flavour name", 
    Description = "Returns the ingredients", 
    OperationId = "FindFlavour")]
public ActionResult<IEnumerable<string>> FindFlavour([FromQuery, BindRequired]string flavourName) 
{ 
    // ... 
} 

Le résultat est le même que dans le cas des commentaires XML:


L’élément Tag permet d’indiquer dans quelle partie sera rangée l’action.

Par exemple, si on précise les tags "Flavour" et "Pizza", l’action sera rangée dans les parties "Flavour" et "Pizza"

[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
[SwaggerOperation( 
    Summary = "Returns the ingredients from a flavour name", 
    Description = "Returns the ingredients", 
    OperationId = "FindFlavour", 
    Tags = new[] { "Flavour", "Pizza" }
)] 
public ActionResult<IEnumerable<string>> FindFlavour([FromQuery, BindRequired]string flavourName) 
{ 
    // ... 
} 

Le résultat sera:


Il est possible préciser d’autres éléments en utilisant SwaggerOperationAttribute comme:

  • Consumes pour préciser les types MIMES que l’action peut consumer.
  • Produces pour préciser les types MIMES que l’action peut générer.
  • Schemes pour indiquer les protocoles de transfert supportés par l’action.

SwaggerResponseAttribute

Cet attribut permet d’indiquer des informations sur les réponses possibles. Il est équivalent à l’attribut ProducesResponseTypeAttribute.

Par exemple en utilisant SwaggerResponseAttribute dans le code suivant:

[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
[SwaggerResponse(200, "The ingredients for the flavour have been found", typeof(IEnumerable<string>))] 
[SwaggerResponse(400, "The flavour has not been found", typeof(string))] 
[SwaggerResponse(500, "Internal server error")]
public ActionResult<IEnumerable<string>> FindFlavour([FromQuery, BindRequired]string flavourName) 
{ 
    // ... 
} 

Le résultat de cet exemple est du type:


SwaggerParameterAttribute

Cet attribut permet de fournir des informations sur les paramètres d’une action. Il est l’équivalent de l’attribut BindRequiredAttribute, toutefois il permet d’indiquer d’apporter une précision supplémentaire comme le nom du paramètre.

Par exemple si on utilise l’attribut SwaggerParameterAttribute dans le code suivant:

[HttpGet("{flavourName}", Name = "FindFlavourByName")] 
public ActionResult<IEnumerable<string>> FindFlavour( 
[FromQuery, SwaggerParameter("Flavour name", Required = true)]string flavourName) 
{ 
    // ... 
} 

Le résultat de cet exemple est du type:


SwaggerTagAttribute

Cet attribut permet de préciser des informations supplémentaires concernant le controller.

Par exemple, en utilisant l’attribut sur la classe du controller:

[Route("api/[controller]")] 
[ApiController] 
[SwaggerTag("Get or create new flavour for pizzas")]
public class PizzaFlavourController : ControllerBase 
{ 
    // ... 
} 

Le résultat de cet exemple est du type:


2 responses... add one

Merci pour votre commentaire et pour le partage de l’adresse de votre blog, très intéressant et en français…

Leave a Reply