ExposureRoom Home
  Log in Sign Up
Shiv's Website
Shiv Kumar
United States
Friends: 70
Focused on : 2
 
       
                                                             

Building your First WebService in Delphi

Not Rated YetNot Rated YetNot Rated YetNot Rated YetNot Rated Yet0votes
January 23, 2008 10:10 AM  Views:916   Favorited:0 Comments:4
Filed Under:  Programming
Tags:  Delphi, SOAP, WebServices
 
If you've been reading the Webservice articles on this site in the order that they are listed, then this is your first Webservice. As WebServices go, this is not the simplest of WebServices, but my thinking was that I could take you through the paces of building a Webservice that addresses some of things you'll need to know when building "real world" WebServices while at the same time not overwhelm you with all these new things. If you haven't read the previous articles, I strongly suggest you do. This tutorial assumes you have read those articles or know about the things that were discussed there.

In this tutorial, we'll start with building a WebService client for an existing WebService. Then we'll build the WebService itself. This way, you don't have to get both projects up and running before you can have any fun at all.

Building the WebService Client

So lets start by building a WebService client for an existing Webservice. Just to get our feet wet as it were. If you go to Xmethods.com you'll see there are a number of WebServices people have posted links for. We'll use one of these services for starters.

You might notice that I'm being partial here. I've chosen the Send an Email WebService. We'll write a simple client for this WebService. Before we can begin to use any WebService, we need to have the WSDL file. With Delphi, you simply need the URL to a WSDL file. You could use a physical WSDL file as well, but the Expert available to us, allows us to supply the URL to a WSDL file so we'll use this capability. The expert will generate the Interface files for us.

WSDL File

Let's take a brief look at the WSDL file. The URL for the WSDL file is http://webservices.matlus.com/scripts/emailwebservice.dll/wsdl/IemailService. If you're using I.E, you can click on this link to see the WSDL file inside your browser. Without going into too many details (the WSDL spec. can be quite complex for the faint hearted), lets take a look at some of the elements you should be aware of.

  1. The <message> element. The name attribute contains the name of the method with the word Request added to it. This is part of the spec. This is not the element that really tells you the name of the method however. That comes later
  2. the <part> element's name attribute tells you the name of the parameters while the type attribute tells you the type of the parameters.
  3. Next, take a look at the next <message> element. The name attribute has the name of the method with the word Response at the end of it. Once again, this is as per spec.
  4. Under this element the <part> element's name attribute tells you what is being returned and the type attribute tells you the type of this return value.
  5. The next important element is the <operation> element. This is the element that really gives you the remote method name. In this case SendMail.
  6. The <soap:operation> element has an attribute called soapAction. You'll see this mentioned once again later in this tutorial. The value of this attribute is generated for you automatically by the Delphi 6 SOAP framework. In particular by the TWSDLHTMLPublish component at the server end. This is the component that actually generated this WSDL file in the first place.(if you examine the URL for this WSDL file, you'll notice that it points to an action in the ISAPI.)
  7. The <service> element and it's name attribute are the next elements to look at. We'll see these again when building the client later. In particular, when populating the THTTPRIO component's Service and Port properties.

That's probably some of the more important things to look for in a WSDL file when building a Webservice client. Of course, things can get a bit funky when you run into problems with namespaces etc. when dealing with services that were either built according to an older or newer SOAP specification, or different development tool. These are some of the issues you're going to have to live with when living on the bleeding edge of technology.

I'd also like to take this opportunity to highlight another aspect that relates to XML. Now you don't need to get too involved with this at this point in time. It'll all come together soon enough. So if you don't understand it now, it's ok. For the moment there is no real need to have to understand it. I'm talking about Namespaces. Any good book on XML will give you a more detailed explanation. However, I'll explain things as they relate to SOAP and in particular the WSDL file in question. Right at the start of this WSDL file, you'll see some namespaces being declared. In I.E. they will be highlighted in red (You must have wondered about this when you first show the WSDL file right?). Namespaces are declared as attributes of an element. In this case the <definitions> element like

xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 

This declaration now gives us a namespace called soap. Further down in the WSDL file there are a few elements that start with the word soap. For example, <soap:operation>. This is really the <operation> element in the soap namespace. You can tell this by the virtue of the fact that there is a : (colon) between the word soap and operation. Hope that makes some sense at this point.

