Voir le flux RSS

Blog de Hinault Romaric (.NET Core, ASP.NET Core, Azure, DevOps)

[Actualité] Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor

Noter ce billet
par , 13/12/2017 à 02h18 (1203 Affichages)
Dans mon précédent billet, j’ai présenté comment intégrer le Framework Swagger dans une application ASP.NET Core Web API.


Pour rappel, Swagger offre des outils permettant de générer la documentation pour son API Web. Il offre également une interface permettant d’explorer et tester les différentes méthodes offertes par le service.

Le framework Swagger pour ASP.NET Core (Swashbuckle.AspNetCore) est une implémentation open source qui repose sur la spécification OpenAPI. Le endpoint JSON retourné par Swashbuckle dans une Web API respecte cette spécification.

Origines du projet OpenAPI

L’open source est un moteur de l’innovation qui a changé le destin de nombreux projets, grâce à la contribution d’une communauté importante. Parmi ceux-ci, figure le projet Swagger.

Swagger est un projet open source lancé par une Startup en 2010. L’objectif est de mettre en place un Framework qui va permettre aux développeurs de documenter et de designer des API, tout en maintenant une synchronisation avec le code.

Parallèlement au développement du Framework, une spécification Swagger a été mise en place pour définir le standard à respecter pour designer et documenter son API. Le projet a attiré l’attention de nombreux développeurs, et est devenu la technologie la plus populaire pour designer et décrire les API RESTful.

L’intérêt de l’industrie pour Swagger a poussé des géants de l’IT à se joindre au développement du projet. Google, IBM ou encore Microsoft ont rejoint SmartBear Software, l’entreprise responsable du développement de la spécification Swagger et les outils associés, pour faire évoluer ce dernier.

En janvier 2016 le projet Swagger Specification a été renommé OpenAPI Specification (OAS) et est passé sous la gouvernance de la fondation Linux. L’OPEN API Initiative a été créé par la fondation pour offrir un cadre de travail aux entreprises participantes. Le projet a été également migré vers un nouveau repository sur GitHub. Plusieurs autres géants de l’IT ont rejoint l’initiative, dont Oracle, Adobe, SAP, SalesForce, etc. À ce jour, plus de 30 entreprises participent au développement de la spécification OpenAPI.

Selon le GitHub du projet, la spécification OpenAPI définie un standard, une interface de description de langage de programmation agnostique pour les API REST, qui permet à la fois aux humains et aux machines de découvrir et comprendre les capacités offertes par un service sans avoir besoin d’accéder à son code, consulter une documentation supplémentaire, ou analyser le trafic réseau.

Lorsqu’une API a été correctement définie avec OpenAPI, les développeurs peuvent comprendre et interagir avec le service avec beaucoup moins d’effort d’implémentation.

Description d’un document OpenAPI

Lorsque vous utilisez la spécification OpenAPI pour designer votre API, vous pouvez entre autres : générer une documentation interactive, générer le code de la documentation, client et serveur. Cela est possible à travers de nombreux projets open source développés autour de la spécification OpenAPI.

Les langages supportés pour designer une API sont JSON et YAML. La spécification définit plusieurs étiquettes qui vont permettre entre autres de :
  • définir les informations générales sur vos API : description, termes d’utilisation, licence, contact, etc. ;
  • fournir la liste des services qui seront offerts, avec pour chacun, comment les appeler et la structure de la réponse qui est retournée ;
  • définir le chemin pour consommer votre API ;
  • etc.


Nom : img13.png
Affichages : 8250
Taille : 52,4 Ko

NB : vous pouvez consulter le site suivant pour explorer les différentes étiquettes de la spécification OpenAPI:
http://openapi-specification-visual-...pihandyman.io/

Une fois que vous maitrisez le langage JSON ou YAML et que vous comprenez la structure d’un document OpenAPI, avec un simple éditeur de texte, vous êtes en mesure de designer votre API.

Ci-dessous la structure d’un document YAML OpenAPI :

Code YAML : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
swagger: '2.0'
info:
  version: Version de votre API
  title: Nom
  description: Description de l'API
  termsOfService: Termes d'utilisation
  contact:
    name: Nom personne de contact
    url: Site Web 
    email: Adesse mail
  license:
    name: Nom de la licence
    url: Site Web ou tenir les informations sur la licence 
