La boîte à Tutoriels de Christopher PECAUD

Top

Consommation du webservice partner.wsdl sous Delphi

Laisser un commentaire

SOMMAIRE

I Introduction
II Création de l'application
III Génération du fichier partner.pas et problèmes rencontrés
IV Codage de l'applcation
V Conclusion

I Introduction

Dans ce tutoriel je vais vous montrer une méthode permettant de consommer le webservice partner.wsdl sous Delphi dans de bonnes conditions. En effet beaucoup d'entre nous sont frustrés par le fait que ce webservice est inutilisable lorsque l'on s'appuie exclusivement sur le fichier généré par l'importateur interne de Delphi. Celui-ci ayant du mal à interpréter quelques balises du fichiers xml, et surtout le plus important, celles qui permmettent de traiter les données retournées par le serveur Salesforce après une requête de type query()... La solution que je vous présente dans ce tutoriel s'appuie sur ce fichier qui est somme toute utilisable pour les opérations de base comme l'accès au serveur, mais on va s'appuyer dans un second temps sur le composant de type THTTPRIO et l'événement associé AfterExecute qui permet de récupérerla réponse SOAP du serveur. Donc celà nécessite de connaître l'utilsation de XPATH et de MSXML. Ce tutoriel a été réalisé sous Delphi 2005, et donc devrait fonctionner sur les versions ultérieures...

II Création de l'application

Créez tout d'abord un nouveau projet de type fiches et nommer le comme bon vous semble. Nous allons détailler le design de l'application Voici le résultat final de l'application...

 image montrant l'interface graphique de l'application que l'on va développer

Voici les composants dont vous aller avoir besoin :

  • Trois composants TEdit associés à leurs labels respectifs dans un composant de type TGroupBox pour les informations de session;
  • Ensuite vous aurez besoin d'un composant de type TMemo qui nous permettrant de visualiser les requêtes que nous enverrons au serveur.
  • posez ensuite un deuxième composant de type TMemo nous permettant de récupérer la réponse XML du serveur distant;
  • Un composant TStringGrid nous permettra d'afficher les données récupérées;
  • Bien entendu il faut déposer sur votre fiche un composant de type THTTPRIO qui est le composant requis pour la suite du tutoriel...

Maintenant il est temps de passer aux choses sérieuses...

III Génération du fichier partner.pas et problèmes rencontrés

Ensuite l'étape suivante consiste à utiliser l'importateur de webservice fourni avec Delphi pour pouvoir générer toutes les choses nécessaires qui vont nous permettre d'interagir avec le serveur distant de Salesforce afin de récupérer en local toutes les informations dont nous avons besoin... La génération du fichier nécessite que vous décochiez l'option 'Ne pas émettre les types inutilisés' dans Options->Autres Options.

Mais malgré celà la génération des classes et des propriétés n'est optimale. En effet certains types complexes sont mal reconnus par le générateur et plus particulièrement en ce qui concenrne la classe queryResult, qui ne permet pas de créer la propriété Any. Or c'est celle-ci qui permet de récupérer les données issues du serveur. Dans l'état actuel des choses le webservice ne nous sert pas à grand chose. Mais pas de panique Delphi nous permet de récupérer la réponse du serveur sous forme XML et il est donc possible de récupérer par ce biais les informations que nous voulons... L'idée est de substituer celui-ci à celui créé par défaut par l'importateur WSDL.

IV Codage de l'applcation

1. Prérequis

Pour coder notre application nous allons avoir recours au fichier partner.pas ainsi qu'à la libairie MSXML, donc il va falloir que vous déclariez ces deux unités dals la clause uses :

uses ..., partner, MSXML;

Vous allez faire appel au composant THTTPRIO que nous avons déposer sur la fiche de l'application.

2. Connexion au serveur

Pour raliser cette opération nous allons nous apuyer sur les classes login, loginResult et loginResponse. La classe login va nous permettre d'envoyer au serveur nos coordonnées d'identification : login, mot de passe, token... la classe loginResponse va nous permettre de récupérer les informations d'id de session, et l'adresse du serveur distant qui va nous permettre de requêter celui-ci ensuite...