WebService Client Project

  1. Start a new project
  2. Then File | New | Other
  3. In the New Items dialog, choose the WebServices tab
  4. Choose the WebServices Importer
  5. Click OK


Figure 1: Selecting the WebServices Importer

This should show you the Wizard.


Figure 2: Showing the WebService Importer Wizard

In the WSDL Location field, type in the URL for the WSDL File for the Send an Email WebService. This should be http://webservices.matlus.com/scripts/emailwebservice.dll/wsdl/IEmailService. Once you've done that (make sure the URL you type in is exactly as shown), click on the Generate button. Delphi will now create a new unit for you and the Interface declaration for the Send an Email WebService. This unit will look like this:

Unit Unit2;

interface

uses Types, XSBuiltIns;
type

  IEmailService = interface(IInvokable)
    ['{C355CCC3-4CD4-4577-A0D2-88FBB9B2801E}']
    function SendMail(const ToAddress: WideString; const FromAddress: WideString; const ASubject: WideString; const MsgBody: WideString): Integer;  stdcall;
  end;


implementation

uses InvokeRegistry;

initialization
  InvRegistry.RegisterInterface(TypeInfo(IEmailService), 'urn:EmailIPortTypeInft-IEmailService', '');

end.

From the code listing above, you should see that the interface is called IEmailService and that is has one method (a function in this case) called SendMail. This method, expects to see the parameters required to send an email. What is interesting to note is that this Interface is derived from an interface called IInvokable. This is new to Delphi 6. IInvokable is almost exactly the same as IInterface (or IUnknown) which is the base type for all Delphi Interfaces. The key difference is that it is compiled with Run time Type information using the {$M } compiler directive. I mentioned in the previous articles, the Delphi SOAP implementation uses RTTI and Interfaces. This is one part of that equation.

