Summary:
I have been trying to make requests of a web service provided by Axis using
a dotnet client with code generated by wsdl.exe and have
been getting exceptions when trying to process the response.
As a result of seraching news groups I guessed that the SOAP response
defines an array element in a way that causes the dotnet deserialization
routines to put the content in a generic object array (object[]) BUT the
content is supposed to go into a specific array (e.g. paymentItem[]). As a
result, when the application tries to populate the reponse object, it tries
to assign the contents of an object[] into a paymentItem[] and things go
wonky.
I used SOAP extensions to manipulate the offending item in the SOAP
response, prior to deserialization, and the exceptions went away.
But, this solution is neither elegant nor suitable as a general means of
dealing with responses from Axis servers.
So, I'm looking for a better way to create dotnet web service clients that
must deal with Axis webservers.
Any body have any suggetions?
Here's the details of my problem and how I dealt with it:
Please note that I have almost no experience with SOAP and my terminology
may be weak.
I am trying to use a webservice from an Axis server. I was given a WSDL file
(I'm sorry, but as far as I know the file is not publicy available and as
such I haven't included it. I have asked if I can post its contents and may
be able to, at a later date, if required. I think I provide enough detail
below that the contents of the WSDL will not be required in order to solve my
problem). I used wsdl.exe to generate a c# file and used that code to make
requests of the Axis server.
I had the following exception when processing the SOAP response:
System.InvalidOperationException: There is an error in XML document (12,
19). ---> System.InvalidCastException: Cannot assign object of type
System.Object[] to an object of type
customertransactions.[editted].PaymentItem[].
at
Microsoft.Xml.Serialization.GeneratedAssembly.XmlS erializationReader1.Read3_ResponseModel()
at
System.Xml.Serialization.XmlSerializationReader.Re adReferencingElement(String
name, String ns, Boolean elementCanBeType, String& fixupReference)
at System.Xml.Serialization.XmlSerializationReader.Re adReferencedElements()
at
Microsoft.Xml.Serialization.GeneratedAssembly.XmlS erializationReader1.Read6_walletPaymentResponse()
--- End of inner exception stack trace ---
at System.Xml.Serialization.XmlSerializer.Deserialize (XmlReader
xmlReader, String encodingStyle, XmlDeserializationEvents events)
at System.Xml.Serialization.XmlSerializer.Deserialize (XmlReader
xmlReader, XmlDeserializationEvents events)
at System.Xml.Serialization.XmlSerializer.Deserialize (XmlReader xmlReader)
at
System.Web.Services.Protocols.SoapHttpClientProtoc ol.ReadResponse(SoapClientMessage message, WebResponse response, Stream responseStream, Boolean asyncCall)
at System.Web.Services.Protocols.SoapHttpClientProtoc ol.Invoke(String
methodName, Object[] parameters)
at
.... [I removed the top level of the trace and made another small edit to the
exception text for proprietary reasons]
I have seen someone with a similar problem (It also appears that they were
trying to access the same web service) but the discussion seems to have ended
before a solution was posted (see
http://groups.google.com/groups?hl=e...TNGP10.phx.gbl)
I also found discussions, in what appear to be Axis user and developer mail
archives, about Axis/dotnet interop issues with respect to arrays
( see
http://www.mail-archive.com/ax******.../msg00629.html,
http://www.mail-archive.com/ax******.../msg23304.html,
and
http://www.mail-archive.com/ax******.../msg00195.html)
The latter mention how a SOAP message element may face deserialization
issues if the xsi:type attribute is "soapenc:Array" and the soapenc:arrayType
attribute is "xsd:anyType[n]" (where n is the number of elements in the
array). It specifically mentions that the contents may get put into an
object[].
As a result I tried to find a way to look at the SOAP requests and
responses. I found a reference to soapextensions (the reference was rather
sparse) and from there a found a link to a microsoft help page
(ms-help://MS.VSCC.2003/MS.MSDNQTR.2003FEB.1033/cpguide/html/cpconalteringsoapmessageusingsoapextensions.htm#cp conalteringsoapmessageusingsoapextensionsanchor1)
that ALMOST exlplains how to create a soap extension that will log SOAP
messages. With some twiddling I got the logger to work.
The logged SOAP response was using anyType[n] to describe the array.
I did a test. I modified the SOAP response prior to deserialization by
replacing the anyType[n] text with a reference to the proper element type.
I did this by taking the logger example mentioned in the above link, and
just prior to calling the logger, I inserted the following (yes it's ugly, it
is test code):
if (!(message is SoapServerMessage))
{
TextReader reader = new StreamReader(oldStream);
string joe = reader.ReadToEnd();
int bbdd = joe.IndexOf("anyType");
StringBuilder xformHtml = new StringBuilder();
int pos1 = joe.IndexOf("xsd:anyType");
int pos2 = joe.IndexOf("[",pos1);
xformHtml.Append(joe.Substring(0,pos1));
xformHtml.Append("ns2:PaymentItem");
xformHtml.Append(joe.Substring(pos2));
joe = xformHtml.ToString();
Stream bbbb = new MemoryStream(System.Text.UTF8Encoding.UTF8.GetByte s(joe));
oldStream = bbbb;
}
When the response was modified by the above code, the deserialization did
not result in any exceptions and the resulting response object contained the
correct information.
Here's the orginial response (with the domain name of the service provider
changed):
<multiRef id="id0" soapenc:root="0"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="ns2:ResponseModel"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:ns2="http://serviceprovider.com">
<message xsi:type="xsd:string">Stuff</message>
<paymentItems xsi:type="soapenc:Array" soapenc:arrayType="xsd:anyType[2]"">
<item href="#id1"/>
<item href="#id2"/>
</paymentItems>
<statusCode xsi:type="xsd:string">000</statusCode>
<transactionId xsi:type="xsd:string">1</transactionId>
</multiRef>
<multiRef id="id1" soapenc:root="0"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="ns3:PaymentItem" xmlns:ns3="http://serviceprovider.com"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<amount xsi:type="xsd:string">415</amount>
<itemType xsi:type="xsd:string">CC</itemType>
</multiRef>
<multiRef id="id2" soapenc:root="0"
soapenv:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/"
xsi:type="ns4:PaymentItem" xmlns:ns4="http://serviceprovider.com"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/">
<amount xsi:type="xsd:string">0</amount>
<itemType xsi:type="xsd:string">CHARGES</itemType>
</multiRef>
The modified response only differed the in soapence:arrayType attribute of
the paymentItems element:
<paymentItems xsi:type="soapenc:Array"
soapenc:arrayType="ns2:PaymentItem[2]">
I noticed that the array elements were defined as PaymentItem.
It appears that the dotnet deserializion algorithms were not able to determine
that paymentItems was an array of PaymentItem elements unless the
soapenc:arrayType attribute was modified to explicity instruct so. This is
consistent with what I read in
http://www.mail-archive.com/ax******.../msg00195.html.
Now, I have found a way to get dotnet to process a specific SOAP message.
But it is ugly and it involves my having a knowledge of the SOAP message
contents and schema. From what little I know about web services they are
supposed to remove me from the loop and they are supposed to be platform
independent; given a wsdl file and a proper wsdl processor the client
developer doesn't need to know about the message content. So, I figure there
must be a better way to do this.
So, is there a known problem with Axis/dotnet interop with respect to
complex message objects (or, specifically, arrays) and is there a known
solution? Is there something that I must do if I know that I am using a
dotnet client to access an Axis webservice?
Please note that I have no control over the service provider. If the
solution involves client side changes, great. If it involves server side
changes, I'd like references that I can use to convince the service provider
that they need to address this issue, given that they want to deal with
dotnet clients.
Thanks