basePath: "/"
paths:
  "Chemin de la l'API":
    get:
      tags:
      - Nom du Tag
      summary: Resumé de la méthode qui est exposée
      description: Description de la méthode exposée
      operationId: Nom de la méthode exposée
      consumes: []
      produces:
      - application/json
      responses:
        '200':
          description: Description de la réponse
          schema:
            "$ref": "#/definitions/ExempleDefinition"
definitions:
  ExempleDefinition:
    type: sttring

Exemple de document pour une API Customers

Supposons que je souhaite mettre en place une API Customers. Cette API dispose d’une méthode Get permettant de retourner un Customer à partir de son ID. Ci-dessous la définition d’un Customer :

Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
public class Customer
    {
        public int Id { get; set; }
        public string FirstName { get; set; }
        public string LastName { get; set; }
        public string EMail { get; set; }
    }

Le document OpenAPI pour notre API au format JSON est le suivant

Code json : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
{
    "swagger": "2.0",
    "info": {
        "version": "v2",
        "title": "SwaggerDemo API",
        "description": "Customers API to demo Swagger",
        "termsOfService": "None",
        "contact": {
            "name": "Hinault Romaric",
            "url": "http://rdonfack.developpez.com/",
            "email": "hinault@monsite.com"
        },
        "license": {
            "name": "Apache 2.0",
            "url": "http://www.apache.org"
        }
    },
    "basePath": "/",
    "paths": {
        "/api/Customers/{id}": {
            "get": {
                "tags": [
                    "Customers"
                ],
                "summary": "Retourne un client spécifique à partir de son id",
                "description": "Je manque d'imagination",
                "operationId": "ApiCustomersByIdGet",
                "consumes": [],
                "produces": [
                    "application/json"
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "description": "id du client à retourner",
                        "required": true,
                        "type": "integer",
                        "format": "int32"
                    }
                ],
                "responses": {
                    "200": {
                        "description": "client sélectionné",
                        "schema": {
                            "$ref": "#/definitions/Customer"
                        }
                    }
                }
            }
        }
    },
    "definitions": {
        "Customer": {
            "type": "object",
            "properties": {
                "id": {
                    "format": "int32",
                    "type": "integer"
                },
                "firstName": {
                    "type": "string"
                },
                "lastName": {
                    "type": "string"
                },
                "eMail": {
                    "type": "string"
                }
            }
        }
    }
}

Et le document YAML est le suivant :

Code YAML : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
swagger: '2.0'
info:
  version: v2
  title: SwaggerDemo API
  description: Customers API to demo Swagger
  termsOfService: None
  contact:
    name: Hinault Romaric
    url: http://rdonfack.developpez.com/
    email: hinault@monsite.com
  license:
    name: Apache 2.0
    url: http://www.apache.org
basePath: "/"
paths:
  "/api/Customers/{id}":
    get:
      tags:
      - Customers
      summary: Retourne un client spécifique à partir de son id
      description: Je manque d'imagination
      operationId: ApiCustomersByIdGet
      consumes: []
      produces:
      - application/json
      parameters:
      - name: id
        in: path
        description: id du client à retourner
        required: true
        type: integer
        format: int32
      responses:
        '200':
          description: client sélectionné
          schema:
            "$ref": "#/definitions/Customer"
definitions:
  Customer:
    type: object
    properties:
      id:
        format: int32
        type: integer
      firstName:
        type: string
      lastName:
        type: string
      eMail:
        type: string

Comme vous pouvez le constater, le format YAML est beaucoup plus facilement éditable pour un humain.

Éditeur

Écrire un document OpenAPI en utilisant un simple éditeur peut être assez fastidieux. Toutefois, il existe un éditeur Swagger en ligne gratuit. Ce dernier offre de nombreuses fonctionnalités intéressantes, dont l’autocompletion.

Nom : gifimage.gif
Affichages : 8294
Taille : 156,7 Ko

Swagger Editor dispose d’un éditeur à gauche permettant de designer votre API et d’une fenêtre d’aperçu à droite. Cette fenêtre affiche une documentation interactive sur l’API en cours de conception.

Nom : img14.png
Affichages : 8200
Taille : 66,2 Ko

Génération du code de l’API à partir du document OpenAPI