The next thing to note is the initialization section of the unit that was generated. The invocation registry is again new to Delphi 6. The Delphi 6 SOAP framework uses this "registry" to be able to automatically instantiate instances of Invokable classes (classes that implement interfaces derived from IInvokable) and Remoteable classes (we'll get to these in a later tutorial). Needless to say, that if you don't register things that need to be registered, your WebServices will not work. The Online help has a decent explanation of the RegisterInterface method and the parameters expected (most of them are optional). Also notice the stdcall directive. Currently, the Delphi default calling convention (register) is not yet supported. You could use any other calling convention that Delphi supports, such as cdecl, pascal, or safecall. It is not recommended that you use safecall since safecall calls are wrapped with an exception handler by the compiler and this may swallow more exceptions than you'd want to. The default is to use stdcall so I suggest you stick with this.

Lets switch to the main form of the application and drop down a THTTPRIO component on the form. You'll find this component in the WebServices palette. This component has 4 key properties.

  1. WSDLLocation
  2. Service
  3. Port
  4. URL

You either use the WSDLLocation, Service and Port properties or just the URL property.

Given a WSDLLocation (URL) the THTTPRIO component is capable of parsing out the WSDL file and populating the Service and Port property dropdown lists. So lets fill in the WSDLLocation with the same URL we used earlier. Now drop down the Service property's combo box and select IEmailServiceservice. Do the same for the Port property and select IEmailServicePort.


Figure 3: Showing the Object Inspector with the THTTPRIO component's properties populated.

Looking at the parameters required by the SendMail method of the IEmailService, I've built a simple GUI with the required fields as show in Figure 3 below.


Figure 4: Showing the simple GUI for the Send an Email WebService

The Code

I've named the unit that Delphi created as EmailIPortTypeInft. You don't need to name it the same, but you do need to add this unit to the uses clause of the implementation section of your form's unit. Once you do this, in the OnClick event of the Send button you need to write some code.

unit uClientMain;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, Rio, SoapHTTPClient, ComCtrls;

type
  TForm1 = class(TForm)
    HTTPRIO1: THTTPRIO;
    Label1: TLabel;
    Button1: TButton;
    txtToAddress: TEdit;
    Label2: TLabel;
    txtFromAddress: TEdit;
    Label3: TLabel;
    txtSubject: TEdit;
    MemMessageBody: TMemo;
    Label4: TLabel;
    StatusBar1: TStatusBar;
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
 EmailIPortTypeInft;

procedure TForm1.Button1Click(Sender: TObject);
begin
  Screen.Cursor := crHourGlass;
  StatusBar1.SimpleText := 'Sending Email....';
  StatusBar1.Refresh;
  try
    if (HTTPRIO1 as IEmailService).SendMail(txtToAddress.Text, txtFromAddress.Text,
      txtSubject.Text, MemMessageBody.Lines.Text) <> 0 then
      StatusBar1.SimpleText := 'Error Sending Email.'
    else
      StatusBar1.SimpleText := 'Email Sent.';
    StatusBar1.Refresh;
  finally
    Screen.Cursor := crDefault;
  end;
end;


end.

Basically, you really need just one line of code. Most of the code you see in the OnClick event is really related to GUI frills. The one line of code that really does the work is:

    if (HTTPRIO1 as IEmailService).SendMail(txtToAddress.Text, txtFromAddress.Text,
      txtSubject.Text, MemMessageBody.Lines.Text) <> 0 then

If you run this application and fill in the values to send yourself an email and hit the send button, you'll receive and email if all goes well.

Behind the Scenes

So what's happening? First of all, you're using a WebService that is already there for you to use. Because the service exists, you can use it without the need to build it yourself. This is one the reasons that SOAP is so exciting. SOAP has added another dimension to the word "re-usability".

Behind the scenes, the THTTPRIO component has built a SOAP envelope for you and sent it over to the WebService server. The server knows what to do with this SOAP envelop. This SOAP envelope (for those interested) looks like this:

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>
Let me highlight some of the things you might want/need to know.
  1. In the HTTP header take a look at the URL that was constructed - http://matlus.matlus.com/scripts/emailwebservice.dll/soap/Iemailservice. What is key here is the part after the emailwebservice.dll. On the server side, when the Service receives this Request a component called THTTPSOAPDispatcher fields this request (because it has PathInfo /soap). It then hands it over to another component called THTTPPascalInvoker. This component looks up the Invocation Registry and tries to match the name of the interface (IEmailService). If it finds a match, it instantiates an instance of the class that implements this interface. We'll get to this when we build the server side. But you should know, that this is done for you automatically by the Delphi 6 SOAP framework.
  2. The SOAPAction HTTP header. This is a new HTTP header required for SOAP calls. This is one of the headers that a firewall/proxy can look for to either allow/disallow SOAP calls past it for security reasons. Of course, the SOAP spec. does not make this mandatory. I would guess that in time this header will be mandatory.
  3. Next, within the XML Document (SOAP envelope) you should see the name of the method.
  4. You should also see the parameters and values to these parameters that are being sent to the server.

The server processed this packet and responded with another SOAP packet that looks like this

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>
After looking at all of this, I'm sure you'll appreciate the fact that the Delphi 6 SOAP framework hides the HTTP and XML details from you and does all of the hard work while you wrote a simple line of code!

If you really want to know what all the XML stuff is you'll need to go deeper into the SOAP spec and XML. If you want to be a good SOAP programmer you can't avoid this. For now however, we don't need to know.

What if this WebService was written in Java or C ? How would they know what to do with such as request? In particular, how do they treat IEmailService? As I mentioned in the previous articles, Delphi 6's SOAP implementation uses Interfaces. Other SOAP tool kits don't have to. So they will understand this request the way They need to. SOAP is an implementation and platform agnostic protocol. With WebServices it doesn't matter what tool the service is written in or what platform its running on. Well, this is the ideal scenario. Currently, there are interoperability issues, but we'll get there one day I'm sure. For the most part, things should work just fine.

With the knowledge you've gained so far, you should be able to experiment with other WebServices listed on the XMethods web site. I suggest you do this and then come back here to learn how to build the server for this WebService.

THTTPRIO Explained

When building a WebService client, the THTTPRIO component is the key component. Therefore, I'd like to explain some of the properties of this component, while we're still playing with the client side. Take a look at Figure 5


Figure 5: Showing the Properties of THTTPRIO

We've used the WSDLLocation property, the Service property and the Port. Keep in mind, that for you to be able to use the WSDLLocation in design time (or run time) you need access to the Internet if your WSDLLocation is a URL. Also, you use the Service and Port properties only when using the WSDLLocation property. Instead of using the WSDLLocation property, you could use the URL property. When using the URL property, you don't need (and shouldn't) use the Service and Port properties. The value of the URL property will not be the same as the WSDLLocation. You need to use the proper value in this case. This is the SOAP Endpoint URL. When looking at SOAP services that listed at sites such as XMethods or SALCentral, you will see the SOAP Endpoint URL listed along with the WSDL file. The THTTPRIO component gets the WSDL file at run time, parses it out and finds the SOAP Endpoint URL in the WSDL File. So it goes without saying, that using the URL property will be slightly faster than using the WSDLLocation property since some round trips are saved. Keep this in mind, if you are making repeated calls to a certain WebService in your applications. Another interesting point to note is that the URL for the WSDL file and the SOAP Endpoint (only for Delphi 6 build WebServices) have a very small difference. The word wsdl is replaced with the word soap. Note that this is by default and that you have the option to change this.

What if you are behind a proxy/firewall either at home or work? Well, then you're going to have some issues that you'll need to circumvent. You won't be able to use the WebService Importer as I explained earlier. What you'll have to do is use your browser to get to the WSDL File. Save it locally, and then use this file (you can use the Browse button) to fill in the WSDLLocation field in the wizard. Once you've done this and the interface file(s) is created for you, your next issue will be when you attempt to fill in the properties of the THTTPRIO component. First, if you look at the properties shown in Figure 5, you'll see that the THTTPRIO component has a Proxy property as well as UserName and Password properties. You'll need to get the details from your Network administrator if you don't know what to fill in here. As an example, if you have a proxy server called MyProxy that uses port 8080, then the Proxy property needs to be filled in as MyProxy:8080. As explained earlier, I suggest you use the URL property instead of the WSDLLocation. If you do plan to use the WSDLLocation property, you need to use the file path and name instead of the URL to this file and most probably, when you deploy your client application, it's going to then need to find this WSDL file as well.

The Agent property is another interesting property. If you look at the HTTP header that was shown earlier, you'll see that the User-Agent HTTP header was Borland SOAP 1.1. Now you're free to change this to whatever else you'd like. I normally use the User-Agent HTTP header to distinguish my client applications from others. If you have to deal with security (proxies/firewalls), the User-Agent HTTP header can come in handy to allow for further filtering. For example, you can tell you network security guys, to allow HTTP requests that have a Content-Type of text/xml ONLY if the User-Agent is xyz and the SOAPAction header is abc. Etc.

The THTTPRIO component can be lifetime managed as a component or as an Interfaced object. It is managed, as a component when its owner is not nil. If you drop it on a form, its owner is the form and so it is managed as a component. This becomes important to know in the following case:

In the OnClick event of code listed above, you'll notice that we've not declared a variable of the type IEmailService. But what if we needed to do this? What if we needed a global variable of this type in our application? Well, we declare a variable of this type in the private section and use it in the OnClick event and elsewhere in our code. When we do this, we need to be sure to nil out this variable in the destructor of the form (or some other appropriate place). The code below shows and example of this.

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    EmailService: IEmailService;
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
  EmailService := HTTPRIO1 as IEmailService;
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  EmailService := nil;
end;

end.

Building the Send an EMail WebService Server

Before we begin with building the Send an Email Webservice, let me take you through the paces of what we will be doing.

We're going to start a new SOAP Server Application using the Wizard. This essentially produces a Web server extension (CGI, ISAPI etc.) that contains a Web Module and some components on it. This is really very similar to a Web Broker application. We'll choose the ISAPI option.

exclamation.gif If you're not familiar with ISAPI applications, I suggest you look at the tutorials on my web site related to ISAPI applications. The WebServices you'll build with the help of tutorials on this web site will mostly be ISAPI applications. Your knowledge of ISAPI Applications/debuggin etc. will be leveraged here. If you don't have experience building ISAPI applications, debugging etc. I strongly suggest you read the tutorials on my web site for this before you attempt to build WebServices. This way you'll have a firmer grasp of what is going on and this will save you (and me ) endless hours of frustration.

I assume you know ISAPI application development and as a result, you will find that I do not explain ISAPI related concepts in any of the WebService tutorials.

The standard SOAP Server application has three components on it.

  1. THTTPSOAPDispatcher
  2. THTTPSOAPPascalInvoker
  3. TWSDLHTMLPublish

The THTTPSOAPDispatcher dispatches all requests that have /soap in the PathInfo. It then hands the request over to THTTPSOAPPascalInvoker. This component really handles the bulk of the process. What is this process? Well, If you take a look at the HTTP request header from earlier, you see, that the full request looks like this:


http://matlus.matlus.com/scripts/emailwebservice.dll/soap/IEmailservice

The PathInfo part of this request is:

/soap/IEmailservice

You learned earlier, that the THTTPSOAPDispatcher handles all requests, with /soap in it and hands it over to the THTTPSOAPPascalInvoker. So the THTTPSOAPPascalInvoker has to do something with /IEMailservice. So what does it do? It looks up the Invocation Registry for any registered Interfaces that match the name IEMailservice. It also parses out the content of the web request (this is the SOAP Packet from earlier) and finds the name of the method and the parameters. Once it has done that, it instantiates an instance of the class that implements this interface and calls the required method handing it the parameters it received. Remember that it's able to do all this using RTTI for interfaces (new to Delphi 6) and some old RTTI magic. Since it receives all this information in the form of a string, it is converting these strings into method calls that instantiate an instance of a class and call the required methods (very much like a scripting engine). When you build a WebService, it is your job to:

  1. Define an Interface.
  2. Implement this interface in a class.
  3. Register the interface and class with the invocation registry.

That's it. The Delphi 6 SOAP framework handles the rest of it for you. What's the rest of it you might ask?

  1. Parsing the SOAP envelope.
  2. Instantiating the required class.
  3. Calling the required method and passing the received (parsed) parameters to it
  4. Getting back a result from the method.
  5. Putting the result back in a SOAP response envelope and sending it back out.
  6. If there is an exception it constructs a SOAP Fault packet instead

To put it in really simple terms!

I hope this short explanation has cleared things up a bit. If it has, then we're ready to move on and start with building our first Delphi 6 WebService.

The SOAP Server Application

  1. From the Delphi main menu, choose File | New | Other
  2. Choose the WebServices tab and choose the SOAP server Application Expert
  3. In the next dialog, leave the ISAPI/NSAPI (this is the default) Dynamic Link Library option selected and Click OK.

Figure 6: Showing the Steps involved in Creating a New SOAP Server Application
After doing these steps, you should see something like the last image in Figure 6. I suggest you save this project as EmailWebService and the WebModule unit as MainU.pas.

exclamation.gif Note: I assume at this point that you know about building ISAPI Applications and debugging them etc. I keep stressing this because I don't want to be flooded with emails asking me questions about WebServices that really pertain to ISAPI applications. I believe the information available on my web site related to ISAPI is more than enough to help you get started with ISAPI applications and get to the point where you can now build WebServices.

Defining the Interface

If you've registered Delphi 6, you should then have access to some of the power toys that will be made available to you. At the time of this writing, I believe they are not yet available. Anyhow, I'll use (one of these toys) the Invokable Wizard that will help in building the WebService. Don't worry, if you don't have this wizard. What it does is it creates two new units for you, one that defines the Interface and the other that defines a class that implements this interface. I'll show you the full units, so you can create them by hand if required. Why have the interface defined in one unit and the implementation in another? Technically, there is obviously no need for this. But if you're building a client for the WebService as well (and 99% of the time you will, only if it's to test the WebService), you can include the unit that declares your interface in the client application's project as well. This will eliminate the need to import the WSDL file (one of the steps we went through while building the client). However, if you want to test your WebService completely, you're better off building the client as we did earlier. That is, treat the client as an independent part of your system, so you know that others attempting to use the WebService will be able to.

The Invokable Wizard can be found in the New Items dialog (File | New | Other | WebServices) as shown in Figure 7 below.


Figure 7: Showing the Invokable Wizard

As Shown in Figure 8 below, the Invokable Wizard presents you with another dialog.


Figure 8: Showing the dialog the Invokable Wizard presents you with.

I've filled in the values as shown there. You'll notice that the InvokableClass field has a combo box. I've chosen TInvokableClass. The other option is TInterfacedObject. There is a subtle difference between these two. TInvokableClass is derived from TInterfacedObject so it implements QueryInterface, AddRef and ReleaseRef. But it also has a virtual constructor. The fact that it has a virtual constructor allows the compiler to create an instance of the class using RTTI. If you need to derive from TInterfacedObject for some reason, you'll have to implement one extra step in your code (Ill show you that later) and register the class differently (I'll show you that too). All this, so the compiler can instantiate an instance of your class on the fly (That is THTTPSOAPPascalInvoker can instantiate an instance as was described earlier).

The two units thus created are shown below. The first one is the unit that declares the Interface - IEmailService and the next one declares the class that will implement this interface - TEmailService.

The unit that declares the IEMailService Interface
unit EMailServiceIntf;

interface

uses
  Types, XSBuiltIns;

type
  IEMailService = interface(IInvokable)
    ['{A987FE00-E596-4733-83BB-E75ACA5FF363}']
  end;

implementation

uses
  InvokeRegistry;

initialization
  InvRegistry.RegisterInterface(TypeInfo(IEMailService), '', '');

end.

The unit that declares the TEMailService class
unit EMailServiceImpl;

interface

uses
  EMailServiceIntf, InvokeRegistry;

type
  TEMailService = class(TInvokableClass, IEMailService)
  end;

implementation

initialization
  InvRegistry.RegisterInvokableClass(TEMailService);

end.

Make a note of the units in the uses clause of both these units and the initialization section as well. If you're building these units, by hand, I suggest you do so at this point looking at the above listings.

To understand the "Registration" need, I suggest you look at the on-line help for more information. Notice also, that the unit that declares the Interface is very similar to the one that was generated for us using the WebService Importer wizard back when we started with building the client application. Minus the SendMail method and GUID obviously.

Lets go ahead and define the SendMail method as shown in the listing below.

unit EMailServiceIntf;

interface
uses
  Types, XSBuiltIns;

type
  IEmailService = interface(IInvokable)
  ['{C56A3546-DA66-492F-9024-D78D960AB248}']
    function SendMail(ToAddress, FromAddress, ASubject, MsgBody: string): Integer; stdcall;
  end;

implementation

uses
  InvokeRegistry;

initialization
  InvRegistry.RegisterInterface(TypeInfo(IEmailService));


end.

Save these units using the names shown (these are the names generated for you by the Wizard if you used it). New to Delphi 6 is the ability to use Code Insight to fill in the methods of an interface in the class that implements the interface. You can even multi-select the methods shown to get Code Insight to write them all out for you as they were declared in the Interface. Note that the units need to be saved for this to work.

So switch to the unit that declares the class the implements the IEMailService, move you cursor under the "T" of TEMailService and hit CTRL Space. You should see the SendMail method listed in the code completion drop down. Select it and hit the Enter key and you'll see that Delphi does the rest for you. Now do the class completion (CTRL SHIFT C)

We're using Indy to do the sending of emails. You'll notice that Indy is now a part of Delphi 6. Those of you who have not used Indy yet, will obviously need to get to know how to use them. Our implementation of sending an email is nothing special, except for the fact that all objects are being created on the fly. You will also need to add the required units to the uses clause of your units. The listing below shows the full unit for the TEmailService class. Remember, this is the class that implements the interface IEmailService.

unit EMailServiceImpl;

interface

uses
  InvokeRegistry, XSBuiltIns, EmailIServiceInft, IdBaseComponent, IdComponent,
  IdTCPConnection, IdTCPClient, IdMessageClient, IdSMTP, IdMessage;

type
  TEmailService = class(TInvokableClass, IEmailService)
  public
    function SendMail(ToAddress: String; FromAddress: String;
      ASubject: String; MsgBody: String): Integer; stdcall;
  end;

implementation

{ TEmailService }

function TEmailService.SendMail(ToAddress, FromAddress, ASubject,
  MsgBody: String): Integer;
const
  SMTPServer = 'smtp-server'; { Change this to your SMTP server }
var
  IdSMTP: TIdSMTP;
  IdSendMsg: TIdMessage;
begin
  IdSMTP := TIdSMTP.Create(nil);
  IdSendMsg := TIdMessage.Create(IdSMTP);
  try
   Result := -1;
   with IdSendMsg do
   begin
     Body.Text := MsgBody;
     From.Text := FromAddress;
     Recipients.EMailAddresses := ToAddress;
     Subject := ASubject;
   end; { with IdSendMsg do }

   with IdSMTP do
   begin
     Host := SMTPServer;
     Connect;
     try
       Send(IdSendMsg);
       Result := 0;
     finally
       Disconnect;
     end;
   end; { with IdSMTP do }
  finally
    IdSendMsg.Free;
    IdSMTP.Free;
  end; { try }
end;

initialization
  InvRegistry.RegisterInvokableClass(TEmailService);

end.

Add these two units to your project and we're almost done now. Pretty simple so far huh? The last thing that's left is getting the service application to respond to SOAP calls and publish the WSDL file. Remember, without a WSDL file, your WebService is practically useless to anyone else.

TWSDLPublish

What we need to do is select the TWSDLPublish component on the WebModule and set its AdminEnabled property to true. Now run your application and using a browser, navigate to this URL http://matlus.matlus.com/scripts/EmailWebService.dll/wsdl. Change the domain and path according to your setup. Just be sure you put the /wsdl as a path of the final URL. You should see a screen similar to that shown in Figure 9.


Figure 9: Showing the WSDL Administration Page

If you had not set the AdminEnabled property to True, you wouldn't see the Administrator button. Click on the WSDL for IEmailService link. You should now see a WSDL file in your browser. Scroll to the end of the document. You should see that the <service> element is an empty tag (as per XML definition). If you look at the WSDL file at the URL specified at the start of this tutorial, you should notice that the <service> element had a child element called <port> and the <port> element had a <soap:address> element as a child like so:

<port name="IEmailServicePort" binding="IEmailServicebinding">
  <soap:address location="http://matlus.matlus.com/scripts/EmailWebservice.dll/soap/IEmailService" />

We need to define the Port for our service. We do that by clicking on the Administrator button and then on the WSDL for IEMailService link. You should see a screen that looks like that shown in Figure 10.


Figure 10: Showing the Admin Screen that allows for registering a Port for the WebService

In the PortName field enter IEmailServicePort and in the Address field enter http://matlus.matlus.com/scripts/EmailWebservice.dll/soap/IEMailservice. Change the domain name as per your set up. Click the Add button. You should see a screen that looks like that shown in Figure 11.


Figure 11: Showing the Admin Screen that allows for registering a Port for the WebService

Hit the back button of your browser twice. This should show the URL http://matlus.matlus.com/scripts/EmailWebService.dll/wsdl in your browser. Now if you click on the WSDL for IEMailService link and check the WSDL file thus generated, the <service> and <port> elements are as they should be. You should note also, that this operation has generated an .ini file in the /scripts folder (the same folder in which your DLL is installed. This .ini file will have the same name as your ISAPI dll with the words _WSDLADMIN.INI appended to it. The contents of this file should be something like this:


[IWSDLPublish]
IWSDLPublishPort=http://matlus.matlus.com/scripts/EmailWebservice.dll/soap/IWSDLPublish

[IEmailService]
IEmailServicePort=http://matlus.matlus.com /scripts/EmailWebservice.dll/soap/IEmailService

When you deploy your WebService to your production server, you either need to go through these steps again, or simply copy this .ini file over to the production server and change the domain name parts in both sections to match that of your production server. Do not forget this step. Without this step, your WebService will be quite un-usable.

TInvokableClass or TInterfacedObject

Earlier, I mentioned that if you decide to use TInterfacedObject as the class to derive your implementation class, you'll need to do things differently. Here are the differences and why you need to do them. TInterfacedObject does not have a virtual constructor declared. As a result, there is no way for your application to instantiate an instance of your class at run time when it gets a SOAP request to do so. As usual, there is always an option with Delphi. (These guys think of everything). What you need to do is implement a "Factory" method for your class. This method needs to return an instance of your class as an out parameter. Further, for the run time to figure it out, you need to register this method when registering the class with the invocation registry so that the application can call this method to get back an instance of your class. This is how you should implement this method and register it.

procedure EMailServiceFactory(out obj: TObject);
begin
  obj := TEmailService.Create;
end;

initialization
  InvRegistry.RegisterInvokableClass(TEMailService, EMailServiceFactory);

end.

Note the change in the call to the RegisterInvokableClass method in the initialization section as well.

Well, that pretty much does it. This tutorial contains more explanation than the actual work involved in building a WebService with Delphi 6. It's really very simple and I hope, after you've read this tutorial, you feel the same. Testing your WebService server is a simple task, now, since you already have a client. All you need to do is change the WSDLLocation property of the THTTPRIO component in your client application (or URL property as the case may be) to match that of your WebService (instead on mine). The projects are available for download as a project group containing the WebService Client project and WebService server project.

Comments have been Disabled for this post





Menus

Theme

Privacy Policy  |  Terms Of Service  |  Contact Us  |  Support  |  Help/FAQs  |  News