Sunday, December 30, 2007

Creating a Web Service Client with Delphi

 In this article, the second of three parts covering web services, we will build a web service client and discuss the various components and methods needed to make an application work. A downloadable file for this article is available here.

In the previous article we learned what a web service is and examined the kind of code used to create one. We also looked at some actual WSDL, XML and SOAP code. In the second part of the "Web Services Made Easy" series we are going to create a web service client for an already existing web service, and then we are going to create our own simple web service and client with Borland Delphi. This is because I want you to see how easy it is to access a web service that is already built by someone else in a different programming  language. We will create our own custom web service because in this way you will learn how to create your own methods for the web service and also how to access them. Since we've done a lot of theoretical work in the first part, I feel it is time to demonstrate everything we discussed, in practice.

Building a web service client

The first client we are going to create is going to be a service that will give us the weather forecast for international cities such as London or Paris. So open up your web browser and go to http://www.xmethods.com/. There you will see a complete listing of all the web services that are available, as well as what programming language was used to create them.

Click on the link that says "View full listing" and then press "ctrl + f" to bring up the find dialog box (I.E. browsers only). Type in "USA Weather Forecast" and then click on "find next." This should take you straight to the "USA Weather Forecast" link. Click on the link and then find the label that says "WSDL." Copy the link next to it. Now start Delphi, go to File|New|Other, and then click on the web services tab. You should now see a dialog like this:

Click on the  WSDL importer icon. You should now see the following screen:

Enter the link you copied and click on "next." You should now see the following screen:

Click on "finish" and Delphi will create a unit called "Weatherforecast" for you. Save the unit. We'll take a look at the unit's code listing in the next section.

 // ************************************************************* //

// The types declared in this file were generated from data read from the

// WSDL File described below:

// WSDL     : http://www.webservicex.net/WeatherForecast.asmx?WSDL

// Encoding : utf-8

// Version  : 1.0

// (13/08/2006 14:39:01 - 1.33.2.5)

// ************************************************************* //

unit WeatherForecast;

interface

uses InvokeRegistry, SOAPHTTPClient, Types, XSBuiltIns;

type

  // ************************************************************* //

  // The following types, referred to in the WSDL document are not being represented

  // in this file. They are either aliases[@] of other types represented or were referred

  // to but never[!] declared in the document. The types from the latter category

  // typically map to predefined/known XML or Borland types; however, they could also

  // indicate incorrect WSDL documents that failed to declare or import a schema type.

  // ************************************************************* //

  // !:string          - "http://www.w3.org/2001/XMLSchema"

  // !:float           - "http://www.w3.org/2001/XMLSchema"

  WeatherData          = class;                 { "http://www.webservicex.net" }

  WeatherForecasts     = class;                 { "http://www.webservicex.net" }

  // ************************************************************** //

  // Namespace : http://www.webservicex.net

  // ************************************************************* //

  WeatherData = class(TRemotable)

  private

    FDay: WideString;

    FWeatherImage: WideString;

    FMaxTemperatureF: WideString;

    FMinTemperatureF: WideString;

    FMaxTemperatureC: WideString;

    FMinTemperatureC: WideString;

  published

    property Day: WideString read FDay write FDay;

    property WeatherImage: WideString read FWeatherImage write FWeatherImage;

    property MaxTemperatureF: WideString read FMaxTemperatureF write FMaxTemperatureF;

    property MinTemperatureF: WideString read FMinTemperatureF write FMinTemperatureF;

    property MaxTemperatureC: WideString read FMaxTemperatureC write FMaxTemperatureC;

    property MinTemperatureC: WideString read FMinTemperatureC write FMinTemperatureC;

  end;

  ArrayOfWeatherData = array of WeatherData;    { "http://www.webservicex.net" }

  // ************************************************************** //

  // Namespace : http://www.webservicex.net

  // ************************************************************* //

  WeatherForecasts = class(TRemotable)

  private

    FLatitude: Single;

    FLongitude: Single;

    FAllocationFactor: Single;

    FFipsCode: WideString;

    FPlaceName: WideString;

    FStateCode: WideString;

    FStatus: WideString;

    FDetails: ArrayOfWeatherData;

  public

    destructor Destroy; override;

  published

    property Latitude: Single read FLatitude write FLatitude;

    property Longitude: Single read FLongitude write FLongitude;

    property AllocationFactor: Single read FAllocationFactor write FAllocationFactor;

    property FipsCode: WideString read FFipsCode write FFipsCode;

    property PlaceName: WideString read FPlaceName write FPlaceName;

    property StateCode: WideString read FStateCode write FStateCode;

    property Status: WideString read FStatus write FStatus;

    property Details: ArrayOfWeatherData read FDetails write FDetails;

  end;

  // ************************************************************** //

  // Namespace : http://www.webservicex.net

  // soapAction: http://www.webservicex.net/%operationName%

  // transport : http://schemas.xmlsoap.org/soap/http

  // style     : document

  // binding   : WeatherForecastSoap

  // service   : WeatherForecast

  // port      : WeatherForecastSoap

  // URL       : http://www.webservicex.net/WeatherForecast.asmx

  // ************************************************************** //

  WeatherForecastSoap = interface(IInvokable)

  ['{45A46EB0-5550-8C38-49AC-AE13AD113F74}']

    function  GetWeatherByZipCode(const ZipCode: WideString): WeatherForecasts; stdcall;

    function  GetWeatherByPlaceName(const PlaceName: WideString): WeatherForecasts; stdcall;

  end;