Comme je l’ai souligné plus haut, une fois votre API désignée avec OpenAPI, vous pouvez directement générer la documentation interactive, le code client et serveur. Swagger Editor offre des fonctionnalités de génération du code client et serveur pour un large panel de plateformes et langages de programmation :

Nom : img15.png
Affichages : 8160
Taille : 15,0 Ko

En utilisant la fonctionnalité de génération de code serveur pour ASP.NET Core, une application ASP.NET Core Web API est générée avec une solution Visual Studio :

Nom : img16.png
Affichages : 8120
Taille : 10,0 Ko

Comme vous pouvez le constater, à partir du document OpenAPI de mon API, le code de l’entité Customer a été automatiquement généré avec les propriétés nécessaires :

Code C# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
   [DataContract]
    public partial class Customer :  IEquatable<Customer>
    {
 
        /// <summary>
        /// Initializes a new instance of the <see cref="Customer" /> class.
        /// </summary>
        /// <param name="Id">Id.</param>
        /// <param name="FirstName">FirstName.</param>
        /// <param name="LastName">LastName.</param>
        /// <param name="EMail">EMail.</param>
        public Customer(int? Id = default(int?), string FirstName = default(string), string LastName = default(string), string EMail = default(string))
        {
            this.Id = Id;
            this.FirstName = FirstName;
            this.LastName = LastName;
            this.EMail = EMail;
 
        }
 
        /// <summary>
        /// Gets or Sets Id
        /// </summary>
        [DataMember(Name="id")]
        public int? Id { get; set; }
        /// <summary>
        /// Gets or Sets FirstName
        /// </summary>
        [DataMember(Name="firstName")]
        public string FirstName { get; set; }
        /// <summary>
        /// Gets or Sets LastName
        /// </summary>
        [DataMember(Name="lastName")]
        public string LastName { get; set; }
        /// <summary>
        /// Gets or Sets EMail
        /// </summary>
        [DataMember(Name="eMail")]
        public string EMail { get; set; }
 
        /// <summary>
        /// Returns the string presentation of the object
        /// </summary>
        /// <returns>String presentation of the object</returns>
        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append("class Customer {\n");
            sb.Append("  Id: ").Append(Id).Append("\n");
            sb.Append("  FirstName: ").Append(FirstName).Append("\n");
            sb.Append("  LastName: ").Append(LastName).Append("\n");
            sb.Append("  EMail: ").Append(EMail).Append("\n");
            sb.Append("}\n");
            return sb.ToString();
        }
 
        /// <summary>
        /// Returns the JSON string presentation of the object
        /// </summary>
        /// <returns>JSON string presentation of the object</returns>
        public string ToJson()
        {
            return JsonConvert.SerializeObject(this, Formatting.Indented);
        }
 
        /// <summary>
        /// Returns true if objects are equal
        /// </summary>
        /// <param name="obj">Object to be compared</param>
        /// <returns>Boolean</returns>
        public override bool Equals(object obj)
        {
            if (ReferenceEquals(null, obj)) return false;
            if (ReferenceEquals(this, obj)) return true;
            if (obj.GetType() != GetType()) return false;
            return Equals((Customer)obj);
        }
 
        /// <summary>
        /// Returns true if Customer instances are equal
        /// </summary>
        /// <param name="other">Instance of Customer to be compared</param>
        /// <returns>Boolean</returns>
        public bool Equals(Customer other)
        {
 
            if (ReferenceEquals(null, other)) return false;
            if (ReferenceEquals(this, other)) return true;
 
            return 
                (
                    this.Id == other.Id ||
                    this.Id != null &&
                    this.Id.Equals(other.Id)
                ) && 
                (
                    this.FirstName == other.FirstName ||
                    this.FirstName != null &&
                    this.FirstName.Equals(other.FirstName)
                ) && 
                (
                    this.LastName == other.LastName ||
                    this.LastName != null &&
                    this.LastName.Equals(other.LastName)
                ) && 
                (
                    this.EMail == other.EMail ||
                    this.EMail != null &&
                    this.EMail.Equals(other.EMail)
                );
        }
 
        /// <summary>
        /// Gets the hash code
        /// </summary>
        /// <returns>Hash code</returns>
        public override int GetHashCode()
        {
            // credit: http://stackoverflow.com/a/263416/677735
            unchecked // Overflow is fine, just wrap
            {
                int hash = 41;
                // Suitable nullity checks etc, of course :)
                    if (this.Id != null)
                    hash = hash * 59 + this.Id.GetHashCode();
                    if (this.FirstName != null)
                    hash = hash * 59 + this.FirstName.GetHashCode();
                    if (this.LastName != null)
                    hash = hash * 59 + this.LastName.GetHashCode();
                    if (this.EMail != null)
                    hash = hash * 59 + this.EMail.GetHashCode();
                return hash;
            }
        }
 
        #region Operators
 
        public static bool operator ==(Customer left, Customer right)
        {
            return Equals(left, right);
        }
 
        public static bool operator !=(Customer left, Customer right)
        {
            return !Equals(left, right);
        }
 
        #endregion Operators
 
    }

