3.2. Interoperability demo: consume a .NET web service with Perl.

3.2.1. Make a client, for a .NET web service, with Perl and SOAP::Lite.

Let's create a SOAP client for our favorite .NET web service with Perl (still Infobel web service also used for the same demo in PHP). This is an interesting exercice because we have to play with a more advanced interface: format object as param and read the imbrication of object returned, and for a document/literal operation style. See the Section called Tip: a way to solve interoperability problem with a NuSoap client (for document/literal web service). in Chapter 2 for a description of this service.

Again with Perl and SOAP::Lite, it is very important to have a good idea of the structure of the SOAP data send and returned by the remote procedure you want to call.

Example 3-2. Perl/cgi SOAP client using SOAP::Lite.

I cover here the case of a client accessing a .NET document/literal web service. The other operation style are more easy to consume, and there is a lot of documentation on the internet, and probably of best quality than mine... ;-)

#!/usr/bin/perl -w
# infobelclient.cgi SOAP client for Infobel web service
use strict;

use SOAP::Lite;

print "Content-type: text/html\n\n";

print "<html><head><title>Perl Infobel web service client</title>
	<META HTTP-EQUIV='Content-Type' CONTENT=\"text/html; charset=iso-8859-1\">
        </head><body>";

print "<h3>Perl client for the .NET Infobel web service using SOAP::Lite</h3>";

# do not forget to specify the soapaction (on_action),
# you will find it in the wsdl.
# uri is the target namespace in the wsdl
# proxy is the endpoint address
my $soap = SOAP::Lite
    -> uri('http://www.infobel.com/WebService/')
    -> on_action( sub { return '"http://www.infobel.com/WebService/Search"' } )
    -> proxy('http://hal.kapitol.com/infobelservices/service1.asmx');

# you must define the namespace used
# in the wsdl, as an attribute to the
# method Search without namespace prefix
# for compatibility with .NET (document/literal)
my $method = SOAP::Data->name('Search')
  ->attr({xmlns => 'http://www.infobel.com/WebService/'});

# the big part now, refer to the doc on-line of 
# the .NET web service. This is very usefull 
# to make the query
# You have to construct the following xml structure:
# <inputQuery>
#   <login>...</login>
#   <password>...</password>
#   ....
#   <CoordType>...</CoordType>
# </inputQuery>
# Use the constant enumeration as required for
# the fields "country", "service", etc...
# Again refer to the doc of the web service on-line.
my $query =
  SOAP::Data
  ->name(inputQuery =>
    \SOAP::Data->value(
      SOAP::Data->name(login => 'infobel'),
      SOAP::Data->name(password => 'test'),
      SOAP::Data->name(country =>  'aeCountryBE'), 
      SOAP::Data->name(service => 'aeSrvStandard'),  
      SOAP::Data->name(Name => 'durand'),
      #SOAP::Data->name(FirstName => ''),
      #SOAP::Data->name(Street => ""),
      #SOAP::Data->name(bldnum => ""),
      SOAP::Data->name(City => 'bruxelles'),
      SOAP::Data->name(Zip => ''),
      #SOAP::Data->name(Phone => ''),
      #SOAP::Data->name(Area => ''),
      #SOAP::Data->name(Category => ''),
      SOAP::Data->name(XCoord => 0),
      SOAP::Data->name(YCoord => 0),
      SOAP::Data->name(Range => 0),
      SOAP::Data->name(PageStep => 5),
      SOAP::Data->name(Language => 'aeLangFrench'),  
      SOAP::Data->name(CoordType => 'aeCTWGS')));  

# make the call
my $result = $soap->call($method => $query);

# if no error
unless ($result->fault) {

  # refer to the structure of the xml soap response of
  # the web service (use TcpTunnelGui for example) for the 
  # access of the "status", "numrecs", ... values
  my $statusmes = $result->valueof('//SearchResponse/SearchResult/Status');

  my $numrecs = $result->valueof('//SearchResponse/SearchResult/Result/NumRecs');

  # @crecords will contain the structure of the CRecord object
  # containing themselves the fields "name", "address", etc...
  # and eventually the structure of category 
  my @crecords = $result->valueof('//SearchResponse/SearchResult/Result/collRecord/CRecord');

  print "<p>message: " . $statusmes;
  print "<br>numrecs: $numrecs";
  print "<p>record(s):";

  my $collcat;
  my @cat;

  # you can use a foreach loop to access the values
  # of each record
  foreach my $pcval (@crecords) {
    print "<table border=1 width=300>";
    print "<tr><td>Name</td><td>".$pcval->{'Name'}."</td></tr>";
    print "<tr><td>Address</td><td>".$pcval->{'Address'}."</td></tr>";
    print "<tr><td>City</td><td>".$pcval->{'City'}."</td></tr>";
    print "<tr><td>Zip</td><td>".$pcval->{'Zip'}."</td></tr>";
    print "<tr><td>Phone</td><td>".$pcval->{'Phone'}."</td></tr>";
    print "<tr><td>Fax</td><td>".$pcval->{'Fax'}."</td></tr>";

    # do the same to access the values nested
    # in the array of category
    $collcat = $pcval->{'collCategory'};
    @cat = $collcat->{'CCategory'};
    print "<tr><td>category</td><td><table>";
    foreach my $catval (@cat) {
      print "<tr><td>".$catval->{'code'}."</td><td>".$catval->{'description'}."</td></tr>";
    }
  
    print "</table></td></tr>";
    print "<tr><td>Coordinates</td><td>".$pcval->{'XCoord'}.", ".$pcval->{'YCoord'}."</td></tr>";
    print "</table><p>";
  }

} else {
  # some error handling
  print join ', ',
    $result->faultcode,
    $result->faultstring,
    $result->faultdetail;
}
print "</body></html>";

Not always so simple isn't it? We see here the limitations of the system. Web services should have been the way to make business on the internet. Sure, we can find a workaround for all these interoperability issues, whatever the language used. But doing business easily between different platforms/languages is not yet for now.