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;
implementationconst
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
Add a comment