Le code du contrôleur a été généré avec la méthode d’action Get, qui prend en paramètre l’Id. Je n’ai plus qu’à modifier cette méthode d’action pour retourner le client à partir de ma banque de données.

Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
namespace IO.Swagger.Controllers
{ 
    /// <summary>
    /// 
    /// </summary>
    public class CustomersApiController : Controller
    { 
 
        /// <summary>
        /// Retourne un client specifique à partir de son id
        /// </summary>
        /// <remarks>Je manque d'imagination</remarks>
        /// <param name="id">id du client a retourné</param>
        /// <response code="200">client selectionné</response>
        [HttpGet]
        [Route("//api/Customers/{id}")]
        [SwaggerOperation("ApiCustomersByIdGet")]
        [SwaggerResponse(200, type: typeof(Customer))]
        public virtual IActionResult ApiCustomersByIdGet([FromRoute]int? id)
        { 
            string exampleJson = null;
 
            var example = exampleJson != null
            ? JsonConvert.DeserializeObject<Customer>(exampleJson)
            : default(Customer);
            return new ObjectResult(example);
        }
    }
}

Par ailleurs, la classe Startup a été configurée pour utiliser le générateur Swagger et SwaggerUI. Ce qui permet d’offrir un point d’accès à votre documentation à partir de votre application. Et si vous modifiez l’application, le générateur Swagger va générer le document OpenAPI correspondant :

Code c# : Sélectionner tout - Visualiser dans une fenêtre à part
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
// This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
            // Add framework services.
            services.AddMvc()
                .AddJsonOptions(
                    opts => { opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver(); });
 
            services.AddSwaggerGen();
 
            services.ConfigureSwaggerGen(options =>
            {
                options.SingleApiVersion(new Info
                {
                    Version = "v1",
                    Title = "IO.Swagger",
                    Description = "IO.Swagger (ASP.NET Core 1.0)"
                });
 
                options.DescribeAllEnumsAsStrings();
 
                var comments = new XPathDocument($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{_hostingEnv.ApplicationName}.xml");
                options.OperationFilter<XmlCommentsOperationFilter>(comments);
                options.ModelFilter<XmlCommentsModelFilter>(comments);
            });
 
        }
 
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory)
        {
            loggerFactory.AddConsole(Configuration.GetSection("Logging"));
            loggerFactory.AddDebug();
 
            app.UseMvc();
 
            app.UseDefaultFiles();
            app.UseStaticFiles();
 
            app.UseSwagger();
            app.UseSwaggerUi();
        }

Pour l’instant, la génération de code utilise ASP.NET Core 1.0.x.

Lorsque vous voulez créer des API REST, il est assez simple d’utiliser OpenAPI pour designer votre API, ensuite générer le code correspondant. Ce qui offrira un canevas aux développeurs pour les différents services qui seront offerts par l’API. Ces derniers n’auront plus qu’à implémenter la logique métier des services.

Si vous avez déjà créé votre Web API, comme je l’ai présenté dans le tutoriel précédent, vous pouvez simplement configurer le framework Swagger pour générer la documentation à partir du code.

Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog Viadeo Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog Twitter Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog Google Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog Facebook Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog Digg Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog Delicious Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog MySpace Envoyer le billet « Comprendre la spécification OpenAPI (Swagger) et utiliser Swagger Editor » dans le blog Yahoo

Mis à jour 14/12/2017 à 14h28 par Hinault Romaric

Catégories
DotNET , C# , ASP.NET , .NET Core , ASP.NET Core