Delphi – How to Complete XML for SOAP Client

delphi

I am trying to implement a SOAP client to handle electronic reporting of California payroll taxes for the web service described by the WSDL file at this location: FSET Service. In this WSDL file, there are references to several schemas including one at http://schemas.xmlsoap.org/ws/2004/08/addressing that seems to be stopping my program before it even gets to the OnBeforeExecute event.

To get off the ground with this project, I am trying to access the Ping function declared in the WSDL file.

I am just now getting some experience with SOAP client creation in Delphi. I am using Delphi 2010. Here is what I did to get to this point. I used the WSDL importer to import the file at the above address. It created a unit called fsetservice for me. Then, I added a form to my project, to which I added an HTTPRIO component. I set the component's WSDLLocation to a local copy of the WSDL file, put a couple of TMemos on the form to capture the request and response headers and added code to do that. Then I set up a button and in its OnClick event, I wrote this code:

procedure TForm1.Button1Click(Sender: TObject);
var sResponse: String;
begin
  sResponse := GetFsetServiceSoap.Ping;
end;

When I click the button, I get this message before my breakpoint in the HTTPRIO OnBeforeExecute event is reached:

Header http://schemas.xmlsoap.org/ws/2004/08/addressing:Action for
ultimate recipient is required but not present in the message

I know we should only ask one question per posting, but sometimes you don't know enough to ask just one good question:

EDIT: To save time for anyone else with the same questions, I am putting the answers I have found under these questions.

It looks like the HTTPRIO component may be checking the XML imported from the WSDL file for completeness against the schemas referenced in the WSDL. That would seem pretty cool but is it true?

*ANSWER: Not true, at least according to the note at the bottom of the page at http://www.tutorialspoint.com/wsdl/wsdl_definition.htm. According to the note, it is not necessary for the schema to actually exist at that location, it just has to uniquely identify the schema used in the WSDL.*

In the automatically created (and unmodified) GetFsetService function, I can step all the way to the end and the error happens on return with no seemingly no possibility to step into the actual process where the error gets invoked. Is there a way to do it that I am missing?

ANSWER: I am still not sure on this but it seems that the answer is no. Anyway, although it will be more tedious to write this from scratch, that is a better solution than waiting for a miracle from the WSDL wizard.

Did I miss something that says I need to implement the FsetService interface that was automatically created for me?

ANSWER: Apparently not.

I saw in Marco Cantu's book Mastering Delphi 2005 that there is an application supplied by Borland (WebAppDbg.exe)that allows you to look at the HTTP you send on a particular port. I tried it but got no result. Would this be helpful and if so, what port should I use?

ANSWER: Use the Fiddler2 tool. Nice find.

How do I get past this error?

ANSWER: Do it by hand.

Best Answer

First of all I would start with adding Fiddler2 and SoapUI to your arsenal of tools when dealing with soap and web services.

Sounds like the envelope header is incomplete. In my experience with D2007 and a WCF web service, Delphi would not build the envelope header correctly, so I had to intercept the call and add the items like this:

procedure TMainForm.MyMessageEnvelopeComplete(Sender: TROSOAPMessage);
begin
  Sender.EnvelopeNode.AddAttribute('xmlns:a', 'http://www.w3.org/2005/08/addressing', False);
  Sender.HeaderNode.Add('a:Action').Value := 'http://Testservice.Connect/IConnect/Ping';
  Sender.HeaderNode.Add('a:To').Value := 'http://testservice-pc:2021/WSConnect';
end;

EDIT:

If you want to build the soap envelope and the post command by hand, I have done it using the following code...

procedure TMainForm.Button5Click(Sender: TObject);
  procedure HandleError(const errorCode: integer);
  var
    errorMessage: AnsiString;
  begin
    SetLength(errorMessage, 256);
    FormatMessage( FORMAT_MESSAGE_FROM_SYSTEM or FORMAT_MESSAGE_FROM_HMODULE,
                   Pointer(GetModuleHandle('wininet.dll')),
                   errorCode, 0, PChar(errorMessage), Length(errorMessage), nil);
    SetLength(errorMessage, StrLen(PChar(errorMessage)));
    raise Exception.Create(errorMessage);
  end;

  function BuildHeader: TStringStream;
  begin
    result := TStringStream.Create('');
    try
      result.WriteString('Content-Type: application/soap+xml;charset=UTF-8;action="http://Thermo.Connect/IHCSConnect/Ping"' + sLineBreak);
    except
      result.Free;
      raise;
    end;
  end;

  function BuildBody: TStringStream;
  begin
    result := TStringStream.Create('');
    with result do
      try
        WriteString('<s:Envelope xmlns:s="http://www.w3.org/2003/05/soap-envelope" xmlns:a="http://www.w3.org/2005/08/addressing">' + sLineBreak);
        WriteString('<s:Header>' + sLineBreak);
        WriteString('<a:Action>http://Thermo.Connect/IHCSConnect/Ping</a:Action>' + sLineBreak);
        WriteString('<a:To>http://thermo-pc:2021/WSHCSConnect</a:To>' + sLineBreak);
        WriteString('</s:Header>' + sLineBreak);
        WriteString('<s:Body>' + sLineBreak);
        WriteString('<Ping xmlns="http://Thermo.Connect">' + sLineBreak);
        WriteString('</Ping>' + sLineBreak);
        WriteString('</s:Body>' + sLineBreak);
        WriteString('</s:Envelope>' + sLineBreak);
      except;
        result.Free;
        raise;
      end;
  end;

var
  InetRoot: HINTERNET;
  InetConnect: HINTERNET;
  Request: HINTERNET;
begin
  InetRoot := InternetOpen('GabeCode', INTERNET_OPEN_TYPE_PRECONFIG, nil, nil, 0);
  try
    InetConnect := InternetConnect( InetRoot, 'thermo-pc:2021', 0, '',
                                    '', INTERNET_SERVICE_HTTP, 0, Cardinal(Self));
    try
      Request := HttpOpenRequest( InetConnect, 'POST', 'WSHCSConnect', 'HTTP/1.1', nil, nil,
                                  INTERNET_FLAG_KEEP_CONNECTION or INTERNET_FLAG_NO_CACHE_WRITE,
                                  0);
      try
        // build add header items to the post request
        with BuildHeader do
        try
          HttpAddRequestHeaders(Request, PChar(DataString), Length(DataString), HTTP_ADDREQ_FLAG_ADD);
        finally
          Free;
        end;

        // build the body of data being posted and send the post
        with BuildBody do
        try
          if not HttpSendRequest(Request, nil, 0, PChar(DataString), Length(DataString)) then
            HandleError(GetLastError);
        finally
          Free;
        end;

      finally
        InternetCloseHandle(Request);
      end;
    finally
      InternetCloseHandle(InetConnect);
    end;
  finally
    InternetCloseHandle(InetRoot);
  end;
end;
Related Topic