oLogin := login.create;

oLogin.username := sLogin; // avec oLogin : login
oLogin.password := sMdp+sToken;
HTTPRIO1.WSDLLocation := '\\.psf\Home\Documents\Projets Borland Studio\
                              SalesforceConnect\partner.wsdl';
HTTPRIO1.Service := 'sForceService';
HTTPRIO1.Port := 'Soap';

sForceService := GetSoap(true, '',HTTPRIO1); 
// sForceService est de type Soap.

oLr := LoginResponse.Create;

Comme vous pouvez le constater on va donner des valeurs aux propriétés WSDLLOcation, Service, et Port de notre composant HTTPRIO1 pour qu'il puisse envoyer les bonnes informations au serveur. Comme vous pouvez le constater ensuite on remplace bien le composant créer par le générateur par le nôtre en l'envoyant comme paramètre à la focntion GetSoap.

Nous allons traiter par la suite les réponses issues du serveur par l'intermédiaire d'un try...catch habituel pour éviter que le programme se termine par une exception... Voici le code de traitement de la réponse :

try
      oLr := sForceService.login(oLogin);
      oLResult := oLr.result;
      sURL := oLResult.serverUrl;
      sServerURL := oLResult.serverUrl;
      sSessionId := oLResult.sessionId;

      HTTPRIO1.Service := sSessionId;
      HTTPRIO1.URL := sServerURL;

      oSessionHeader := SessionHeader.Create;
      oSessionHeader.sessionId := sSessionId;

      (HTTPRIO1 as ISOAPHeaders).Send(oSessionHeader);

      StatusBar1.Panels[0].Text := 'Connexion avec le serveur effectuée';

    except

      on E : Exception do
      ShowMessage(E.ClassName+' error raised, with message : '+E.Message);
      
    end;

On récupère donc la réponse du serveur dans une variable de type loginResponse (oLr) par l'intermédiaire de la méthode de classe login. Ce résultat issu du serveur nous permet de récupérer l'id de session et l'adresse URL du serveur. il devient facile de créer les headers pour nos prochaines requêtes à envoyer au serveur. Donc pour ce faire nous avons créer deux variables sServerURL et sServerId qui sont des vaiables de type chaîne de caractères et qui permettent de stocker ces informations. Ensuite on assigne les propriétés Service et URL de notre objet HTTPRIO1 avec ces valeurs. De même on crée une instance d'objet de type SessionHeader, qui nous permettra de nous identifier lors du prochain envoi. Ces informations sont envoyées par l'intermédiaire de la fonction Send. Le traitement de l'exception vous indiquera par exemple si vos identifiants de connexion sont erronés, et vous invitera à les retaper sans que l'application ne plante...

3. L'envoi d'une requête

Le code permettant d'envoyer une requête au serveur s'appuie quant à lui exclusivement sur les classes du fichier partner.pas généré automatiquement par l'importateur WSDL. Voici le code permettant d'effectuer cette opération :

i := 0;
qOpt := QueryOptions.Create;
qOpt.batchSize := 250;
oQuery := query.Create;
  oQuery.queryString := 'select Account.Name, Name, StageName, Amount, 
                         Start_Date__c, End_Date__c, CloseDate 
                         from Opportunity';

oQueryResponse := queryResponse.Create;
oQueryResponse := (HTTPRIO1 as Soap).query(oQuery);
oQueryResult := QueryResult.Create;
oQueryResult := oQueryResponse.result;

iSizeQuery := oQueryResult.size;
Label1.Caption := intToStr(iSizeQuery) + ' enregistrements';
oQueryResult.queryLocator := 'urn:sobject.partner.soap.sforce.com';
                

