Browse Archive

Newsletter

Please enter your e-mail address to subscribe to our newsletter.

Breaking news

Events

< February 2012 >
Mo Tu We Th Fr Sa Su
    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        

Webservices and Delphi 5

2008-05-27 15:54 by Ronald Klaui

Delphi 5 doesn't have native support for SOAP webservices. So accessing webservices created with ASP.NET for instance requires third party tools. Even though delphi 7 has some support for webservices, it seems to be incompatible with ASP.NET 2.0. Bob Swart has some info on this but I couldn't get it working in Delphi 7. Of course later versions work fine, but upgrading isn't an option in this project i'm working on.

I've put together a simple class that can call webservices and send/receive xml messages with Delphi 5 and the indy components.


Most of the time ASP.NET webservices just encapsulate a user defined object and make it available through serialization. Say you have the following .NET class and webservice.

<%@ webservice language="C#" class="SugarWebService.SimpleSOAP" %>

using System;
using System.Web.Services;
using System.Web.Services.Protocols;

namespace SugarWebService
{
public class Project
{
public string Name;
public string Description;
}

[WebService(Namespace="http://www.sugarcoated.nl")]
public class SimpleSOAP: System.Web.Services.WebService
{
[WebMethod(Description="Return a project")]
public Project getProject(Project project)
{
return project;
}
}
}

You can view the ASP.NET generated wrapper for this webservice here: http://www.sugarcoated.nl/webservices/simplesoap.asmx

The soap message required to access the getProject method are here: http://www.sugarcoated.nl/webservices/simplesoap.asmx?op=getProject

To call this method from delphi 5 we just need a tcp connection to the webserver and send the filled in soap message. I've used the v1.1 soap message. We wait for the server's response and make that response available as a xml document. To get your data from the webservice in your delphi classes you need to write some extra code. In a future article I will describe how to send SOAP messages from data in a dataset.

Here's the class that performs the server communication (below you can download the source):

unit USimpleSOAP;

interface

uses
SysUtils, Classes, IdBaseComponent, IdComponent, IdTCPConnection, IdTCPClient,
Dialogs;

type

TSimpleSOAP = class(TObject)
private
FURL: string;
FHostname: string;
FXSD_URL: string;
FTCPClient: TIdTCPClient;

function StripNonNumeric(AText: string): string;
function ReadLine: string;
public
constructor Create(
AOwner: TComponent;
const AURL, AHostname: string;
const AXSD_URL: string=''); reintroduce;
destructor Destroy(); override;

function Invoke(const ARequest, ASoapAction, AXMLData: string): string;
end;

implementation
const

SOAP_REQUEST =
'POST %s HTTP/1.1'#13#10+ // %s for example: /webservices/acmeWebService.asmx
'Content-Type: text/xml; charset=utf-8'#13#10+
'SOAPAction: "%s"'#13#10+ // %s for example: http://www.acme.nl/wsdl/AcmeWebService.wsdl:updateArtikelen
'Host: %s'#13#10+ // %s for example: www.acme.nl
'Content-Length: %d'#13#10+ // %d size of xml data
'Expect: 100-continue'#13#10#13#10; // send double cr/lf

SOAP_XML_ENVELOPE =
'<?xml version="1.0" encoding="utf-16"?>'+
'<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">'+
'<soap:Body>'+
'<%s '+ // %s for example: UpdateArtikelenRequest
'xmlns="%s">'+ // optional %s for example: http://www.acme.nl/xsd/WebMessages.xsd
'%s'+ // put xml data here
'</%s>'+ // same %s as UpdateArtikelenRequest
'</soap:Body>'+
'</soap:Envelope>';

SOAP_REQUEST_ACCEPT =
'HTTP/1.1 100';

SOAP_DATA_OK =
'HTTP/1.1 200';

SOAP_ERROR =
'HTTP/1.1 500';

CRLFCRLF = #13#10#13#10;

CONTENT_LENGTH = 'Content-Length';


{ TSimpleSOAP }

constructor TSimpleSOAP.Create(
AOwner: TComponent;
const AURL, AHostname: string; const AXSD_URL: string='');
begin
Assert(AURL <> '');
Assert(AHostName <> '');

FURL := AURL;
FHostName := AHostname;
FXSD_URL := AXSD_URL;

FTCPClient := TIdTCPClient.Create(AOwner);
FTCPClient.Host := AHostName;
FTCPClient.Port := 80; // ATCPPort;
end;

