Bob Swart (aka Dr.Bob)
SOAP & Web Services (1) met Delphi (alsmede Delphi for .NET en C#)

Dit artikel gaat verder waar mijn artikel over ASP.NET ophield: we richten ons nu uitsluiten op Web Services en real-world problemen die op kunnen treden bij het ontwerp, de bouw en het implementeren/deploy-en van Web Services. Als voorbeelden gebruik ik Delphi 7 Enterprise (om via BizSnap enkele Web Services te bouwen), en laat zien wat de aandachtspunten zijn bij het ontwerpen van je Web Service interface (naar de buitenwereld toe). Beslissingen op dit moment kunnen gevolgen hebben op de performance, schaalbaarheid en uitbreidbaarheid van de Web Service. Vervolgens bekijken we de cross-platform eigenschap van Web Services, door de bestaande Delphi 7 Web Service te importeren met C# onder .NET.
In deel twee zal ik vervolgens een nieuwe Web Service (C#) bouwen onder .NET en die met Delphi 7 onder Win32 importeren. Interoperability en compatibiliteits problemen tussen Delphi en C# komen hierbij uitgebreid aan de orde.

Let op: Ik maak bij het schrijven van dit artikel gebruik van de Delphi for .NET preview command-line compiler met Update 3 (van februari 2003) op .NET Framework versie 1.0 met Service Pack 2. Dezelfde code draait inmiddels ook onder .NET Framework 1.1.

Delphi 7 Web Services
Voor het eerste onderdeel van dit artikel hebben we de Enterprise (or Architect) editie van Delphi 7 nodig. Als je die niet hebt, dan kun je een gratis trial versie van Delphi 7 Architect downloaden van de Borland website. De technieken zijn ook bruikbaar voor Delphi 6 (Enterprise), Kylix 2 en 3 (Enterprise) en zelfs C++Builder 6 (Enterprise) - mits je de Pascal code dan door C++ vervangt.
Een Web Service is een ander woord voor een SOAP object dat als "service" via het "web" beschikbaar wordt gesteld aan gebruikers (ook wel web service consumers genoemd). In theorie is het ook mogelijk om via FTP of SMTP (e-mail) een SOAP object beschikbaar te stellen aan de buitenwereld, maar in praktijk gebeurt dit in 99 van de 100 gevallen via HTTP, en vandaar de naam web service.
We zullen nu eerst in Delphi 7 een web service bouwen, dus start Delphi 7 Enterprise (of Architect) en doe File | New - Other, en ga naar de WebServices tab van de Object Repository.

WebServices tab van de Object Repository

We beginnen met het eerste icon om een nieuwe SOAP Server application te maken. Met de tweede kunnen we eventueel een SOAP Server data module maken (zie mijn dynamische paper voor details over deze mogelijkheid), en met het derde icon kunnen we een nieuw SOAP Server Interface - oftwel een nieuw SOAP object - aan de SOAP Server toevoegen.
Kies voor de SOAP Server application icon, met als resultaat de volgende dialoog waar we kunnen kiezen wat voor soort web server toepassing we willen gebruiken:

New SOAP Server Application

De eerste vier mogelijkheden leveren allemaal een project op dat je kunt deployen op en met een web server - IIS or Apache. Deze zijn echter minder goed te debuggen - zonder ingrijpende stappen te moeten doen. Speciaal hiervoor is de vijfde mogelijkheid toegevoegd: de Web App Debugger executable. Deze laatste keuze levert een stand-alone toepassing op (met een leeg form dat alleen maar dient om de executable te laten draaien met "message-loop" zodat op inkomende requests gewacht kan worden). Een Web App Debugger executable is natuurlijk niet geschikt om daadwerkelijk te deployen - daar moet je een van de eerste vier mogelijkheden voor gebruiken.
In bovenstaand voorbeeld heb ik voor de Web App Debugger executable gekozen, en moet vervolgens een Class Name opgeven: Echo42 in dit geval (het project wordt dan onder de project executable naam gevolgd door een punt en dan de Class Name geregistreerd, zoals we straks zullen zien).
Als we op OK klikken wordt meteen een nieuwe SOAP Server application gemaakt, inclusief een web module met daarop drie componenten, alsmede een nieuw leeg Delphi form, en krijgen we een vraag voorgeschoteld of we meteen een interface voor de SOAP module willen maken:

Create Interface for SOAP module?

Let echter eerst eens even op de drie componenten op de web module achter de dialoog. Deze drie verzorgen het web service skelet van onze SOAP server (en een interface toevoegen aan de SOAP server betekent in feite gewoon een nieuw SOAP object toevoegen aan het project). De HTTPSoapDispatch component ontvangt binnenkomende SOAP requests van web module en heeft als taak op te zoeken welk SOAP object dit request zal (moeten) afhandelen een een response moet genereren. Het tweede component, de HTTPSoapPascalInvoker, wordt gebruikt om van het gevonden SOAP object de juiste Pascal methode aan te roepen (voor C++Builder is er een vergelijkbare HTTPSoapCppInvoker component). Dit is inclusief het meegeven van de juiste parameter waarden en het ontvangen van het resultaat. Het resultaat wordt vervolgens door de HTTPSoapDispatcher weer ingepakt teruggestuurd naar de oorspronkelijke verzender. Het derde component, de WSDLHTMLPublish, wordt gebruikt om de WSDL voor alle geregistreerde SOAP objecten te genereren voor ons (komen we nog op terug). De drie componenten op de web module nemen dus het interface van de web service met de buitenwereld voor hun rekening, en het enige dat we (als Delphi ontwikkelaars) nog hoeven te doen is nieuwe SOAP objecten maken en toe laten voegen aan het project.
Als we de vraag om een interface voor een nieuwe SOAP module met "Yes" beantwoorden krijgen we onderstaande dialoog waarin we daadwerkelijk een nieuw SOAP object (oftewel een nieuw web service interface) kunnen laten genereren. Als Service name vul ik D7Echo in: ik wil met name de interoperability testen tussen Delphi 7 en .NET, en één van de manieren om dit te doen is om een zogenaamde "echo" web service te maken waarin alles wat erin komt ook weer wordt teruggegeven.
In het rechterdeel van de dialoog zie je opties om commentaar te genereren (dat is erg handig), en om een aantal voorbeeld methodes te laten genereren. Dat laatste doe je maar één keer, dan geloof je het wel. Voor de D7Echo service zijn de voorbeeld methodes echter wel handig, want het zijn al de echo methoden die als voorbeeld worden gegenereerd (dat komt dus goed uit in dit geval), namelijk echoEnum, echoDoubleArray, echoMyEmployee en echoDouble.

Add New WebService

Als laatste is er de optie om het Service activation model te kiezen. De meuzemogelijkheden zijn Per Request of Global. Default is de keuze Per Request wat betekent dat voor ieder binnenkomend request een instantie van het SOAP object wordt aangemaakt (speciaal voor dat request) waarmee het request wordt afgehandeld. Uiteraard alleen als het request voor dat betreffende SOAP object is - iets dat door de HTTPSoapDispatcher wordt bepaald. Na afhandeling van het request wordt het SOAP object weer vrijgegeven. Dat aanmaken en vrijgeven kost wat tijd, maar je hebt dus als het goed is nooit een geheugenlek (tenzij er iets foutgaat bij het vrijgeven). Bijkomend feit is dat de web service op deze manier 100% stateless is, want zelfs als je al wat "bagage" achterlaat in het SOAP object (zoals een teller), dan nog zal deze bagage verwijderd zijn als je de tweede keer terugkomt met een request, omdat het SOAP object in de tussentijd al zal zijn verwijderd (en we dus inmddels met een nieuwe instantie praten).
Helaas.. het kiezen van het Global activation model heeft geen effect in Delphi 7 (of Delphi 6.02 waar het voor het eerst te zien was), omdat Borland een beetje vergeten is om het complete template mee te leveren. Op mijn SOAP Bubbles pagina heb ik de juiste versie staan.
Als je nu op de OK knop klikt worden en twee nieuwe units gegenereerd: D7EchoIntf.pas voor het SOAP object interface, en D7EchoImpl.pas voor de implementation ervan. Straks zullen we ook C# web services met ASP.NET zien waarbij de definitie en implementatie bij elkaar zijn opgenomen, maar Delphi houdt deze zaken liever gescheiden.

D7Echo Interface
De inhoud van D7EchoIntf.pas is hieronder te zien. Let op dat we de stdcall calling convention nodig hebben als we zelf nieuwe methodes gaan toevoegen.

  { Invokable interface ID7Echo }

  unit D7EchoIntf;
  interface
  uses
    InvokeRegistry, Types, XSBuiltIns;

  type
    TEnumTest = (etNone, etAFew, etSome, etAlot);

    TDoubleArray = array of Double;

    TMyEmployee = class(TRemotable)
    private
      FLastName: AnsiString;
      FFirstName: AnsiString;
      FSalary: Double;
    published
      property LastName: AnsiString read FLastName write FLastName;
      property FirstName: AnsiString read FFirstName write FFirstName;
      property Salary: Double read FSalary write FSalary;
    end;

    { Invokable interfaces must derive from IInvokable }
    ID7Echo = interface(IInvokable)
    ['{EE9BFE38-D24C-4980-8A8A-1856342A02DF}']

      { Methods of Invokable interface must not use the default }
      { calling convention; stdcall is recommended }
      function echoEnum(const Value: TEnumTest): TEnumTest; stdcall;
      function echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall;
      function echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall;
      function echoDouble(const Value: Double): Double; stdcall;
    end;

  implementation

  initialization
    { Invokable interfaces must be registered }
    InvRegistry.RegisterInterface(TypeInfo(ID7Echo));
  end.
Let ook op de aanroep van RegisterInterface in de initialization sectie die er verantwoordelijk voor is dat het ID7Echo interface in de zogenaamde Invokable Registry zal worden geregistreerd. Dit is een soort repository in het geheugen van de Delphi 7 SOAP Server toepassing die ervoor zorgt dat de interfaces, methodes en parameters te vinden zijn door de WSDLHTMLPublish component zodat die de juiste WSDL kan produceren. Omdat ik wel eens problemen heb gehad met 8-bit Char en 16-bit WideChar types in Delphi en C#, kies ik ervoor om het TMyEmployee type uit te breiden met een Char en WideChar property, zodat we die straks kunnen testen als we met C# clients gaan praten. Daarnaast heb ik slechte ervaringen met de compatibiliteit van dates en times, en daarom voeg ik ook een veld toe van type XSDateTime as well. Dit levert de volgende aangepaste definitie op van de TMyEmployee class:
  TMyEmployee = class(TRemotable)
  private
    FLastName: AnsiString;
    FFirstName: AnsiString;
    FSalary: Double;
    FC: Char; // BS
    FWC: WideChar; // BS
    FDate: TXSDateTime; // BS
  published
    property LastName: AnsiString read FLastName write FLastName;
    property FirstName: AnsiString read FFirstName write FFirstName;
    property Salary: Double read FSalary write FSalary;
    property C: Char read FC write FC; // BS
    property WC: WideChar read FWC write FWC; // BS
    property Date: TXSDateTime read FDate write FDate; // BS
  end;

D7Echo Implementatie
Nadat ik het interface een beetje heb aangepast, volgt hieronder de implementatie van D7EchoImpl.pas waarin TD7Echo is afgeleid van TInvokableClass en het ID7Echo interface implementeert. Het gegenereerde ToDo-commentaar binnen de echoEnum, echoDoubleArray en echoDouble methodes lijkt onnodig, aamgezien het resultaat al een waarde krijgt (de Echo is al geïmplementeerd). Echter, dit kan je wel op het verkeerde been zetten als je naar de implementation van de echoMyEmployee kijkt, want die is nog niet geïmplementeerd. Er wordt wel een TMyEmployee gecreëerd en teruggegeven, maar de velden worden niet gekopieerd. Foutje? Waarschijnlijk, maar er zal niks aan gedaan worden want het zijn toch maar "sample methods", dus je had het kunnen weten...
Ik heb zelf maar de code geschreven om de drie bestaande en de drie nieuwe velden van het TMyEmployee object te kopiëren, zoals hieronder te zien is:

  { Invokable implementation File for TD7Echo which implements ID7Echo }

  unit D7EchoImpl;
  interface
  uses
    InvokeRegistry, Types, XSBuiltIns, D7EchoIntf;

  type
    { TD7Echo }
    TD7Echo = class(TInvokableClass, ID7Echo)
    public
      function echoEnum(const Value: TEnumTest): TEnumTest; stdcall;
      function echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall;
      function echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall;
      function echoDouble(const Value: Double): Double; stdcall;
    end;

  implementation

  function TD7Echo.echoEnum(const Value: TEnumTest): TEnumTest; stdcall;
  begin
    { TODO : Implement method echoEnum }
    Result := Value;
  end;

  function TD7Echo.echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall;
  begin
    { TODO : Implement method echoDoubleArray }
    Result := Value;
  end;

  function TD7Echo.echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall;
  begin
    { TODO : Implement method echoMyEmployee }
    Result := TMyEmployee.Create;
    Result.LastName := Value.LastName; // BS
    Result.FirstName := Value.FirstName; // BS
    Result.Salary := Value.Salary; // BS
    Result.C := Value.C; // BS
    Result.WC := Value.WC; // BS
    Result.Date := Value.Date.Clone; // BS
  end;

  function TD7Echo.echoDouble(const Value: Double): Double; stdcall;
  begin
    { TODO : Implement method echoDouble }
    Result := Value;
  end;

  initialization
    { Invokable classes must be registered }
    InvRegistry.RegisterInvokableClass(TD7Echo);
  end.
En ook hier zien we in de initialization sectie het gebruik van de Invokable Registry. Deze keer om de implementatie class TD7Echo te registreren, zodat de HTTPSoapDispatcher en HTTPSoapPascalInvoker de implementatie van het interface kunnen vinden en de juiste methodes kunnen aanroepen.

D7Echo Test
Tijd om de Web App Debugger executable te testen. Compileer en Run de toepassing zodat het (lege) form te zien is. Start dan de Web App Debugger vanuit het Delphi Tools menu:

Web App Debugger

Klik op de Start knop zodat de server zal starten en de URL aktief wordt (zie screenshot hierboven). We kunnen nu op de URL klikken om de default browser te starten met daarin een lijst met geregistreerde Web App Debugger servers.
Merk op dat serverinfo.ServerInfo de Web App Debugger server zelf is, en D7EchoWAD.Echo42 het voorbeeld waar we op dit moment mee bezig zijn.

Registered Web App Debugger Applications

Kies de D7EchoWAD.ECho42 toepassing en klik op Go om deze te starten. De Web App Debugger zal hiertoe "verbinding" maken met de Web App Debugger toepassing die we op dit moment in de Delphi IDE aan het draaien (debuggen) zijn, en het resultaat in de browser is een lijst van aanwezige interfaces.

WebServices tab of the Object Repository

Zoals je ziet zijn er twee PortTypes aanwezig in de D7EchoWAD web service. De laatste is altijd de IWSDLPublish die het gevolg is van het gebruiken van de WSDLHTMLPublish component op de web module. De eerste is waar het om gaat in dit geval: het ID7Echo interface. We kunnen in ieder geval al de vier methodes zien, en op de WSDL link klikken om de WSDL definitie te krijgen:

WebServices tab of the Object Repository

Als je goed kijkt naar de screenshot met de WSDL, dan zie je dat het targetNamespace attribute nog op de default waarde http://tempuri.org staat. Dit is niet zo'n goed idee, omdat iedereen default deze namespace gebruikt, en dan kun je in problemen komen als jouw web service een naam of type gebruikt die ook iemand anders (in dezelfde default namespace) gebruikt. Niet als ze door afzonderlijke clients gebruikt worden, maar wel als ze ooit gecombineerd zullen worden. De kans is klein, maar het zou jammer zijn als het ooit een keer zou gebeuren, en het is eenvoudig aan te passen.
Ga naar de web module van de SOAP Server toepassing, en verander de TargetNamespace property van het TWSDLHTMLPublish component. Ik heb de TargetNamespace veranderd in http://www.eBob42.org/ voor het voorbeeld project van dit artikel.

Echo42 Project Group
We hebben nu de URL http://localhost:8081/D7EchoWAD.Echo42/wsdl/ID7Echo om de WSDL via de Web App Debugger executable te kunnen gebruiken, maar dit werkt niet zo goed als ik de web service daadwerkelijk wil deployen (ik ben niet van plan de Web App Debugger altijd te laten draaien). Een Web App Debugger project is fijn om lokaal te testen en te debuggen, maar uiteindelijk wil ik een CGI, ISAPI of Apache target deployen op het web. En dan is het handig gebruik te kunnen maken van de Delphi Project Manager zodat ik twee verschillende projecten heb (een WAD en een CGI target bijvoorbeeld) die toch allebei dezelfde SOAP server interface en implementatie units delen. Start hiertoe een nieuwe SOAP Server toepassing, verwijder alle units en voeg alle units van de Web App Debugger project toe (met uitzondering van het lege form dat uiterard alleen zin heeft in een WAD project en niet in een ander web server project).

Project Manager with D7EchoWAD and D7EchoCGI

Ik kan in bovenstaand voorbeeld de CGI versie van de web service op het internet deployen, en toch de WAD versie gebruiken om lokaal te debuggen mocht ik nog fouten vinden (of dingen toevoegen en eerst willen testen).

C#: Importeren met WSDL
Wie de .NET Framework SDK heeft geïnstalleerd heeft daarmee de beschikking over de command-line tool WSDL om web services te importeren en er C# of VB.NET code voor te genereren. Omdat ik de D7Echo web service op mijn eBob42.com website heb staan als http://www.eBob42.com/cgi-bin/D7EchoCGI.exe, kan dat met de volgende command op de command-line:

  wsdl http://www.eBob42.com/cgi-bin/D7EchoCGI.exe/wsdl/ID7Echo
De gegenereerde C# code is niet zo heel interessant om te bekijken (met enkele uitzonderingen waar ik zo terug op kom), en kan met de C# command-line compiler tot een assembly gecompileerd worden die door andere .NET toepassingen gebruikt kan worden:
  csc /t:library ID7Echoservice.cs
Het resultaat is een ID7Echoservice.dll assembly die met de Delphi 7 web service kan communiceren en door .NET toepassingen gebruikt kan worden.

C# Import results
Voor we de ID7Echoservice.dll assembly daadwerkelijk gaan gebruiken wil ik eerst even een kijkje nemen in de gegenereerde C# code, en dan met name naar de C# code die voor de Delphi types TEnumTest en TMyEmployee is gegenereerd.

  /// 
  [System.Xml.Serialization.SoapTypeAttribute("TEnumTest", "urn:D7EchoIntf")]
  public enum TEnumTest {
      /// 
      etNone,
      /// 
      etAFew,
      /// 
      etSome,
      /// 
      etAlot,
  }

  /// 
  [System.Xml.Serialization.SoapTypeAttribute("TMyEmployee", "urn:D7EchoIntf")]
  public class TMyEmployee {
      /// 
      public string LastName;
      /// 
      public string FirstName;
      /// 
      public System.Double Salary;
      /// 
      public string C;
      /// 
      public string WC;
      /// 
      public System.DateTime Date;
  }
De TEnumTest ziet er goed uit, maar het is op z'n minst een beetje vreemd te noemen dat de Char en WideChar van Delphi worden vertaald naar een string aan de C# kant. Er zal toch echt niet meer dan één teken worden verwacht (en teruggegeven) voor de Delphi web service, dus dit lijkt nog niet helemaal 100% correct te zijn. Als we naar de WSDL definitie kijken voor de TMyEmployee, dan ziet die er als volgt uit:
  <xs:complexType name="TMyEmployee">
    <xs:sequence>
      <xs:element name="LastName" type="xs:string"/>
      <xs:element name="FirstName" type="xs:string"/>
      <xs:element name="Salary" type="xs:double"/>
      <xs:element name="C" type="xs:string"/>
      <xs:element name="WC" type="xs:string"/>
      <xs:element name="Date" type="xs:dateTime"/>
    </xs:sequence>
  </xs:complexType>
Dat is dus duidelijk een xs:string die in de WSDL aangegeven staat, dus kan ik de .NET WSDL importer niet de schuld geven, maar moet ik mijn wenkbrouwen optrekken naar Delphi die van Char en WideChar kennelijk liever string wil maken.

C# Console Client
Om de ID7Echoservice.dll assembly te kunnen gebruiken in .NET heb ik een klein C# console programma geschreven dat de assembly gebruikt en de methodes van de Delphi 7 web service aanroept en kijkt of het resultaat ook is wat erin is gestopt.

  using System;

  namespace eBob42
  {
    class UseD7Echo
    {
      static void Main(string[] args)
      {
        ID7Echoservice D7Echo = new ID7Echoservice();

        // echoDouble
        System.Double value = 42.0;
        Console.WriteLine("D7Echo.echoDouble(42.0) = " +
          D7Echo.echoDouble(value).ToString());

        // echoEnum
        TEnumTest MyEnum = TEnumTest.etSome;
        if (D7Echo.echoEnum(MyEnum) == TEnumTest.etSome)
          Console.WriteLine("enum OK");
        else Console.WriteLine("enum failed");

        // echoDoubleArray
        System.Double[] DAin = new System.Double[20];
        for (int i=0; i < 20; i++) DAin[i] = i / 20.0;
        System.Double[] DAout = D7Echo.echoDoubleArray(DAin);
        for (int i=0; i < 20; i++)
          if (DAout[i] != DAin[i])
            Console.WriteLine("EchoDoubleArray: " +
              DAout[i].ToString() + " != " + DAin[i].ToString());

        // echoMyEmployee
        TMyEmployee MyEmployee = new TMyEmployee();
        MyEmployee.LastName = "Swart";
        MyEmployee.FirstName = "Bob";
        MyEmployee.Salary = 42;
        MyEmployee.C = "C"; // string instead of char!
        MyEmployee.WC = "W"; // string instead of char!
        MyEmployee.Date = DateTime.Now;
        TMyEmployee Me = D7Echo.echoMyEmployee(MyEmployee);
        if (Me.LastName != MyEmployee.LastName)
          Console.WriteLine("LastName failed: [" + Me.LastName + "]");
        if (Me.FirstName != MyEmployee.FirstName)
          Console.WriteLine("FirstName failed: [" + Me.FirstName + "]");
        if (Me.Salary != MyEmployee.Salary)
          Console.WriteLine("Salary failed: " + Me.Salary.ToString());
        if (Me.C != MyEmployee.C)
          Console.WriteLine("C failed = [" + Me.C + "]");
        if (Me.WC != MyEmployee.WC)
          Console.WriteLine("W = [" + Me.WC + "]");
        if (Me.Date != MyEmployee.Date)
          Console.WriteLine("Now = " + MyEmployee.Date.ToString() +
                            " != " + Me.Date.ToString());
      }
    }
  }
Het compileren met csc op de command-line moet deze keer met als extra optie de /r switch die naar de ID7Echoservice.dll moet wijzen (een referentie naar de assembly die nodig is):
  csc /r:ID7Echoservice.dll UseD7Echo.cs

Interoperability Resultaten
De resultaten zagen er goed uit, met uitzondering van het DateTime veld van de TMyEmplyee class. Om de een of andere reden was dat veld leeg (er kwamen allemaal 0-en uit). Terwijl ik er toch echt een datum in stop. Wat kon hier de oorzaak van zijn? Wellicht het feit dat de TXSDayeTime in Delphi een class is die gebruikt wordt als property van de TMyEmplyee class? Om te kijken of het aan de TXSDateTime zelf lag, heb ik nog een extra echo methode toegevoegd aan de D7Echo web service, namelijk eentje die een TSXDateTime teruggeeft:

  function TD7Echo.echoDate(const Value: TXSDateTime): TXSDateTime;
  begin
    Result := Value.Clone; // BS
  end;
De C# code om de echoDate methode te testen ziet er als volgt uit:
  // echoDate
  DateTime nu = D7Echo.echoDate(DateTime.Now);
  Console.WriteLine("Date: " + nu.ToString());
En dit werkt zonder problemen. Dus het lijkt erop dat de TXSDateTime binnen een class niet goed gaat, maar als op zichzelfstaande waarde wel goed. Dit ga ik uiteraard verder uitzoeken, en ik kom hier vast nog wel een keer op terug (in een vervolgartikel of tijdens een van mijn sessies).

Als laatste test heb ik nog een Delphi 7 client geschreven die de Delphi 7 web service importeert en gebruikt, en "helaas" gaat het gebruik van de TXSDateTime in dat geval ook goed. Dat betekent dus dat de Delphi 7 web service de waarde van de TXSDateTime goed ontvangt, en deze ook als echo goed teruggeeft (als Date veld van de nieuwe TMyEmployee), maar dat vervolgens alleen Delphi 7 zelf deze waarde kan begrijpen, en .NET er niks meer mee kan. Ergens tusen die twee zit een "lekje" en het lijkt wel alsof er bijvoorbeeld door de .NET client meer informatie bij de xs:datetime aan Delphi wordt meegegeven die Delphi vervolgens niet meer mee teruggeeft, waardoor Delphi (die deze extra informatie niet nodig heeft) de echo wel kan begrijpen, maar .NET / C# er door het ontbreken van de extra informatie niks meer mee kan. Ik zoek dit nog uit, en kom er vast nog wel een keer op terug...

Delphi 7 SOAP Attachments
Delphi 6 was de eerste ontwikkelomgeving met SOAP ondersteuning, en heeft met Delphi 7 verschillende uitbreidingen op dat gebied gekregen. Met name ondersteuning voor SOAP attachments is iets waar Borland best trots op was. Door gebruik te maken van een TSoapAttachment class kun je zowel spullen in als uit een SOAP attachment halen, en het SOAP attachment wordt letterlijk als MIME attachment met het HTTP result meegestuurd van client naar server en vice versa. Dit werkt prima tussen Delphi en Delphi. Maar helaas, ik was niet in staat om vanuit een .NET client gebruik te maken van Delphi 7 SOAP attachments. Erger nog: ik kon de WSDL niet eens probleemloos importeren, omdat er geklaags werd over een "missing matching binding". De on-line help van Delphi 7 bracht uitkomst (of in ieder geval een verklaring): de Delphi 7 attachments worden als MIME multipart forms verstuurd, en .NET kan daar helaas niet mee omgaan, maar werkt zelf met DIME attachments. Om een lang verhaal kort te maken: ik ben er niet in geslaagd om SOAP attachments uit te wisselen tussen Delphi en .NET, dus dit is een feature van Delphi 7 waar we voorlopig wat voorzichtig mee om moeten gaan vrees ik...

Meer Informatie...
In dit artikel heb ik laten zien hoe we Delphi 7 web services kunnen bouwen en importeren in een .NET omgeving, en wat daarbij de compatibiliteits en interoperatbility problemen zijn die we tegen kunnen komen. Ook heb ik een C# web service met ASP.NET gebouwd en deze in Delphi 7 gebruikt, en laten zien waar we daarbij op moeten letten. Als allerlaatste test heb ik toen ook de C# web service "vertaald" naar Delphi for .NET en laten zien wat daarbij de issues zijn waar we op moeten letten..
Mocht iemand nog vragen, opmerkingen of suggesties hebben, dan hoor ik die het liefst via . Wie op de hoogte wil blijven van de laatste ontwikkelingen zou zeker eens moeten overwegen om mijn Delphi for .NET clinic bij te wonen.


Dit artikel is gebasseerd op mijn SOAP sessie tijdens de CttM 2003 - mei 2003

This webpage © 2003-2006 by webmaster drs. Robert E. Swart (aka - www.drbob42.com). All Rights Reserved.