2

I have a very interesting issue when I call a SOAP method with my client, I must pass a parameter which is of type Array_Of_Int(Array_Of_Int = array of Integer), the problem is that when the array is being generated in the request, it generates the following

<ArrayParam>
  <item>12345</item>
  <item>23456</item>
  <item>34567</item>
</ArrayParam>

but I believe the server expects

<ArrayParam>12345</ArrayParam>
<ArrayParam>23456</ArrayParam>
<ArrayParam>34567</ArrayParam>

I'm pretty sure that Delphi has a workaround for this issue somehow in the RegisterSerializeOptions or RegisterInvokeOptions however I can't seem to find the issue, thoughts?!

Thank you all for your time, I'm using Delphi 2010.

EDIT: in order to fix this issue, as Bruneau mentioned, we need to have the following code added in the initialization section of generated .pas file:

InvRegistry.RegisterInvokeOptions(TypeInfo(<ServerInterfaceNameHere>), ioDocument);

However that imposes another issue, the namespace, as a quick and pretty elegant fix, I've added the following code in the THTTPRio's OnBeforeExecute method

procedure TMyDataModule.MyRioBeforeExecute(const MethodName: string; SOAPRequest: TStream);

  procedure FixNamespaces;
  var
    LStrings: TStringList;
  begin
    LStrings := TStringList.Create;
    try
      SOAPRequest.Position := 0;
      LStrings.LoadFromStream(SOAPRequest);
      SOAPRequest.Position := 0;
      SOAPRequest.Size := 0;
      LStrings.Text := StringReplace(LStrings.Text, MethodName, 'NS1:' + MethodName, [rfReplaceAll]);
      LStrings.Text := StringReplace(LStrings.Text, MethodName + ' xmlns', MethodName + ' xmlns:NS1', []);
      LStrings.SaveToStream(SOAPRequest);
      SOAPRequest.Position := 0;
    finally
      FreeAndNil(LStrings);
    end; // tryf
  end; // procedure FixNamespaces;

begin
  FixNamespaces;
end;

The above is just a fix, I really hope I can find a much cleaner and elegant solution to this issue, if anyone knows, please DO post your answer.

2 Answers2

4

The two serialization options you described are both valid and needed. The issue is that from a language/native point of view, Delphi represents both of them with a dynamic array (Array_Of_Int = array of Integer). So, the runtime must be told whether to serialize for a "Pure Collection" (the first kind with elements and the outer ArrayParam) or "unbounded elements" ("ArrayParam" elements).

In earlier versions, including 2010, you could instruct the runtime to serialize as unbounded elements with the following registration:

  RemClassRegistry.RegisterSerializeOptions(TypeInfo(Array_Of_Int), [xoInlineArrays]);

If the type is used in a property, you could also simply tag the property itself as unbounded, as in:

property propName: Array_Of_Int Index (IS_UNBD) read FName write FName;

The drawback of the registration approach is that it does not allow one to (re)use the type for both serializations. In Delphi XE this was remedied and now the type is never registered for a particular scheme. Instead each Dynamic Array property or parameter specifies whether it's a "Pure Collection" vs. "Unbounded Element", eliminating the need to have distinct Dynamic Array of Integers for each serialization.

Cheers,

Bruneau

