Why won't .NET deserialize my primitive array from a web service?

Help! I have an Axis web service that is used by a C # application. Everything works fine, except that arrays of long values ​​are always found as [0,0,0,0] - the correct length, but the values ​​are not deserialized. I tried with other primitives (ints, double), and the same thing happens. What am I doing? I do not want to change the semantics of my service.

+4
source share
3 answers

That's what I did. I never found another solution for this, so if you have something better, by all means, make a contribution.

First, the definition of a long array in the wsdl: types scope:

<xsd:complexType name="ArrayOf_xsd_long"> <xsd:complexContent mixed="false"> <xsd:restriction base="soapenc:Array"> <xsd:attribute wsdl:arrayType="soapenc:long[]" ref="soapenc:arrayType" /> </xsd:restriction> </xsd:complexContent> </xsd:complexType> 

Then we create the SoapExtensionAttribute attribute that will perform the correction. It seems that the problem was that .NET did not execute the multiref identifier for the element containing the double value. So, we process the element of the array, find the value, and then insert it into the element:

 [AttributeUsage(AttributeTargets.Method)] public class LongArrayHelperAttribute : SoapExtensionAttribute { private int priority = 0; public override Type ExtensionType { get { return typeof (LongArrayHelper); } } public override int Priority { get { return priority; } set { priority = value; } } } public class LongArrayHelper : SoapExtension { private static ILog log = LogManager.GetLogger(typeof (LongArrayHelper)); public override object GetInitializer(LogicalMethodInfo methodInfo, SoapExtensionAttribute attribute) { return null; } public override object GetInitializer(Type serviceType) { return null; } public override void Initialize(object initializer) { } private Stream originalStream; private Stream newStream; public override void ProcessMessage(SoapMessage m) { switch (m.Stage) { case SoapMessageStage.AfterSerialize: newStream.Position = 0; //need to reset stream CopyStream(newStream, originalStream); break; case SoapMessageStage.BeforeDeserialize: XmlWriterSettings settings = new XmlWriterSettings(); settings.Indent = false; settings.NewLineOnAttributes = false; settings.NewLineHandling = NewLineHandling.None; settings.NewLineChars = ""; XmlWriter writer = XmlWriter.Create(newStream, settings); XmlDocument xmlDocument = new XmlDocument(); xmlDocument.Load(originalStream); List<XmlElement> longArrayItems = new List<XmlElement>(); Dictionary<string, XmlElement> multiRefs = new Dictionary<string, XmlElement>(); FindImportantNodes(xmlDocument.DocumentElement, longArrayItems, multiRefs); FixLongArrays(longArrayItems, multiRefs); xmlDocument.Save(writer); newStream.Position = 0; break; } } private static void FindImportantNodes(XmlElement element, List<XmlElement> longArrayItems, Dictionary<string, XmlElement> multiRefs) { string val = element.GetAttribute("soapenc:arrayType"); if (val != null && val.Contains(":long[")) { longArrayItems.Add(element); } if (element.Name == "multiRef") { multiRefs[element.GetAttribute("id")] = element; } foreach (XmlNode node in element.ChildNodes) { XmlElement child = node as XmlElement; if (child != null) { FindImportantNodes(child, longArrayItems, multiRefs); } } } private static void FixLongArrays(List<XmlElement> longArrayItems, Dictionary<string, XmlElement> multiRefs) { foreach (XmlElement element in longArrayItems) { foreach (XmlNode node in element.ChildNodes) { XmlElement child = node as XmlElement; if (child != null) { string href = child.GetAttribute("href"); if (href == null || href.Length == 0) { continue; } if (href.StartsWith("#")) { href = href.Remove(0, 1); } XmlElement multiRef = multiRefs[href]; if (multiRef == null) { continue; } child.RemoveAttribute("href"); child.InnerXml = multiRef.InnerXml; if (log.IsDebugEnabled) { log.Debug("Replaced multiRef id '" + href + "' with value: " + multiRef.InnerXml); } } } } } public override Stream ChainStream(Stream s) { originalStream = s; newStream = new MemoryStream(); return newStream; } private static void CopyStream(Stream from, Stream to) { TextReader reader = new StreamReader(from); TextWriter writer = new StreamWriter(to); writer.WriteLine(reader.ReadToEnd()); writer.Flush(); } } 

Finally, we mark all the methods in the Reference.cs file that will deserialize the long array with our attribute:

  [SoapRpcMethod("", RequestNamespace="http://some.service.provider", ResponseNamespace="http://some.service.provider")] [return : SoapElement("getFooReturn")] [LongArrayHelper] public Foo getFoo() { object[] results = Invoke("getFoo", new object[0]); return ((Foo) (results[0])); } 

This fix is ​​long-specific, but it can probably be generalized to handle any primitive type that has this problem.

+6
source

Here is a more or less copied version of the blog post I wrote on this topic.

Summary. You can either change the way NET deserializes the result set (see Chris' solution above), or you can reconfigure Axis to serialize your results in a way that is compatible with the .NET SOAP implementation.

If you go the last route, here's how:

... the generated classes look and seem normal, but if you look at the deserialized array on the client (.NET / WCF), you will find that the array is deserialized incorrectly and all the values ​​in the array are 0. You will have to manually look at the SOAP response, returned Axis to understand what happened; here's a sample answer (again, edited for clarity):

 <?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv=http://schemas.xmlsoap.org/soap/envelope/> <soapenv:Body> <doSomethingResponse> <doSomethingReturn> <doSomethingReturn href="#id0"/> <doSomethingReturn href="#id1"/> <doSomethingReturn href="#id2"/> <doSomethingReturn href="#id3"/> <doSomethingReturn href="#id4"/> </doSomethingReturn> </doSomethingResponse> <multiRef id="id4">5</multiRef> <multiRef id="id3">4</multiRef> <multiRef id="id2">3</multiRef> <multiRef id="id1">2</multiRef> <multiRef id="id0">1</multiRef> </soapenv:Body> </soapenv:Envelope> 

You will notice that Axis does not directly generate values ​​in the returned element, but instead references external elements for the value. This may make sense when there are many references to relatively few discrete values, but regardless of the fact that this is not handled properly by the WCF basicHttpBinding provider (and, as reported, gSOAP and classic .NET web links).

It took me a while to find a solution: edit the deployment of Axis server-config.wsdd and find the following parameter:

 <parameter name="sendMultiRefs" value="true"/> 

Change it to false, then relocate through the command line that looks (under Windows) something like this:

 java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient server-config.wsdl 

The web service response should now be deserialized by your .NET client.

+3
source

Found this link that may offer a better alternative: http://www.tomergabel.com/GettingWCFAndApacheAxisToBeFriendly.aspx

+1
source

Source: https://habr.com/ru/post/1276453/


All Articles