Il ne faut pas oublier de créer une instance d'objets de type queryOptions et surtout assigner la valeur de la propriété batchsize à une valeur car sinon le serveur renvoie un fichier vide (ne renvoie rien). Ensuite on crée une instance de l'objet de type query qui va nous permettre d'initialiser notre requête. Il faut pour celà renseigner la propriété queryString de la classe query. C'est une requête de type OSQL qui ressemble beacoup à une requête SQL. Comme pour la connexion le serveur va nous renvoyer une réponse que nous stockons dans une variable d'instance d'objet de type queryResponse qui nous permet d'alimenter à son tour une variable d'instance de type queryResult par l'interédiaire de la propriété result... Comme nous l'avons vu la classe queryResult ne nous est pas d'un grand secours pour récupérer tous les enregistrements souhaités issus du serveur. Celle-ci est juste utile pour nous donner le nombre d'enregistrements retournés par le serveur.

4.Le traitement des enregistrements issus du serveur grâce au composant HTTPRIO1

Nous sommes bloqués à ce stade par le fait que la classe queryResult générée automatiquement est incomplète. Pour autant la situation nest pas désespéré et il existe une solution en utilisant le composant HTTPRIO1 et plus particulièrement l'événement associé AfterExecute qui permet de récupérer le XML en retour du serveur. Bien entendu cette méthode nécessite la connaissance de MSXML et de XPATH en particulier pour naviguer dans l'arborescence de ce fichier retourné. Voici le code permettant d'effctuer cette opération (nous allons le fractionner pour que celà soit plus clair) :

procedure TForm1.HTTPRIO1AfterExecute(const MethodName: string;
  SOAPResponse: TStream);
  var xmldoc : TXMLDocument;
      xmldomdoc : IXMLDomDocument;
      root, node : IXMLDomNode;
      current, node2, attrib : IXMLDOmNode;
      list, list2, attriblist : IXMLDomNodeList;
      i, j : integer;
      haslist : bool;
      sResult : string;
begin
   SoapResponse.Position := 0;
   Memo2.Lines.LoadFromStream(SoapResponse);
   xmldoc := TXMLDocument.Create(nil);


   haslist := false;
   xmldoc.loadFromStream(SoapResponse);

   xmldoc.SaveToFile('C:\response.xml');
   xmldomdoc := CoDomDocument.Create;
   xmldomdoc.async := false;
   xmldomdoc.load('C:\response.xml');
   root := xmldomdoc.DocumentElement;

   current := xmldomdoc.documentElement.childNodes.item[0].childNodes.
              item[0].childNodes.item[0];
   node := xmldomdoc.documentElement.childNodes.item[0].childNodes.item[0];

   sResult := node.nodeName;
   if (sResult = 'queryResponse') then
   begin
   haslist := current.hasChildNodes;

   if haslist then
      begin
      list := current.childNodes;


      for i:=2 to list.length-2 do
        begin

        StringGrid1.Cells[0, i-1] := 'record n°' + intToStr(i-1);
        node := list.item[i];
        list2 := node.childNodes;
        if ((tabRecords = nil)) then
          begin
          SetLength(tabRecords, list2.length, list.length - 1);
          StringGrid1.RowCount := list.length - 1;
          StringGrid1.ColCount := list2.length-1;
          end;

        for j := 2 to list2.length-1 do
          begin
          if (i = 2) then
            StringGrid1.Cells[j-1,0] := list2.item[j].nodeName;

          node2 := list2.item[j];

          if (node2.text <> null) then
            begin

            tabRecords[j,i] := node2.text;
            StringGrid1.Cells[j-1, i-1] := node2.text;
            end ;


          end;
        end;
      end;
    end;
end;
                

découpons le fichier en plusieurs parties, voici la première partie qui nous permet de récupérer la réponse du serveur qui est un flux et de le stocker dans un fichier xml que nous pourrons traiter ensuite...

SoapResponse.Position := 0;
Memo2.Lines.LoadFromStream(SoapResponse);
xmldoc := TXMLDocument.Create(nil);


haslist := false;
xmldoc.loadFromStream(SoapResponse);

xmldoc.SaveToFile('C:\response.xml');
xmldomdoc := CoDomDocument.Create;
xmldomdoc.async := false;
xmldomdoc.load('C:\response.xml');
                