destructor TSimpleSOAP.Destroy;
begin
FreeAndNil(FTCPClient);

inherited;
end;

function TSimpleSOAP.Invoke(const ARequest, ASoapAction, AXMLData: string): string;
var
vSoapHeader : string;
vSoapData : string;
vLine : string;
vStatus : string;
vLength : integer;
begin
Assert(ARequest <> '');
Assert(ASoapAction <> '');
Assert(AXMLData <> '');

Result := '';
// tcp connect
FTCPClient.Connect();
try
// create data first and then header
vSoapData := Format(SOAP_XML_ENVELOPE,[ARequest, FXSD_URL, AXMLData, ARequest]);
vSoapHeader := Format(SOAP_REQUEST,[FURL, ASoapAction, FHostName, Length(vSoapData)]);

// write the Soap request
FTCPClient.WriteBuffer(vSoapHeader[1], Length(vSoapHeader));

// Get server response
vLine := ReadLine;

// Check if server responded with 100 continue
if (AnsiPos(SOAP_REQUEST_ACCEPT, vLine) > 0) then begin

// Clear any messages that are still queued
while (vLine <> '') do
vLine := FTCPClient.ReadLn(#10,-1, 100000);

// now write the xml data
FTCPClient.WriteBuffer(vSoapData[1], Length(vSoapData));

// Get entire server response
vLine := ReadLine;

// Check if server responded with 200 OK
if (AnsiPos(SOAP_DATA_OK, vLine) > 0) then begin

// now read the headers and find the ContentLength
vLine := '';
while (AnsiPos(CONTENT_LENGTH, vLine) = 0) do
vLine := ReadLine;

// vline now contains the length, strip to integer
vLength := StrToInt(StripNonNumeric(vLine));

// now read until a blank line is encountered
while (vLine <> '') do
vLine := FTCPClient.ReadLn(#10,-1, 100000);

// now we can read vLength chars
Result := FTCPClient.ReadString(vLength);
end else begin
// error occured, we want to know what happened so read the rest of the
// server soap response
if (AnsiPos(SOAP_ERROR, vLine) > 0) then begin
vStatus := vLine;
// now read the headers and find the ContentLength
vLine := '';
while (AnsiPos(CONTENT_LENGTH, vLine) = 0) do
vLine := ReadLine;

// vline now contains the length, strip to integer
vLength := StrToInt(StripNonNumeric(vLine));

// now read until a blank line is encountered
while (vLine <> '') do
vLine := FTCPClient.ReadLn(#10,-1, 100000);

// now we can read vLength chars
Result := FTCPClient.ReadString(vLength);
raise Exception.Create('TSimpleSoap error2: SOAP Data sended, server responded: '+ vStatus+#13#10+'"'+Result+'"');
end else begin
raise Exception.Create('TSimpleSoap error3: SOAP Data sended, server responded: '+ vLine);
end;
end;

end else begin
raise Exception.Create('TSimpleSoap error1: SOAP Request sended, server responded: '+ vLine);
end;
finally
FTCPClient.Disconnect;
end;
end;

function TSimpleSOAP.StripNonNumeric(AText: string): string;
var
i : integer;
begin
result := '';
for i := 1 to Length(AText) do begin
if AText[i] in ['0','1','2','3','4','5','6','7','8','9'] then begin
result := result + AText[i];
end;
end;
end;

function TSimpleSOAP.ReadLine: string;
begin
result := '';
while (result = '') do
result := FTCPClient.ReadLn(#10,-1, 100000);
end;

end.

Now here's a code snippet that calls the getProject webmethod described above:

procedure TForm1.Button1Click(Sender: TObject);
var
vXMLData : string;
vXML : TStringList;
vSimpleSoap : TSimpleSOAP;
begin
vXMLData := '<project><Name>MyProject</Name><Description>MyProjectDescription</Description></project>';
vXML := TStringList.Create;
vSimpleSoap := TSimpleSOAP.Create(Application,
'/webservices/projectservice.asmx',
'www.sugarcoated.nl',
'http://www.sugarcoated.nl');
try
vXML.Text := vSimpleSoap.Invoke(
'getProject',
'http://www.sugarcoated.nl/getProject',
vXMLData);
ShowMesage(vXML.Text);
finally
vXML.Free;
vSimpleSoap.Free;
end;
end;

When executing the function above you get a messagebox with the return xml. Now you can parse the string for the data you want or put the string in a xmldocument.

Download the source soaptest.zip

Go back

Add a comment