PDA

View Full Version : Creating a Web Service



cairnswm
12-11-2004, 02:11 PM
What is a Web Service?
Delphi Help defines a web service as: "Web Services are self-contained modular applications that can be published and invoked over the Internet. Web Services provide well-defined interfaces that describe the services provided. Unlike Web server applications that generate Web pages for client browsers, Web Services are not designed for direct human interaction. Rather, they are accessed programmatically by client applications."

Its a pretty good definition actually. The key aspects of a web service are:
Self-Contained
Invoked over the Internet
Well Defined Interface
Not for Humnan Interaction
So each web service does everything it needs to by itself (or uses other web services to do so), called over the internet. A well defined interface gets imported into other applications that handle the user interaction.

This tutorial will show you how to create a Web Service in Delphi 7.

Why start with the Web Service as opposed to the application that uses the Web Service?

When I first started learning about web services I wanted to create an applicaiton that could consume (use) a web service. I never understood why every document I read first started speaking about the server side of things.

The reason for this is that the server side of things structures everything you need to consume a web service. Part of the web service creation is the definition of a WSDL (Web Sevice Definition Language) for the web service. This WSDL describes to other application what functionality is made available by your web service. This means that trying to consume something without understanding the need of a WSDL is pointless.

What can a web service do for you?

Web Services are one of the latest buzz words in IT. But what can they actually do for us? Basically a web service can do anything a Delphi Class could do (note I said Class not Component). For Example: A Web Service cannot display a List box to a user but it can store information about a user. So basically a Web Service can be seen as a Class that you are using on a remote computer (the web server). This class can then manage data, access a database, cache messages or just about anything.

Ok so lets start creating a web Service:
Open Delphi and Select File - New - Other, go to the "Web Services" tab and select "Soap Server Application"

The Delphi Wizards gives a number of options to define how the web service will be packaged.
e.g. Isapi, Cgi, Apache 1, Apache 2
The details of each of these options is outside the scope of this tutorial but all have their place.

Select CGI to create a cgi implementation for your web service. CGI is definitily easiest when you begin learning as it has no state and as such can easily be modified. An ISAPI dll requires it to be unloaded form the IIS before it can be updated.

When asked to "Create interface for soap module" select YES to get the standard Web Service interfaces put in place.

Enter the name you want to give your web service (I used TMyWebService), ensure that "Generate Sample Methods" is checked before pressing the [OK] button. The Generate Sample Methods is used to show the syntax of the methods and properties published through the Web Service. While its possible to add all this yourself its a lot easier to modify the example methods.

Two units are created for you. In my case they were TMyWebServiceImpl and TMyWebServiceIntf (Obviously yours may have different names). TMyWebServiceIntf is the interface and only be modified to add methods and properties to the interface.

TMyWebServiceImpl is where the actual code is added to make a web service.

Ok so what was actually added by the Delphi wizard?

type

TEnumTest = (etNone, etAFew, etSome, etAlot);

TDoubleArray = array of Double;

TMyEmployee = class(TRemotable)
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;

{ Invokable interfaces must derive from IInvokable }
ITMyWebService = interface(IInvokable)
['{6FB8FD20-7C1C-4860-9521-0420EF92B774}']

{ 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;


These are examples to show how the web service is structured.

TMyEmployee shows how a complex object can be passed. The others show how different information types are passed.

Save your Project in a directory on your PC. Compile the Project (You should get an EXE).

Copy the EXE to the cgi-bin directory of your IIS implementation. Most IIS implementation do not come with a standard cgi-bin directory so you may need to add it. Also make sure that the Executable permission is set for the directory.

I always set my projects output directory to be my cgi-bin directory so that the web service is updated everytime I recompile.

So how do we see if the web service is working?

Open your internet explorer and point it to your new application:
http://localhost\cgi-bin\WebServiceServer.exe

What you see is the Serive Info Page. The important bit here is the ITMyWebService section. Click on WSDL link next to its name. This displays the actual web service definition which is used by later to import the web service into a client (My next Tutorial).

So how do I add my own methods to the web service?

The next step is obviously to add our own functionality into the web service. The easiest way to show this is to make a small example that gets and sets a name.

I first modify the Web Service impleemntation to add the following code.

TTMyWebService = class(TInvokableClass, ITMyWebService)
Private
FNAme : String;
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;
Procedure SetName(Const Value : String); stdcall;
Function GetName : String; stdcall;
end;
..
function TTMyWebService.GetName: String;
begin
Result := FName;
end;

procedure TTMyWebService.SetName(const Value: String);
begin
FName := Value;
end;

If you compile this now you see that these two new methods are not compiled - this is because they are not part of the interface and are therefore meaningless to the web service.

By modifying the web service interface to add these two methods:

{ Invokable interfaces must derive from IInvokable }
ITMyWebService = interface(IInvokable)
['{6FB8FD20-7C1C-4860-9521-0420EF92B774}']

{ 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;
Procedure SetName(Const Value : String); stdcall;
Function GetName : String; stdcall;
end;


A recompile now compiles both functions. Go check again at the web service page in your browser (press refresh) and you'll see the two new methods have been added.

If you check the WSDL you will also see it has been modified to add the two new methods.

Now remember that a CGI application does not have any state (its created when needed and then closed when finished). This means that between the set and the get the server will forget about the name. So we need to store this information somewhere.

To show this I use a small ini file as follows: (This goes into the Implementation unit):


Constructor Create; Override;
Destructor Destroy; Override;
end;
..
constructor TTMyWebService.Create;
Var
Ini : TIniFile;
begin
inherited;
Ini := TIniFile.Create('C:\WS.ini');
FName := Ini.ReadString('Values','Name','William');
Ini.Free;
end;

destructor TTMyWebService.Destroy;
Var
Ini : TIniFile;
begin
Ini := TIniFile.Create('C:\WS.ini');
Ini.WriteString('Values','Name',FName);
Ini.Free;
inherited;
end;


Normally you would want this in a database or something. If you were using an ISAPI dll as your web service the web service would remember its state betwene calls and you would only need to store data that needs to have long term persistence. Then whenever the server starts up you reload the data and continually save as people work. By caching the data link this your performance would be improved as the data is already available when someone asks for it.

Remember however that you are not going to be allowed to unload ISAPI dlls on your ISP host, so you may want to use CGI exes for your web services unless you actually own and manage your web site.

So what is TMyEmployee and why is it remotable?
Delphi defines TRemotable as: TRemotable is the base class for classes that can be passed as parameters or return values in a Web Service application.

Basically TRemotable allows you to pass complex objects from one server to another. This means that you can pass everything about a person through in a single call instead of doing multiple calls to fetch each field.

In the next tutorial I will use the web service in a client application.

cairnswm
12-11-2004, 02:14 PM
The next Tutorial is

Consuming a Web Service (http://terraqueous.f2o.org/dgdev/viewtopic.php?t=1635)