Comme vous pouvez le constater nous créons un fichier physique sur le disque dur local, nous affichons la réponse serveur dans le Memo2, le Memo1 sert à afficher les requ^tes que nous envoyons au serveur...

On crée ensuite une instance d'obet qui va nous permettre de parcourir l'arborescence de notre fichier xml...

Il nous reste plus qu'à traiter les informations issues de ce fichier pour en récupérer les informations requises... Voici le code nous permettant d'effectuer cette opération :

root := xmldomdoc.DocumentElement;
   current := xmldomdoc.documentElement.childNodes.item[0].childNodes.
              item[0].childNodes.item[0];
   node := xmldomdoc.documentElement.childNodes.item[0].childNodes.item[0];
   sResult := node.nodeName;
   if (sResult = 'queryResponse') then
   begin
   haslist := current.hasChildNodes;

   if haslist then
      begin
      list := current.childNodes;


      for i:=2 to list.length-2 do
        begin

        StringGrid1.Cells[0, i-1] := 'record n°' + intToStr(i-1);
        node := list.item[i];
        list2 := node.childNodes;
        if ((tabRecords = nil)) then
          begin
          SetLength(tabRecords, list2.length, list.length - 1);
          StringGrid1.RowCount := list.length - 1;
          StringGrid1.ColCount := list2.length-1;
          end;

        for j := 2 to list2.length-1 do
          begin
          if (i = 2) then
            StringGrid1.Cells[j-1,0] := list2.item[j].nodeName;

          node2 := list2.item[j];

          if (node2.text <> null) then
            begin

            tabRecords[j,i] := node2.text;
            StringGrid1.Cells[j-1, i-1] := node2.text;
            end ;


          end;
        end;
      end;
    end;
end;
                

la première ligne de ce bloc de code nous permet de sélectionner la racine de notre fichier xml (variable root). Nous descendons ensuite dans l'arborescence pour atteindre le noeud result (variable current) qui ous intéresse plus particulièrement car c'est à partir de ce noeud que l'on va récupérer les données des enregistements (à partir des enfants de ce noeuds). La variable node nous permet de récupérer le type de réponse du retour de la requête issu du serveur. En effet nous ne voulons traiter que le retour de type queryResponse et non la loginResponse ou autre... Donc s'il s'agit du type souhaité nous examinons s'il existe des noeuds enfants issus du noeud records. pour celà on effectue une boucle en éliminant les deux premiers noeuds car les informations ne nous intéressent pas (enfin me concernant, pour vous je ne sais pas???). En effet il s'agit de l'information done égale à true et le queryLocator. C'est donc pour cette raison que l'indice i commence à 2 pour la boucle principale. Cette boucle a nous permettre de lister tous les noeuds nommés records. Le nombre de noeuds doit être équivalent au nombre assigné à la propriété size de notre variable d'instance queryResult... Maintenant pour récupérer les informations sur chaque informations, il faut initialiser une boucle imbriquée pour récupérer les valeurs de chaque noeud enfant qui correspond à chaque valeur de champ demandée dans notre requêt. Là aussi nous faisons parti notre indice boucle à 2 pour enlever les informations du type d'objet et d'Id. Il devient simple de récupérer les informations souhaitées en récupérant la valeur de chaque noeud grâce à la fonction Text... Les entêtes de notre grille sont renseignées par l'intermédiaire de chaque attribut de chaque noeud... Et voilà nous récupérons bien toutes les informations voulues issues du serveur Salesforce.

V Conclusion

Dans cetutoriel nous avons unesolution pour contourner un problème récurrent concernant l'utilisation de Webservice écrit sur une plateforme .Net. En effet l'importateur WSDL fourni par Borland ne permet pas de parser quelques informations complexes issues de ceux-ci. Or celà touche précisément la partie la plus importante du WebService partner.wsdl, empéchant toute utilisation dans de bonnes conditions. Je vous ai donc présenté une méthode hybride qui permet de s'en sortir par l'utilisation d'un composant de type THTTPRIO. Une autre solution est de partir sur l'utilisation de XML en utilisant pocketsoap par exemple...