I. Résumé▲
4e Dimension version 2003 et ultérieures propose en natif la publication de méthodes en tant que Services Web et permet à toute application, tout outil ou tout environnement de développement compatible Service Web d’appeler les méthodes 4e Dimension en envoyant des messages SOAP (Simple Object Access Protocol) au serveur Web natif de 4e Dimension.
Les messages SOAP suivent des règles strictes de formatage XML et d’encodage des données. Ces règles sont définies par une série de standards interdépendants.
Par défaut, le système de publication de Services Web de 4e Dimension gère toute la complexité de l’analyse, de la navigation et de la conversion de valeurs entre XML et les types natifs de 4e Dimension.
Des circonstances peuvent se présenter cependant où des développeurs souhaitent lire des messages SOAP directement.
Par exemple :
- pour déboguer des échanges SOAP ;
- pour journaliser des requêtes SOAP ;
- pour lire des entrées SOAP non liées automatiquement ;
- distinguer entre les paramètres entrants passés avec une valeur vide et ceux qui ne sont pas présents dans la requête ;
- analyser les requêtes directement grâce à la commande native Analyser variable XML ou en recourant à un parseur externe ;
- en apprendre plus sur la structure et le contenu des requêtes SOAP ;
- acquérir une meilleure compréhension du fonctionnement du serveur SOAP de 4e Dimension.
- heureusement, il est possible de lire le texte brut des messages SOAP entrants en utilisant le comportement non documenté de la commande LIRE VARIABLES FORMULAIRE WEB. Au besoin, les entêtes HTTP entrants sont accessibles grâce à la commande LIRE ENTETE HTTP.
II. Échantillon de requête SOAP▲
Les Services Web, en dépit de leur nom, n’ont théoriquement pas besoin de fonctionner au travers du Web. Cependant, en pratique, quasiment toutes les requêtes SOAP sont échangées au travers de HTTP, le protocole du Web 4e Dimension, comme la plupart des environnements, ne prend en charge SOAP qu’au travers de HTTP).
La distinction entre SOAP et les messages HTTP est signifiante, car les messages HTTP peuvent contenir n’importe quel type d’information, tandis qu’un message SOAP doit toujours être un document XML bien formé et se conformer aux règles des messages SOAP. Reformulé plus simplement, les messages HTTP peuvent contenir du XML alors que les messages SOAP sont du XML. Ci-dessous une requête HTTP commentée incluant un message SOAP (des espaces ont été ajoutés pour la lisibilité) :
Notes |
Exemples |
---|---|
Entête HTTP |
POST /4DSOAP/ HTTP/1.1 |
Corps HTTP/message SOAP |
Sélectionnez
|
La première partie du message ci-dessus est du pur HTTP et ne se conforme pas aux règles XML ou SOAP.
La section entête HTTP est classique et se présenterait de la même façon si les données transportées provenaient d’un formulaire, d’un document téléchargé, ou d’une requête vers une URL.
La seconde partie du message représente le corps HTTP.
Le corps d’une requête HTTP peut être vide, comprendre un fichier téléchargé, des données de champs d’un formulaire, ou toute autre chose acceptée par un serveur Web.
Dans cet exemple, le corps HTTP est un message SOAP complet, respectant toutes les règles de XML et SOAP.
Cette note technique et la base de démo qui l’accompagne montre comment reconstruire les requêtes SOAP complètes reçues par un serveur Web 4e Dimension afin d’en permettre une inspection directe.
III. Rappel : la gestion automatique des messages SOAP▲
Lorsque 4e Dimension reçoit une requête SOAP, il lit la section SOAP-ENV:Body du message afin de retrouver le nom de la méthode et ses paramètres d’entrée, soulignés dans l’extrait ci-dessous :
2.
3.
<
mns
:
ConvertFeetToMeters
xmlns
:
mns
=
"http://www.4d.com/namespace/default"
>
<inFeet
xsi
:
type
=
"xsd:float"
>
100.1</inFeet>
</
mns
:
ConvertFeetToMeters>
4e Dimension extrait et convertit automatiquement les paramètres d’entrée puis appelle la méthode spécifiée, ConvertFeetToMeters dans notre exemple. Les paramètres d’entrée peuvent être recopiés dans des paramètres de méthode et/ou n’importe quelle variable ou tableau process lié par la commande DECLARATION SOAP, comme décrit dans le manuel Référence du Langage 4e Dimension. Ci-dessous le code de la méthode ConvertFeetToMeters :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
` ---------------------------------------
C_REEL
(
$0
;
$meters_r
)
C_REEL
(
$1
;
$feet_r
)
DECLARATION SOAP(
$0
;
Est un numérique
;
SOAP sortie
;
"outMeters"
)
DECLARATION SOAP(
$1
;
Est un numérique
;
SOAP entrée
;
"inFeet"
)
C_ENTIER LONG
(
$rounding_l
)
$rounding_l
:=
soap_GetRounding
$feet_r
:=
$1
$meters_r
:=
$feet_r
*
0
,3048
$meters_r
:=
Arrondi
(
$meters_r
;
$rounding_l
)
$0
:=
$meters_r
` ---------------------------------------
Appelé par le message SOAP listé plus haut, lorsque la $1 est automatiquement traduite en un réel valant 100,1.
IV. Lire la requête brute▲
Reconstruire le message SOAP Le système de traduction automatique des messages SOAP par 4e Dimension est facile à utiliser et pratique, mais très peu du message SOAP brut est apparent. Heureusement, appelée dans le contexte d’un process SOAP, LIRE VARIABLES FORMULAIRE WEB contient le texte complet de la requête originale à l’exception d’un seul signe égal. LIRE VARIABLES FORMULAIRE WEB découpe normalement les données de formulaire Web en paires nom-valeur.
Les noms des champs et leurs valeurs sont séparés par des signes « égal ». De la même façon, LIRE VARIABLES FORMULAIRE WEB découpe la requête SOAP entrante en deux parties, divisant la requête au premier signe « égal ».
Le code ci-dessous réassemble la requête SOAP initiale :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
` ---------------------------------------
`soap_GetFullRequest() : Requête SOAP complète :
C_TEXTE
(
$0
;
$result_t
)
$0
:=
""
$result_t
:=
""
Si
(
Est une requete SOAP)
`Ce code n'a de sens que dans un process démarré par une requête Web Service
TABLEAU TEXTE
(
$names_at
;
0
)
TABLEAU TEXTE
(
$values_at
;
0
)
LIRE VARIABLES
FORMULAIRE WEB(
$names_at
;
$values_at
)
$result_t
:=
""
$result_t
:=
$result_t
+
$names_at
{1
}
$result_t
:=
$result_t
+
"="
`Un signe = est retiré lorsque les items sont découpés
$result_t
:=
$result_t
+
$values_at
{1
}
Fin de si
$0
:=
$result_t
` ---------------------------------------
En reprenant le message listé plus haut, voici les contenus de $names_at{1} et $values_at{1} :
$names_at{1} |
Sélectionnez
|
$values_at{1} |
Sélectionnez 1. 2. 3. 4. 5. 6. 7. 8. 9. 10. 11.
|
Une fois rassemblé, le message SOAP original constitue un document XML bien formé et peut être parcouru par les commandes XML natives ou celles d’un plugin.
Notes et avertissements :
· La méthode soap_GetFullRequest peut être appelée de n’importe quelle méthode à l’intérieur d’un process créé par une requête à un service Web. Les résultats sont les mêmes, que le code soit appelé depuis la méthode base Sur authentification Web, Compiler_Web, la méthode projet spécifiée dans le message SOAP ou n’importe quelle autre méthode projet.
· Le comportement de LIRE VARIABLES FORMULAIRE WEB exploité par soap_GetFullRequest est non documenté et non supporté, mais ne devrait pas changer.
· La méthode soap_GetFullRequest repose sur des paramètres et des éléments de tableaux de type texte 4D, avec la limitation correspondante à 32 000 caractères. Les paramètres entrants SOAP ne sont pas limités à 32 000 caractères, ils peuvent comporter plusieurs éléments texte, des BLObs, et d’autres données relativement volumineuses. Dans le cadre d’un système acceptant des données importantes en entrée, le code ci-dessus peut ne pas fonctionner.
Lire les entêtes HTTP▲
En cas de besoin, la section HTTP originale de la requête peut être reconstruite avec le code suivant :
2.
3.
4.
5.
` ---------------------------------------
C_TEXTE
(
$headers_t
)
$headers_t
:=
""
LIRE ENTETE HTTP(
$headers_t
)
` ---------------------------------------
V. Application : journaliser, étudier et déboguer les requêtes▲
Résumé▲
Journaliser, étudier et déboguer les messages SOAP constituent d’excellentes applications des techniques présentées dans cette note. Ci-dessous, nous décrivons quelques situations spécifiques où l’accès direct au message SOAP entrant peut se révéler utile, voire indispensable.
· Il est plus facile de résoudre des problèmes relatifs à des requêtes de Services Web en regardant le message SOAP complet. De cette façon, il est possible de déterminer si le problème provient d’appels à DECLARATION SOAP inadéquats ou manquants, de requêtes malformées d’un service Web distant, ou d’un comportement inattendu du système de traduction automatique de 4e Dimension. La capture d’entrées SOAP dans 4e Dimension est une technique de débogage utile, mais ne remplace pas toujours la capture de paquets réseau avant qu’ils n’atteignent 4e Dimension. Pour plus d’information, nous vous renvoyons à l'ouvrage « 4D Web Companion » ou à la Note Technique 4D-200308-22-FR « Détecter et suivre un problème TCP/IP ».
· Le journal du serveur Web de 4e Dimension comprend la même entrée pour toutes les requêtes vers le Service Web, comme dans l’extrait ci-dessous :
209.238.253.9 - - [23/Dec/2003:17:04:31 +1100] "POST /4DSOAP/ HTTP/1.1" 200 632
Si un système nécessite de l’information plus détaillée, du code dédié peut extraire et rassembler de l’information depuis les entêtes HTTP ou le message SOAP, par exemple le nom de la méthode appelée.
Nous allons maintenant nous intéresser à un exemple de code créant une journalisation sur mesure des requêtes SOAP.
Code exemple▲
Dans cet exemple, le système journalise plusieurs informations pour chaque requête SOAP : l’heure, la date, l’adresse IP du client, le nom de la méthode 4e Dimension et le logiciel utilisé comme user-agent (navigateur/client). La méthode base Sur authentification Web s’exécute une fois pour chaque requête au service Web, elle constitue donc l’emplacement logique pour journaliser les requêtes SOAP.
Le code ci-dessous appelle la méthode soap_LogRequest pour exécuter la tâche d’extraction et d’enregistrement des informations de la requête :
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.
` ---------------------------------------
`Méthode Base : Sur Authentification Web
C_BOOLEEN
(
$0
;
$allowRequest_b
)
C_TEXTE
(
$1
)
` URL
C_TEXTE
(
$2
)
` requête HTTP (entête + corps)
C_TEXTE
(
$3
;
$clientAddress_t
)
` adresse IP Client
C_TEXTE
(
$4
;
$serverAddress_t
)
` adresse IP Serveur
C_TEXTE
(
$5
;
$userName_t
)
` nom Utilisateur
C_TEXTE
(
$6
)
` Mot de passe
$clientAddress_t
:=
$3
$serverAddress_t
:=
$4
$userName_t
:=
$5
$allowRequest_b
:=
Vrai
`la routine ci-dessous montre comment utiliser une requête complète SOAP entrante
Si
(
Est une requete SOAP)
` Enregistrer la requête complète et les entêtes dans un enregistrement
soap_SaveRequest (
"Sur authentification Web"
)
` Extrait et enregistre les valeurs spécifiques dans un enregistrement
soap_LogRequest (
$clientAddress_t
;
$serverAddress_t
;
$userName_t
)
Fin de si
$0
:=
Vrai
` ---------------------------------------
La méthode soap_LogRequest, reproduite ci-dessous, enregistre les informations générales de la requête, aussi bien que l’entête HTTP User-Agent et le nom de la méthode appelée comme service Web.
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.
` ---------------------------------------
` soap_LogRequest
C_TEXTE
(
$1
;
$clientAddress_t
)
C_TEXTE
(
$2
;
$serverAddress_t
)
C_TEXTE
(
$3
;
$userName_t
)
$clientAddress_t
:=
$1
$serverAddress_t
:=
$2
$userName_t
:=
$3
Si
(
Est une requete SOAP)
` ---------------------------------------
` Installe la gestion d'erreurs
` ---------------------------------------
C_ENTIER LONG
(
Error)
Error:=
0
APPELER SUR ERREUR
(
"demo_TraceErrors"
)
` Ouvre le débogueur en cas d'erreurs
` ---------------------------------------
`Récupère les entêtes HTTP et extrait les valeurs souhaitées
` ---------------------------------------
TABLEAU TEXTE
(
$httpNames_at
;
0
)
TABLEAU TEXTE
(
$httpValues_at
;
0
)
LIRE ENTETE HTTP(
$httpNames_at
;
$httpValues_at
)
C_ENTIER LONG
(
$index
)
$index
:=
Chercher dans tableau
(
$httpNames_at
;
"User-Agent"
)
` User-Agent signifie "navigateur" ou
` "application cliente"
C_TEXTE
(
$userAgent_t
)
Si
(
$index
>
0
)
$userAgent_t
:=
$httpValues_at
{$index
}
Sinon
$userAgent_t
:=
""
Fin de si
` ---------------------------------------
`Récupère la requête complète SOAP et extrait les valeurs désirées
` ---------------------------------------
C_TEXTE
(
$soapRequest_t
)
$soapRequest_t
:=
soap_GetFullRequest
`S'il s'agit d'une requête SOAP, les éléments sont organisés de la manière suivante :
` <SOAP-ENV:Envelope
` <SOAP-ENV:Body
` <nom de la méthode
`Le code ci-dessous navigue à travers cette structure et extrait le nom de la méthode
C_ALPHA(
16
;
$xmlroot
;
$xmlref
)
$xmlroot
:=
Analyser variable XML(
$soapRequest_t
)
` SOAP-ENV:Envelope
$xmlref
:=
$xmlroot
` Préserve la référence originale à l'arbre XML pour la repasser à FERMER XML
$xmlref
:=
Lire premier element XML(
$xmlref
)
` SOAP-ENV:Body
$xmlref
:=
Lire premier element XML(
$xmlref
)
` Nom de la méthode
C_TEXTE
(
$methodName_t
)
$methodName_t
:=
""
LIRE NOM ELEMENT XML(
$xmlref
;
$methodName_t
)
FERMER XML(
$xmlroot
)
`Le nom de la méthode peut être préfixé par un alias de namespace (espace de noms)
`, comme ceci :
` mns:ConvertFeetToMeters
`La seule utilisation possible des ":" dans les noms XML s'applique aux préfixes de namespaces
`Le code suivant cherche ":" et s'il les retrouve, retire le préfixe du namespace. `
C_ENTIER LONG
(
$index
)
$index
:=
Position
(
":"
;
$methodName_t
)
Si
(
$index
>
0
)
$methodName_t
:=
Sous chaine
(
$methodName_t
;
$index
+
1
)
Fin de si
` ---------------------------------------
` Enregistrement des données
` ---------------------------------------
`Cette démo journalise dans un enregistrement, vous pouvez préférer utiliser un
`autre emplacement, comme un document
CREER ENREGISTREMENT
([
Log
ged_SOAP_Request])
[
Log
ged_SOAP_Request]
ID:=
Numerotation automatique
([
Log
ged_SOAP_Request])
[
Log
ged_SOAP_Request]
Request_Date:=
Date du jour
(*)
[
Log
ged_SOAP_Request]
Request_Time:=
Heure courante
(*)
[
Log
ged_SOAP_Request]
Client_IP:=
$clientAddress_t
[
Log
ged_SOAP_Request]
Server_IP:=
$serverAddress_t
[
Log
ged_SOAP_Request]
User_Name:=
$userName_t
[
Log
ged_SOAP_Request]
Method_Name:=
$methodName_t
[
Log
ged_SOAP_Request]
User_Agent:=
$userAgent_t
STOCKER ENREGISTREMENT
([
Log
ged_SOAP_Request])
` ---------------------------------------
` Désactivation de la gestion d'erreurs
` ---------------------------------------
APPELER SUR ERREUR
(
""
)
Fin de si
` (Est une requete SOAP)
` ---------------------------------------
Suggestions▲
Si vous êtes intéressés par la construction d’un système de journalisation des requêtes SOAP dans un environnement de production, voici quelques suggestions d’implémentations :
- conserver le code s’exécutant en réponse au service Web, ainsi qu’aux requêtes Web aussi rapide que possible.
- le code proposé ci-dessus peut être remodelé et amélioré pour une meilleure flexibilité.
- cet exemple enregistre les données du journal dans des enregistrements principalement parce qu’il est plus facile de le coder de cette manière ! En environnement de production, l’écriture dans des documents disques est à considérer sérieusement.
- dans un système reposant sur 4D Server, réfléchissez à enregistrer les requêtes entrantes dans des enregistrements au lieu d’extraire les données dans le process gérant la requête au Service Web. Un process spécifique ou un client dédié peut être programmé pour examiner périodiquement les enregistrements sauvés et extraire l’information désirée afin de l’inclure dans les enregistrements d’un fichier de journalisation. C’est une stratégie particulièrement intéressante lorsque le serveur Web s’exécute sur un ou plusieurs 4D Clients et qu’en conséquence le fichier de journalisation doit être consolidé sur une machine.
VI. Exemple : relire des entrées non déclarées▲
Description▲
Une autre application de la technique décrite dans cette note technique est de lire des valeurs d’un message SOAP qui ne sont pas déclarées par DECLARATION SOAP. Par exemple, la méthode ConvertFeetToMeters reproduite plus haut pourrait accepter un argument optionnel spécifiant la précision de l’arrondi. Le fragment XML ci-dessous montre à quoi ressemble la requête XML avec un nouveau champ nommé inRoundTo (en gras pour la mise en évidence) :
2.
3.
4.
<
mns
:
ConvertFeetToMeters
xmlns
:
mns
=
"http://www.4d.com/namespace/default"
>
<inFeet
xsi
:
type
=
"xsd:float"
>
100</inFeet>
<inRoundTo
xsi
:
type
=
"xsd:float"
>
3</inRoundTo>
</
mns
:
ConvertFeetToMeters>
La gestion manuelle par rapport à la gestion automatique▲
En utilisant le système de conversion SOAP automatique de 4e Dimension, la méthode ConvertFeetToMeters n’a pas accès à l’entrée inRoundTo. Si ConvertFeetToMeters requiert toujours un argument d’arrondi, l’approche la plus simple est de déclarer l’entrée en tant que paramètre de la méthode ou variable process et de la définir comme entrée SOAP à l’aide de DECLARATION SOAP, comme dans le fragment de code ci-dessous :
2.
3.
4.
5.
` ---------------------------------------
C_ENTIER LONG
(
$2
)
…
DECLARATION SOAP (
$2
;
Est un entier long
;
"inroundTo"
)
` ---------------------------------------
Note : Les déclarations Compiler et les déclarations SOAP répondent à des objectifs différents et sont toutes les deux nécessaires. Les valeurs utilisées comme entrées ou sorties SOAP devraient être déclarées pour le compilateur et par la commande DECLARATION SOAP.
Étant donné la facilité de déclaration des entrées SOAP, pourquoi les développeurs se trouveraient-ils dans l’obligation de traiter les entrées – ou certaines entrées – SOAP manuellement ?
Voici quelques raisons :
- en utilisant le système de conversion automatique de 4e Dimension, il n’existe aucun moyen de distinguer entre les messages qui n’incluent pas l’entrée et ceux qui incluent une valeur par défaut pour le type. Par exemple, en utilisant le système automatique, le paramètre $2 ci-dessus vaut 0 si inRoundTo est soumis avec une valeur de 0 ou si inRoundTo n’est pas passé du tout ;
- le système automatique de 4e Dimension demande d’associer les entrées avec des paramètres de méthodes, des variables process ou des tableaux process. En outre, toute variable process ou tableau lié à une entrée SOAP doit être déclaré dans la méthode Compiler_Web, ou dans une sous-routine appelée par Compiler_Web. La lecture manuelle des messages SOAP permet aux développeurs d’extraire, convertir, et stocker les entrées librement dans des champs, documents locaux, variables et tableaux process ou interprocess. De plus, si le système de traduction automatique n’est pas utilisé, les valeurs n’ont pas besoin d’être déclarées dans Compiler_Web ;
- dans la vie d’un service Web, les listes de paramètres et les déclarations peuvent changer. La lecture directe du message révèle exactement quels paramètres ont été envoyés. Il devient alors plus facile de supporter des listes de paramètres multiples pour une même méthode, ou de renvoyer une faute SOAP intelligemment lorsque le client SOAP doit mettre à jour son code.
Code échantillon▲
Le code ci-dessous améliore la méthode ConvertFeetToMeters proposée plus haut en appelant une sous-routine nommée soap_GetRounding qui retourne une valeur d’arrondi depuis le message SOAP ou une valeur d’arrondi par défaut :
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
` ---------------------------------------
C_REEL
(
$0
;
$meters_r
)
C_REEL
(
$1
;
$feet_r
)
DECLARATION SOAP(
$0
;
Est un numérique
;
SOAP sortie
;
"outMeters"
)
DECLARATION SOAP(
$1
;
Est un numérique
;
SOAP entrée
;
"inFeet"
)
C_ENTIER LONG
(
$rounding_l
)
$rounding_l
:=
soap_GetRounding
$feet_r
:=
$1
$meters_r
:=
$feet_r
*
0
,3048
$meters_r
:=
Arrondi
(
$meters_r
;
$rounding_l
)
$0
:=
$meters_r
` ---------------------------------------
La seule différence entre la version originale de la méthode ConvertFeetToMeters et la version modifiée consiste dans les trois lignes de code reprises ci-dessous :
2.
3.
4.
5.
` ---------------------------------------
C_ENTIER LONG
(
$rounding_l
)
$rounding_l
:=
soap_GetRounding
$meters_r
:=
Arrondi
(
$meters_r
;
$rounding_l
)
` ---------------------------------------
Voici le code de la méthode soap_GetRounding :
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.
` ---------------------------------------
C_ENTIER LONG
(
$0
;
$rounding_l
)
$rounding_l
:=
0
C_TEXTE
(
$xml_t
)
$xml_t
:=
soap_GetFullRequest
C_ALPHA(
16
;
$xmlref
)
$xmlref
:=
Analyser variable XML(
$xml_t
)
` Analyse de l'XML contenu dans une variable texte ou BLOB
C_TEXTE
(
$rounding_t
)
$rounding_t
:=
""
` Extrait la valeur de l'élément 'inRoundTo' vers une chaine, si cet élément est trouvé
$rounding_t
:=
xutil_GetValue (
$xmlref
;
"inRoundTo"
)
`Convertit la valeur en nombre (les chaînes vides sont converties en 0)
$rounding_l
:=
Num
(
$rounding_t
)
Si
(
$rounding_l
<=
0
)
`Modif CK
$rounding_l
:=
2
` une valeur négative n'a pas de sens, retour de la valeur par défaut 2
Fin de si
FERMER XML(
$xmlref
)
`Toujours faire correspondre un appel à Analyser variable XML
` par un appel à FERMER XML
$0
:=
$rounding_l
` ---------------------------------------
La méthode xutil_GetValue retourne la valeur en texte de n’importe quel élément simple XML, s’il existe. Les commandes de navigation XML natives de 4e Dimension ou celles des nombreuses solutions tierces peuvent également s’utiliser.
VII. Base exemple▲
Téléchargez la base exemple :
Base pour Windows
Base pour Mac