BruneauB
  • 1,104
  • 6
  • 5
  • I should have added that the WSDL Importer should have generated the necessary 'RegisterSerializeOptions' call. I just looked at our tests from D2010 and I see that when importing the following WSDL (https://adwords.google.com/api/adwords/v13/CampaignService?wsdl) the importer correctly generated: " RemClassRegistry.RegisterSerializeOptions(TypeInfo(Array_Of_int), [xoInlineArrays]);". If you have a WSDL where the importer fails to do so, please, do let us know (or open a report at qc.embarcadero.com). Thank you. – BruneauB Feb 23 '11 at 15:20
  • thank you again Bruneau for your interest in helping me, while importing the WSDL the xoInlineArrays serialization option was added, however for some reason it does not generate the expected result, again, the whole issue is that for each item in the array, delphi generates value which is frustrating because thus far I was unable to work around it and my dead line is closer with each hour :(, thoughts? thank you –  Feb 23 '11 at 15:29
  • Does the Service use XML encoding or SOAP encoding? We will only pay attention to the serialization if it's XML encoding. Could you verify whether the WSDL importer generated the following line: " InvRegistry.RegisterInvokeOptions(TypeInfo(), ioDocument); " and let us know. Thank you. – BruneauB Feb 23 '11 at 15:49
  • it's SOAP encoding... yes the importer added "ioDocument" however it fails to "talk" to the service unless I change that to ioHasNamespace, ioHasAllSoapActions or remove the line... I don't know exactly why... –  Feb 23 '11 at 16:10
  • Now, I'm confused. "SOAP Encoding" cannot/should-not use/expect unbounded elements. Section 5 of the SOAP spec. clearly says how arrays are to be encoded. If you look in the WSDL of the service does it contain `use="literal"` or '`use="encoded"`? Can you point me to the WSDL, if it's accessible online? Thank you. – BruneauB Feb 23 '11 at 16:21
  • @BruneauB: You can Edit your answer ;-) – Jeroen Wiert Pluimers Feb 23 '11 at 17:22
  • Thanks Jeroen. I forgot this is not the newsgroup. Will do! – BruneauB Feb 23 '11 at 17:54
  • @BurneauB I'm sorry I was very frustrated yesterday with the whole SOAP thingy so I called it a day... the WSDL is not publicly available but I can confirm it has use="literal" in the WSDL file, also the server uses SOAP 1.1, thank you! –  Feb 24 '11 at 09:13
  • I've managed to fix the issue "partially" -- of course how else?! -- if registering the invoke options with ioDocument the array elements are generated as the server expected, the remaining issue is that the namespace is not generated "correctly" i.e. should be I've tried registering invoke options with [ioDocument, ioHasNamespace] so far not working... –  Feb 24 '11 at 10:47
1

Since no one cares to post their answer or have no other idea on how to fix this issue, I'll just post my fix until others can come out with a more elegant solution than editing the request.

Make sure the next line of code is added in the initialization section of the *.pas file generated when you imported the WSDL file(big thanks to Bruneau for pointing this out)

InvRegistry.RegisterInvokeOptions(TypeInfo(<ServerInterfaceNameHere>), ioDocument);

However that imposes another issue, the namespace, as a quick and pretty elegant fix, I've added the following code in the THTTPRio's OnBeforeExecute method

procedure TMyDataModule.MyRioBeforeExecute(const MethodName: string; SOAPRequest: TStream);

  procedure FixNamespaces;
  var
    LStrings: TStringList;
  begin
    LStrings := TStringList.Create;
    try
      SOAPRequest.Position := 0;
      LStrings.LoadFromStream(SOAPRequest);
      SOAPRequest.Position := 0;
      SOAPRequest.Size := 0;
      LStrings.Text := StringReplace(LStrings.Text, MethodName, 'NS1:' + MethodName, [rfReplaceAll]);
      LStrings.Text := StringReplace(LStrings.Text, MethodName + ' xmlns', MethodName + ' xmlns:NS1', []);
      LStrings.SaveToStream(SOAPRequest);
      SOAPRequest.Position := 0;
    finally
      FreeAndNil(LStrings);
    end; // tryf
  end; // procedure FixNamespaces;

begin
  FixNamespaces;
  // other possible issue to be fixed -- if any
end;
  • 1
    I can't confirm without seeing the WSDL/Schema but based on the fix you've posted, the Service probably expects a different namespace for the request/response than the namespace of the interface. Support for this was only added to Delphi/XE via the 'RequestNS' and 'ResponseNS' attribute that the importer will generate for each method in these cases. Before Delphi XE the only way to support this is either via the BeforeExecute event (as you did above) or by registering the interface in a different namespace. The latter would not work if one method expected ns1 while the other expected ns2. – BruneauB Feb 25 '11 at 18:09
  • I am really disappointed that we have little flexibility(IMO) on how the SOAP requests are made, for example I do NOT wish to do something so trivial manually if with some properties, registrations, etc. can be solved, in the future(next Delphi version XE2, SUPER XE) I will need to at least review every request to be sure it works properly(which to some degree defies the RAD idea...), furthermore I wouldn't be surprised if support will drop in the next year release... anyways the client is very strict about the WSDL(can't be made public[!]). Thank you very much for support Bruneau. –  Feb 25 '11 at 20:35
  • 1
    I agree with you regarding the lack of flexibility. The original plan to tie SOAP to RTTI was made based on SOAP Encoding rules. As the standard switched to XML encoding, it became clear that this was an unfortunate decision as XML encoding requires much more flexibility/customization than can be encoded in RTTI:( – BruneauB Feb 25 '11 at 21:49