function GetWeatherForecastSoap(UseWSDL: Boolean=System.False; Addr: string=''; HTTPRIO: THTTPRIO = nil): WeatherForecastSoap;

implementation

function GetWeatherForecastSoap(UseWSDL: Boolean; Addr: string; HTTPRIO: THTTPRIO): WeatherForecastSoap;

const

  defWSDL = 'http://www.webservicex.net/WeatherForecast.asmx?WSDL';

  defURL  = 'http://www.webservicex.net/WeatherForecast.asmx';

  defSvc  = 'WeatherForecast';

  defPrt  = 'WeatherForecastSoap';

var

  RIO: THTTPRIO;

begin

  Result := nil;

  if (Addr = '') then

  begin

    if UseWSDL then

      Addr := defWSDL

    else

      Addr := defURL;

  end;

  if HTTPRIO = nil then

    RIO := THTTPRIO.Create(nil)

  else

    RIO := HTTPRIO;

  try

    Result := (RIO as WeatherForecastSoap);

    if UseWSDL then

    begin

      RIO.WSDLLocation := Addr;

      RIO.Service := defSvc;

      RIO.Port := defPrt;

    end else

      RIO.URL := Addr;

  finally

    if (Result = nil) and (HTTPRIO = nil) then

      RIO.Free;

  end;

end;

destructor WeatherForecasts.Destroy;

var

  I: Integer;

begin

  for I := 0 to Length(FDetails)-1 do

    if Assigned(FDetails[I]) then

      FDetails[I].Free;

  SetLength(FDetails, 0);

  inherited Destroy;

end;

initialization

  InvRegistry.RegisterInterface(TypeInfo(WeatherForecastSoap), 'http://www.webservicex.net', 'utf-8');

  InvRegistry.RegisterDefaultSOAPAction(TypeInfo(WeatherForecastSoap), 'http://www.webservicex.net/%operationName%');

  InvRegistry.RegisterInvokeOptions(TypeInfo(WeatherForecastSoap), ioDocument);

  RemClassRegistry.RegisterXSClass(WeatherData, 'http://www.webservicex.net', 'WeatherData');

  RemClassRegistry.RegisterXSInfo(TypeInfo(ArrayOfWeatherData), 'http://www.webservicex.net', 'ArrayOfWeatherData');

  RemClassRegistry.RegisterXSClass(WeatherForecasts, 'http://www.webservicex.net', 'WeatherForecasts');

end.

As you can see, the file describes in some detail the service methods and, among other things, the location of the file. We are only interested in two methods and their return types:

function  GetWeatherByZipCode(const ZipCode: WideString):
WeatherForecasts; stdcall;

 function  GetWeatherByPlaceName(const PlaceName: WideString):
WeatherForecasts; stdcall;

The functions takes a parameter each, either the name or zip code of the city whose weather forecast you want to check. They then return a "weatherforecasts" type. Now if you call the function like so:

Memo1.lines.Add( getplacebyname('London'));

It won't work, because the type that is returned is not of the "TString" type, it is of the "weatherforecast" type.  If you take  a closer look at the "WeatherData" class, you will see that all the data is stored in an "ArrayOfWeatherData" array. So when you call either of these functions, the data is stored in the above array.

