Delphi 7 Web Services migreren naar ASP.NET |
Dit artikel beschrijft het belangrijkste deel van de Conference to the Point van 12 december 2003, waar ik in een sessie van 1 uur en 15 minuten in meer detail ben ingaan op het onderwerp, en heb laten zien hoe we bestaande Delphi 7 web services op eenvoudige wijze kunnen migreren naar ASP.NET. Ik heb hierbij zelfs als eindresultaat een cross-platform project overgehouden die zowel met Delphi 7 als met Delphi for .NET te compileren is tot een Web Service toepassing.
Delphi 7 Web Service
Ik wil beginnen met het bouwen van een Delphi 7 Web Service - op de meest default manier die er is - en vervolgens laten zien hoe ik hier een ASP.NET Web Service van kan maken.
Start Delphi 7 Enterprise voor het eerste deel van het voorbeeld. Doe File | New - Other, ga naar de WebServices tab en maak een nieuwe Soap Server Application. In de dialoog die volgt kun je kiezen voor het target van de Win32 Web Service. Het maakt me niet uit wat je hier kiest: ikzelf kies om te beginnen vaak voor CGI (lekker makkelijk), maar ook ISAPI of een Web App Debugger executable is goed. Dat is slechts de "schil" van het project - het gaat mij om het SOAP object dat erin komt te zitten.
Bij Delphi 7 komt op een gegeven moment de vraag "Create Interface for SOAP module", om te kijken of je een SOAP Interface wilt toevoegen aan je project. Natuurlijk wil je dat, maar je kunt hetzelfde bereiken door de SOAP Server Interface wizard te gebruiken die je ook in de WebServices tab van de Object Repository kunt vinden (handig te weten voor als je meer dan één SOAP object in je toepassing wilt stoppen).
Hoe je er ook komt, in ieder geval heb je uiteindelijk de "Add New WebService" dialoog voor je, te zien in figuur 1:
Implementatie
Het lijkt verkeerd om, maar laten we beginnen bij de implementatie. De xxxImpl.pas unit bevat de volgende code (deze werkt alleen nog maar met Delphi 6 of 7):
{ Invokable implementation File for TCrossPlatformSoapService which implements ICrossPlatformSoapService } unit CrossPlatformSoapServiceImpl; interface uses InvokeRegistry, Types, XSBuiltIns, CrossPlatformSoapServiceIntf; type { TCrossPlatformSoapService } TCrossPlatformSoapService = class(TInvokableClass, ICrossPlatformSoapService) 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 TCrossPlatformSoapService.echoEnum(const Value: TEnumTest): TEnumTest; stdcall; begin { TODO : Implement method echoEnum } Result := Value; end; function TCrossPlatformSoapService.echoDoubleArray(const Value: TDoubleArray): TDoubleArray; stdcall; begin { TODO : Implement method echoDoubleArray } Result := Value; end; function TCrossPlatformSoapService.echoMyEmployee(const Value: TMyEmployee): TMyEmployee; stdcall; begin { TODO : Implement method echoMyEmployee } Result := TMyEmployee.Create; Result.FirstName := 'Bob'; Result.LastName := 'Swart'; end; function TCrossPlatformSoapService.echoDouble(const Value: Double): Double; stdcall; begin { TODO : Implement method echoDouble } Result := Value; end; initialization { Invokable classes must be registered } InvRegistry.RegisterInvokableClass(TCrossPlatformSoapService); end.
Uses Clause
We beginnen met de uses clause. De Borland implementatie van SOAP maakt gebruik van de InvokeRegistry, Types, en XSBuiltIns units, maar voor ASP.NET kunnen we volstaan met de System.Web.Services unit. Omdat de CLR compiler optie is voorgedefinieerd voor Delphi for .NET, kunnen we dit als volgt uitdrukken:
uses {$IFDEF CLR} System.Web.Services, {$ELSE} InvokeRegistry, Types, XSBuiltIns, {$ENDIF} CrossPlatformSoapServiceIntf;
TCrossPlatformSoapService
Vervolgens komen we bij de definitie van de TCrossPlatformSoapService. Nog afgezien van het feit dat een T-prefix niet meer gebruikelijk is onder .NET (maar je de naam van de SOAP server waarschijnlijk niet zomaar kan wijzigen), zullen we ook hier de nodige wijzigingen moeten aanbrengen. De Delphi 7 versie van de code geeft aan dat de nieuwe web service is afgeleid van TInvokableClass en een bepaald interface implementeert, (ICrossPlatformSoapService in mijn geval). Dat interface is onder .NET niet meer nodig, en de voorvader van de web service heet daar gewoon WebService. Dus dat wordt als volgt:
type { TCrossPlatformSoapService } {$IFDEF CLR} [WebService(Namespace='http://eBob42.org', Description='Geschreven in Delphi 7, bruikbaar in ASP.NET')] TCrossPlatformSoapService = class(WebService) {$ELSE} TCrossPlatformSoapService = class(TInvokableClass, ICrossPlatformSoapService) {$ENDIF}De ASP.NET Web Service moet ook nog het attribuut [WebService] meekrijgen, met daarin de Namespace (anders wordt de default namespace http://tempuri.org gebruikt) en eventueel een Description.
Web Methods
Voor de individuele methoden moeten we ook een kleine aanpassing verrichten. De stdcall calling conventie heeft geen betekening meer onder .NET, en in plaats daarvan moet iedere methode die we als web service methode willen exporteren het attribuut [WebMethod] meekrijgen. Dat komt er dus als volgt uit te zien:
public {$IFDEF CLR} [WebMethod] {$ENDIF} function echoEnum(const Value: TEnumTest): TEnumTest; {$IFNDEF CLR} stdcall; {$ENDIF} {$IFDEF CLR} [WebMethod] {$ENDIF} function echoDoubleArray(const Value: TDoubleArray): TDoubleArray; {$IFNDEF CLR} stdcall; {$ENDIF} {$IFDEF CLR} [WebMethod] {$ENDIF} function echoMyEmployee(const Value: TMyEmployee): TMyEmployee; {$IFNDEF CLR} stdcall; {$ENDIF} {$IFDEF CLR} [WebMethod] {$ENDIF} function echoDouble(const Value: Double): Double; {$IFNDEF CLR} stdcall; {$ENDIF} end;
Implementatie
En nu komt het mooiste: bij de implementatie van de web methods hoeven we alleen maar op de stdcall te letten. Je kan die netjes tussen
{$IFNDEF CLR} stdcall; {$ENDIF}neerzetten, of helemaal weghalen (de stdcall calling conventie werd immers al aangegeven bij de declaratie van de web methods in the class zelf, en dan mag je bij de implementatie zelfs de argumentenlijst weglaten, en zeker de calling conventie).
Initialization
We zijn bijna klaar - met deze unit dan - alleen nog even de initialization sectie onder handen nemen. Die moet alleen bij de Delphi 7 versie uitgevoerd worden, voor een ASP.NET server is het niet nodig, dus dat wordt als volgt:
{$IFNDEF CLR} initialization { Invokable classes must be registered } InvRegistry.RegisterInvokableClass(TCrossPlatformSoapService); {$ENDIF} end.
Interface
Nu dan de interface unit. Die bevat behalve de definitie van het ICrossPlatformSoapServer interface ook de custom types die we gebruiken in onze web service (zoals de TMyEmployee bijvoorbeeld). Dat is de enige reden dat we überhaubt nog iets te maken hebben met de xxxIntf.pas unit, anders zouden we hem helemaal links kunnen laten liggen. Om maar meteen met de deur in huis te vallen: het eindresultaat met {$IFDEFs} moet er als volgt uit komen te zien:
{ Invokable interface ICrossPlatformSoapService } unit CrossPlatformSoapServiceIntf; interface {$IFNDEF CLR} uses InvokeRegistry, Types, XSBuiltIns; {$ENDIF} type TEnumTest = (etNone, etAFew, etSome, etAlot); TDoubleArray = array of Double; TMyEmployee = class{$IFNDEF CLR}(TRemotable){$ENDIF} 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; {$IFNDEF CLR} { Invokable interfaces must derive from IInvokable } ICrossPlatformSoapService = interface(IInvokable) ['{FD1BBEBC-382E-47D6-A3D1-81D91C935F2F}'] { 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; {$ENDIF} implementation {$IFNDEF CLR} initialization { Invokable interfaces must be registered } InvRegistry.RegisterInterface(TypeInfo(ICrossPlatformSoapService)); {$ENDIF} end.Voor de ASP.NET versie zijn we dus alleen geïnteresseerd in de custom types, en niet in het ICrossPlatformSoapService interface of the initialization sectie.
ASMX
Er rest nog een ding: de ASP.NET code voor de web service ook daadwerkelijk als .asmx pagina aanbieden. Liefst zonder teveel moeite te hoeven doen, uiteraard. Daarvoor wil ik gebruik maken van Code Behind, door de implementatie van de web service als een .NET assembly beschikbaar te maken. Dat kan alleen maar door de unit header zodanig te veranderen dat we onder .NET een library (assembly) opleveren, en onder Win32 gewoon een unit. Dit heb ik als volgt gedaan:
{$IFDEF CLR} library CrossPlatformSoapServiceImpl; {$ELSE} unit CrossPlatformSoapServiceImpl; interface {$ENDIF}Ik kan nu CrossPlatformSoapServiceImpl.pas met dccil compileren tot een assembly, en deze in de bin directory van mijn scripts (virtual) directory op de web server neerzetten (lokaal is dat mijn C:\Inetpub\Scripts\bin directory).
<%@ WebService Class='CrossPlatformSoapServiceImpl.TCrossPlatformSoapService' %>
Als namespace de naam van de assembly, en als classname de naam van de web service zelf (nu nog met de T-prefix).
Deployment
De voorbeeld web service uit dit artikel is op mijn eBob42.com website terug te vinden (zodat je kunt zien dat ze allebei nog werken). De oorspronkelijke Delphi 7 Enterprise versie is te vinden als http://www.eBob42.com/cgi-bin/CrossPlatformSoapService.exe (met nog de default namespace):
Terwijl de ASP.NET versie als http://www.eBob42.com/cgi-bin/CrossPlatformSoapService.asmx te vinden is:
Wie nog vragen of opmerkingen heeft, kan die altijd per e-mail aan mij kwijt, of kon natuurlijk ook de Conference to the Point van vrijdag 12 december bezoeken, waar ik dit in praktijk heb laten zien (met een wat uitgebreider voorbeeld).
Wie op de hoogte wil blijven van de laatste ontwikkelingen zou daarnaast zeker eens moeten overwegen om mijn Delphi for .NET clinic bij te wonen.