In this tutorial, I'll show you one of the ways you can make use of a WebService in your ISAPI if you're using Delphi 5 Pro/Enterprise, or Delphi 6 Pro. We'll build a regular GUI application that uses a WebService as well as an ISAPI. The WebService in question sends emails. We'll also build an ISAPI that uses this same WebService as well as the other ISAPI to send emails. In other words, you have:
- A WebService that sends emails - (We won't be building this here).
- An ISAPI that sends emails.
- A Regular GUI Application that makes use of 1&2 above.
- An ISAPI that makes use of !&2 above.
To be able to use a WebService, you need to communicate with it the way it expects you to. Basically, you need to send a SOAP envelope to the WebService the way it expects to receive it and then get back the response and probably parse it out so you can get the actual reply that the WebService sent back. Since WebServices use HTTP we'll use the Indy TIdHTTP component to help with the HTTP protocol.
Since WebServices need to be sent a specific SOAP envelope, depending on the method and parameters etc. you'll obviously need to know what the request should look like. Most WebServices that have been listed do show you a sample SOAP request and response. If the WebService is in-house then it should be simple enough to ask the developer of the WebService for a sample packet. However you get this sample packet, just remember, that you will need to know what it looks like before you can use the WebService.

If you haven't already, take a look at the tutorial on my site called
Building WebService Clients by hand. This tutorial shows you how you can build a generic SOAP client. Using that application you should be able to see the SOAP request and response envelops for the WebService so you can communicate with it.
In this tutorial, we'll use the Send an Email WebService. You can get more details on this WebService from the tutorial - Building your First WebService. The idea being, that if a number of ISAPI's you've built need the ability to send an email or mass emails, then it's better you re-use this functionality in the form of either a WebService or a separate ISAPI.
The SOAP Envelopes
Lets take a look at what the SOAP envelopes look like for the Send an Email WebService.
The SOAP Request Envelope
POST http://matlus.matlus.com/scripts/emailwebservice.dll/soap/IEmailservice HTTP/1.0
Accept: application/octet-stream, text/xml
SOAPAction: "urn:EmailIPortTypeInft-IEmailService"
Content-Type: text/xml
User-Agent: Borland SOAP 1.1
Host: matlus.matlus.com
Content-Length: 759
Proxy-Connection: Keep-Alive
Pragma: no-cache
<?xml version="1.0" encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">
<SOAP-ENV:Body>
<NS1:SendMail xmlns:NS1="urn:EmailIPortTypeInft-IEmailService" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<NS1:ToAddress xsi:type="xsd:string">shivk@erols.com</NS1:ToAddress>
<NS1:FromAddress xsi:type="xsd:string">Shiv Kumar<shiv@matlus.com></NS1:FromAddress>
<NS1:ASubject xsi:type="xsd:string"> Testing</NS1:ASubject>
<NS1:MsgBody xsi:type="xsd:string">This is a TEST MESSAGE</NS1:MsgBody>
</NS1:SendMail>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The SOAP Response Envelope
HTTP/1.1 200 OK
Server: Microsoft-IIS/5.0
Date: Sat, 14 Jul 2001 07:35:44 GMT
Content-Type: text/xml
Content-Length: 531
Content:
<?xml version="1.0" encoding='UTF-8'?>
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:xsd="http://www.w3.org/1999/XMLSchema"
xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"
xmlns:SOAP-ENC=http://schemas.xmlsoap.org/soap/encoding/>
<SOAP-ENV:Body>
<NS1:SendMailResponse xmlns:NS1="urn:EmailIPortTypeInft-IEmailService" SOAP-ENV:encodingStyle=http://schemas.xmlsoap.org/soap/encoding/>
<NS1:return xsi:type="xsd:int">0</NS1:return>
</NS1:SendMailResponse>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>
The HTTP header part will be taken care of for us by the Indy TIdHTTP component. We'll need to modify the header a bit as I'll explain later. The Content part is simple XML. Do we need to use an XML parser to build the XML? You don't really need to. Basically, the XML remains the same; it's just the "parameters" that need to change. You're free to use a parser to create the XML document if you like, it's just that each time, you'll be building exactly the same document with changed parameters.
Building the EmailService ISAPI
Since we already have the WebService up and running, lets start with building the ISAPI that sends emails. You might ask why we're doing this. Re-use is one reason. The other reason is since ISAPI DLLs are multi-threaded, it makes sense to then make an ISAPI that can send emails for a number of applications or when you need to send mass emails. This method will be more reliable than the threaded email tutorial you might have read on my web site.
This ISAPI is a regular WebBroker ISAPI with one action. Its only job is to send emails. The code for the OnAction event for the default action of this ISAPI look like this:
The
OnAction event of the default Action
procedure TWebModule1.WebModule1WebActionItem1Action(Sender: TObject;
Request: TWebRequest; Response: TWebResponse; var Handled: Boolean);
var
IdMsg : TIdMessage;
begin
IdMsg := TIdMessage.Create(nil);
try
IdMsg.Body.Text := Request.ContentFields.Values['MessageBody'];
IdMsg.Recipients.EMailAddresses := Request.ContentFields.Values['ToAddress'];
IdMsg.Subject := Request.ContentFields.Values['Subject'];
IdMsg.From.Text := Request.ContentFields.Values['FromAddress'];
{ You don't want people replying, so I've set the address to their own address }
IdMsg.ReplyTo.EMailAddresses := Request.ContentFields.Values['FromAddress'];
IdSMTP1.Connect;
try
IdSMTP1.Send(IdMsg);
finally
IdSMTP1.Disconnect;
end;
finally
IdMsg.Free;
end;
end;

With the various version of Indy, the email address "styles" required have changed. Email Address (with names) may be in the format:
- Shiv Kumar <shivk@erols.com>
- Shiv Kumar<shivk@erols.com>
Notice that in the first case, there is a space between the name and email address part. Depending on your version of Indy, you may need to play with these formats to figure out what Indy requires.
The code is really very simple. There is nothing much to explain here. But notice that we're using Request.ContentFields so in other words, our client applications need to use the HTTP Post method. Note that the HTTP GET method has a limit to the amount of data that can be sent and so we use the POST method. Make a note of the "Form Fields" as well. In particular the field names.
To test this ISAPI without our clients (since we haven't built them yet), we can build a simple html form. This html form should look like this.
The Test HTML Page.
<html>
<head><title>Send an Email</title></head>
<body>
<form action="http://matlus.matlus.com/scripts/emailservice.dll" method="post" >
<table>
<tr>
<td>From</td><td><input type="text" style="width: 300px;" name="FromAddress" value="Shiv Kumar<shiv@matlus.com>" /></td>
</tr>
<tr>
<td>To</td><td><input type="text" style="width: 300px;" name="ToAddress" value="shivk@erols.com" /></td>
</tr>
<tr>
<td>Subject</td><td><input type="text" style="width: 300px;" name="Subject" value="Testing the Email Service ISAPI" /></td>
</tr>
<tr>
<td>Message</td><td><textarea name="MessageBody" cols="35" rows="10">This Email has been sent using a test HTML Form and the EmailService ISAPI</textarea></td>
</tr>
<tr>
<td><input type="submit" name="submit" value="Send Email" /></td><td><input type="reset" value="Reset" /></td>
</tr>
</table>
</form>
</body>
</html>
Figure 1: Showing the Test HTML Page
Make sure you change the action attribute to match your environment. Remember we're building this html form only so we can test our ISAPI out.
You'll notice also, that in the code, there is no exception handling. This is so that our client applications (both the ISAPI as well as the GUI) can get these exceptions and act on them as they see fit.
Building a GUI Client
Figure 2: Showing the GUI Client Application that can use a WebService Or ISAPI to send Emails.
The GUI Client application is built such that it can use either the WebService or the ISAPI. In fact, the same code, put inside an ISAPI will work just as well. Let's take a look at the code for the OnClick event of the Send Using ISAPI Button.
The
OnClick event of the
btnSendISAPI Button.
procedure TMainForm.btnSendISAPIClick(Sender: TObject);
var
RequestStrings: TStrings;
ResponseStrm: TStream;
ErrMessage: string;
begin
RequestStrings := TStringList.Create;
ResponseStrm := TMemoryStream.Create;
try
with IdHTTP1 do
begin
RequestStrings.Add(Format('FromAddress=%s', [edtFrom.Text]));
RequestStrings.Add(Format('ToAddress=%s', [edtTo.Text]));
RequestStrings.Add(Format('Subject=%s', [edtSubject.Text]));
RequestStrings.Add(Format('MessageBody=%s', [memBody.Lines.Text]));
Request.ContentType := 'application/x-www-form-urlencoded';
try
Post(edtHost.Text, RequestStrings, ResponseStrm);
{ We Assume that if there is no Exception then all went ok }
except
on E: EIdProtocolReplyError do
begin
ResponseStrm.Position := 0;
SetLength(ErrMessage, ResponseStrm.Size);
ResponseStrm.Read(ErrMessage[1], ResponseStrm.Size);
{ The Error Message is now in ErrMessage. You might want to log this or something instead }
ShowMessage(ErrMessage);
end
else raise;
end;
end;
finally
RequestStrings.Free;
ResponseStrm.Free;
end;
end;
You might have noticed from Figure 2 that we've got a TIdHTTP component dropped on the form. The ISAPI that we built in the previous step expects to see "form fields" that have particular names. To keep things simple, we use a StringList to construct the name=value pairs required.
A key point to note is that we have set the ContentType property of the IdHTTP component. If we don't do this, our ISAPI won't be able to recognize the form fields. This is the default ContentType of regular HTML forms. Actually, the <form> tag has an attribute called enctype that defaults to application/x-www-form-urlencoded. We also need to use the Post method of the IdHTTP component (this is by design). The exception handling is really simple. If you use this code inside an ISAPI, you may want to log errors to a file (if you're using this "service" for mass emails) or alter it to suite your specific needs. The GUI has a Host field that the IdHTTP component needs when using the Post or Get method.
At this stage, we have an EmailService ISAPI that can be used from either a regular GUI application or another ISAPI. We have a GUI application that uses this ISAPI to send emails. The code used here can be used inside an ISAPI as well. We won't build an ISAPI for this however.
Getting the GUI/ISAPI to use the WebService
The primary difference here is that the "Request" that the IdHTTP component sends out needs to be a well defined XML document in the form of a SOAP envelope. Earlier in this tutorial, you saw what the request and response SOAP envelopes should look like.
Below is the code for the OnClick event of the btnSenWebService button.
Showing the code for the
OnClick event of
btnSendWebService Button.
procedure TMainForm.btnSendWebServiceClick(Sender: TObject);
const
WebServiceHost = 'http://webservices.matlus.com/scripts/emailwebservice.dll/soap/IEmailservice';
SOAPEnvelope =
'<?xml version="1.0" encoding="UTF-8"?>'#13#10
'<SOAP-ENV:Envelope'#13#10
' xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/"'#13#10
' xmlns:xsd="http://www.w3.org/1999/XMLSchema"'#13#10
' xmlns:xsi="http://www.w3.org/1999/XMLSchema-instance"'#13#10
' xmlns:SOAP-ENC="http://schemas.xmlsoap.org/soap/encoding/">'#13#10
' <SOAP-ENV:Body>'#13#10
' <NS1:SendMail xmlns:NS1="urn:EmailIPortTypeInft-IEmailService" SOAP-ENV:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">'#13#10
' <NS1:ToAddress xsi:type="xsd:string">%s</NS1:ToAddress>'#13#10
' <NS1:FromAddress xsi:type="xsd:string">%s</NS1:FromAddress>'#13#10
' <NS1:ASubject xsi:type="xsd:string">%s</NS1:ASubject>'#13#10
' <NS1:MsgBody xsi:type="xsd:string">%s</NS1:MsgBody>'#13#10
' </NS1:SendMail>'#13#10
' </SOAP-ENV:Body>'#13#10
' </SOAP-ENV:Envelope>';
var
RequestStrm: TStream;
ResponseStrm: TStream;
sTemp: string;
sTo: string;
sFrom: string;
ErrMessage: string;
begin
RequestStrm := TMemoryStream.Create;
ResponseStrm := TMemoryStream.Create;
try
sTo := StringReplace(edtTo.Text, '<', '<', [rfReplaceAll]);
sTo := StringReplace(sTo, '>', '>', [rfReplaceAll]);
sFrom := StringReplace(edtFrom.Text, '<', '<', [rfReplaceAll]);
sFrom := StringReplace(sFrom, '>', '>', [rfReplaceAll]);
sTemp := Format(SOAPEnvelope, [sTo, sFrom, edtSubject.Text, memBody.Lines.Text]);
RequestStrm.write(sTemp[1], Length(sTemp));
RequestStrm.Position := 0;
with IdHTTP1 do
begin
try
Request.ExtraHeaders.Clear;
Request.ExtraHeaders.Add('SOAPAction: "urn:EmailIPortTypeInft-IEmailService"');
Request.ContentType := 'text/xml';
Request.UserAgent := 'Matlus SOAP v1.0';
Post(WebServiceHost, RequestStrm, ResponseStrm);
{ We Assume that if there is no Exception then all wen ok }
except
on E: EIdProtocolReplyError do
begin
ResponseStrm.Position := 0;
SetLength(ErrMessage, ResponseStrm.Size);
ResponseStrm.Read(ErrMessage[1], ResponseStrm.Size);
{ The Error Message is now in ErrMessage. You might want to log this or something instead }
ShowMessage(ErrMessage);
end
else raise;
end;
end;
finally
RequestStrm.Free;
ResponseStrm.Free;
end;
end;
A few things in the code listing above need mention. We've declared two constants. WebServiceHost and SOAPEnvelope. The SOAPEnvelope is exactly what was shown earlier towards the start of this tutorial, with a slight difference. We've put placeholders for the values of the required parameters such as ToAddress, FromAddress etc. We later use the Format function to replace the placeholders with actual values.
There are some other things going on in the code as well. The FromAddress and ToAddress may contain < & >. This won't work "as is", since we are essentially dealing with an XML document, and these special characters have a different meaning in terms of XML. To avoid problems, we replace these special characters with their encoded equivalents. This is done in the following lines of code.
sTo := StringReplace(edtTo.Text, '<', '<', [rfReplaceAll]);
sTo := StringReplace(sTo, '>', '>', [rfReplaceAll]);
sFrom := StringReplace(edtFrom.Text, '<', '<', [rfReplaceAll]);
sFrom := StringReplace(sFrom, '>', '>', [rfReplaceAll]);
sTemp := Format(SOAPEnvelope, [sTo, sFrom, edtSubject.Text, memBody.Lines.Text]);
Further down in the code, you should notice that we're making use of the ExtraHeaders property of the TIdHTTP component. This is as per the SOAP spec. For regular HTTP, this HTTP header is not required, but for SOAP Requests, we need this header. The ContentType needs to be changed specifically to text/xml since we are in fact sending an XML document.
Request.ExtraHeaders.Clear;
Request.ExtraHeaders.Add('SOAPAction: "urn:EmailIPortTypeInft-IEmailService"');
Request.ContentType := 'text/xml';

I'm not explaining the actual WebService side of things here. The reason is that all the things you need to know are explained in detail in the -
Building your First WebService tutorial. If you're not familiar with SOAP and WebServices, I strongly suggest you read all the WebService related articles/tutorials even if you're still using Delphi 5. A lot of the information in there will give you a good grounding with regard to SOAP/WebServices.