Send simple strings in SOAP Header in Delphi

2019-02-06 14:14发布

问题:

I need to send something like this:

   <soapenv:Header>
      <ser:userName>admin</ser:userName>
      <ser:userPassword>secret</ser:userPassword>
   </soapenv:Header>

Delphi WSDL importer, generated this:

  userName2 = class(TSOAPHeader)
  private
    FValue: string;
  published
    property Value: string  read FValue write FValue;
  end;

  userName      =  type string;

  WsService = interface(IInvokable)
    function call(const userName: userName; const userPassword: userPassword);

and registered the type as:

  InvRegistry.RegisterHeaderClass(TypeInfo(WsService), userName2, 'userName', 'http://localhost/path/to/services');

The problem is that when I call it using the delphi generated code it puts the userName and password in the Body section of the SOAP message, not in the Header.

So I tried sending the Headers myself, like this:

Changed the type definition to inherit from the userName2 class because I can't send a string using the ISOAPHeaders.Send() method.

userName = class(userName2);        

Then sent the headers:

user := userName.Create;
user.Value := 'admin';

WS := GetWsService;
(WS as ISOAPHeaders).Send(user);

Now the headers are in the correct place, but they are being sent like this:

<SOAP-ENV:Header>
    <NS1:userName xmlns:NS1="http://localhost/path/to/services">
        <Value xmlns="http://localhost/path/to/services">admin</Value>
    </NS1:userName>
</SOAP-ENV:Header>

Almost there, but I don't want the "Value" property, I just want a plain simple tag in the header.

How can I do it?

Thanks.

== EDIT ==

As requested, the WSDL is here: http://desenvolvimento.lemontech.com.br:8081/wsselfbooking/WsSelfBookingService?wsdl

SOAP UI imported it and generated this sample request:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://lemontech.com.br/selfbooking/wsselfbooking/services">
   <soapenv:Header>
      <ser:userPassword></ser:userPassword>
      <ser:userName></ser:userName>
      <ser:keyClient></ser:keyClient>
   </soapenv:Header>
   <soapenv:Body>
      <ser:pesquisarSolicitacao>
         <!--You have a CHOICE of the next 2 items at this level-->
         <idSolicitacaoRef></idSolicitacaoRef>
         <dataInicial></dataInicial>
         <dataFinal></dataFinal>
         <registroInicial>1</registroInicial>
         <!--Optional:-->
         <quantidadeRegistros>50</quantidadeRegistros>
      </ser:pesquisarSolicitacao>
   </soapenv:Body>
</soapenv:Envelope>

This sample request works just fine, but I can't figure out how to make this call in Delphi.

回答1:

Solution:

It wasn't much obvious, but I just had to add the IS_TEXT value to the Index and declare a new TSOAPHeader descendant, the solution was like this:

const
  IS_TEXT = $0020;

type
  TSimpleHeader = class(TSOAPHeader)
  private
    FValue: string;
  published
    property Value: string Index (IS_TEXT) read FValue write FValue;
  end;

  userName = class(TSimpleHeader);

Then register this header:

InvRegistry.RegisterHeaderClass(TypeInfo(WsService), userName, 'userName', 'http://localhost/path/to/services');

And send the Header manually:

    User := userName.Create;
    User.Value := 'username';

    (WS as ISOAPHeaders).Send(User);

Basically, the IS_TEXT value in the Index prevents Delphi from creating a userName tag and a Value tag inside it. It just places the string of the Value property inside the userName tag.

It's sad that the Index keywork is used for something so not obvious, also the documentation about it is difficult to find and hard to understand:

The AS_ATTRIBUTE feature has been deprecated. It still works for legacy code, but the preferred approach is to use the index value of a property. The index property allows you to specify whether a property is an attribute, an unbounded element, an optional element, a text value, or can have a value of NULL.

Source: http://docwiki.embarcadero.com/RADStudio/XE3/en/Using_Remotable_Objects



回答2:

You can override the serialization for any TSOAPHeader class. Just override its ObjectToSOAP function. I came up with this:

unit Unit16;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, WsSelfBookingService, StdCtrls,
  InvokeRegistry, SOAPHTTPClient, opCOnvertOptions, XMLIntf, XSBuiltIns;

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

type
 TSOAPCredentials = class(TSoapHeader)
 private
    FPassword: string;
    FUsername: string;
    FKeyClient: string;
 public
   function ObjectToSOAP(RootNode, ParentNode: IXMLNode;
                            const ObjConverter: IObjConverter;
                            const NodeName, NodeNamespace, ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions;
                            out RefID: InvString): IXMLNode; override;
 published
   property userName     : string read FUsername write Fusername;
   property userPassword : string read FPassword write FPassword;
   property keyClient : string read FKeyClient write FKeyClient;
 end;



var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TSOAPCredentials }

function TSOAPCredentials.ObjectToSOAP(RootNode, ParentNode: IXMLNode; const ObjConverter: IObjConverter; const NodeName,
  NodeNamespace, ChildNamespace: InvString; ObjConvOpts: TObjectConvertOptions; out RefID: InvString): IXMLNode;
begin
 Result := ParentNode.AddChild('userName');
 Result.Text := FUsername;
 Result := ParentNode.AddChild('userPassword');
 Result.Text := FPassword;
 Result := ParentNode.AddChild('keyClient');
 Result.Text := FKeyClient;
end;

procedure TForm1.Button1Click(Sender: TObject);

var
 ws   : WsSelfBooking;
 Req  : pesquisarSolicitacao;
 Resp : pesquisarSolicitacaoResponse;
 Rio  : THTTPRIO;
 Cred : TSOAPCredentials;

begin
 Rio := THttpRIO.Create(nil);
 ws := GetWsSelfBooking(false, '', Rio);
 Cred := TSOAPCredentials.Create;
 Cred.userName := 'admin';
 Cred.userPassword := 'secret';
 Cred.keyClient := 'key';
 Rio.SOAPHeaders.Send(cred);
 Req := pesquisarSolicitacao.Create;
 Req.registroInicial := 1;
 Req.quantidadeRegistros := 50;
 Resp := ws.pesquisarSolicitacao(Req);    
end;

end.

results in this request header:

<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:Header>
 <SOAP-ENV:userName>admin</SOAP-ENV:userName>
 <SOAP-ENV:userPassword>secret</SOAP-ENV:userPassword>
 <SOAP-ENV:keyClient>key</SOAP-ENV:keyClient>
</SOAP-ENV:Header>


回答3:

You can inject the tags by using a stringreplace on the XML string right before it goes out the door "onto the wire". You need a RIO_BeforeExecute handler, and you can then deal with the SOAPRequest directly.