Create a new application and add a memo, a button (caption it Check Weather), and an edit box. Go to the web services tab and drop an HTTPRIO component . Save this application in the same directory where you've saved the unit created earlier. In the implementation section of the form add the following - uses WeatherForecast;

Your form should now look something like this at design time:

The HTTPRIO component represents a remote invokable object over an HTTP connection. What this means is that our client will use this component to communicate with the web service over the Internet. The component has four key properties:

  • URL
  • Service
  • WSDLLocation
  • Port

Of these properties, you either use the URL property or the WSDLLocation, Service and Port  properties. So let's fill these these properties as follows:

WSDLLocation - http://www.webservicex.net/WeatherForecast.asmx?WSDL

Service - WeatherForecast

Port – WeatherForecastSoap

Most of these properties will already have information available to them when you click on the dropdown boxes. The HTTPRIO component will now contain all the methods of the web service.

Next, double click on the button and add the following code:

procedure TForm1.Button1Click(Sender: TObject);
var
wf:WeatherForecastS;
res:ArrayOfWeatherData;
i:integer;
begin
wf:=(htt as WeatherForecastSoap).GetWeatherByPlaceName
(edit1.Text);
if wf.PlaceName<> '' then
res:=wf.Details;
memo1.Lines.Add('The min and max temps in Fahrenheit is:');
memo1.Lines.Add(' ');
for i:= 0 to high(res) do
begin
memo1.Lines.Add(res[i].Day+'   -   '+ ' Max Temp. Fahr: '+res
[i].MaxTemperatureF+'   -   '+'Min Temp Fahr: '+res
[i].MinTemperatureF);
end
end;

The code first takes the city name and then retrieves the weather data:

wf:=(htt as WeatherForecastSoap).GetWeatherByPlaceName
(edit1.Text);
if wf.PlaceName<> '' then
res:=wf.Details;
memo1.Lines.Add('The min and max temps in Fahrenheit is:');
memo1.Lines.Add(' ');

Then it simply loops through the results that are stored in the array and adds them to the memo:

for i:= 0 to high(res) do
begin
memo1.Lines.Add(res[i].Day+'   -   '+ ' Max Temp. Fahr: '+res
[i].MaxTemperatureF+'   -   '+'Min Temp Fahr: '+res
[i].MinTemperatureF);

Below is a screen shot of a test run of the application:

So what is happening behind the scenes when you execute the code? Well, the HTTPRIO component creates a SOAP envelop and sends this off to the web service server, which in turn processes the message and returns an appropriate response. The messages, both outgoing and incoming, are in XML  format:

Outgoing message:

<?xml version="1.0"?>

<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsd=http://www.w3.org/2001/XMLSchema
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"><SOAP-
ENV:Body>
<GetWeatherByPlaceName xmlns="http://www.webservicex.net">
<PlaceName>london</PlaceName>
</GetWeatherByPlaceName>
</SOAP-ENV:Body>
</SOAP-ENV:Envelope>

You can clearly see the place name parameter that we entered. In this case it is London. Below is the response message we get from the web service server:

Incoming message:

<?xml version="1.0" encoding="utf-8"?>
<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>
<GetWeatherByPlaceNameResponse xmlns="http://www.webservicex.net">
<GetWeatherByPlaceNameResult><Latitude>30.6171017</Latitude>
<Longitude>99.62553</Longitude>
<AllocationFactor>2.7E-05</AllocationFactor>
<FipsCode>48</FipsCode>
<PlaceName>LONDON</PlaceName>
<StateCode>TX</StateCode>
<Details><WeatherData>
<Day>Sunday, August 13, 2006</Day>
<WeatherImage>http://www.nws.noaa.gov/weather/images/fcicons/
nscttsra10.jpg
</WeatherImage>
….
</soap:Body>
</soap:Envelope>

The Delphi SOAP framework does all the hard work of putting together the SOAP envelope and sending it off to the server; then it reads the response and converts the XML into a human readable form.

Conclusion

In this article we went through the process of building a web service client and also discussed the various components and methods needed to make an application work. In the next article we are going to create a web service from scratch and also create a client that will enable us to use the service.



Never miss a thing. Make Yahoo your homepage.

0 Comments: