2008-08-27 9 views
4

Hilfe! Ich habe einen Axis-Webdienst, der von einer C# -Anwendung genutzt wird. Alles funktioniert gut, außer dass Arrays mit langen Werten immer als [0,0,0,0] - die richtige Länge, aber die Werte sind nicht deserialisiert. Ich habe es mit anderen Primitiven versucht (Ints, Doubles) und das Gleiche passiert. Was mache ich? Ich möchte die Semantik meines Dienstes nicht ändern.Warum wird .NET mein primitives Array nicht von einem Webdienst deserialisieren?

Antwort

6

Hier ist, was ich endete. Ich habe dafür nie eine andere Lösung gefunden. Wenn Sie also etwas Besseres haben, tragen Sie unbedingt dazu bei.

Zunächst wird die lange Felddefinition in der wsdl: types Gebiet:

<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> 

Als Nächstes erstellen wir eine Soapextensionattribute, die das Update durchführen wird. Es scheint, dass das Problem darin bestand, dass .NET die Multireff-ID nicht dem Element folgte, das den doppelten Wert enthielt. So haben wir das Array-Element zu verarbeiten, gehen Sie den Wert finden, und fügen Sie dann den Wert in das 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(); 
    } 
} 

Schließlich markieren wir alle Methoden in der Reference.cs Datei, die eine lange Reihe wird Deserialisieren mit unser Attribut:

Dieser Fix ist lang-spezifisch, aber es könnte wahrscheinlich verallgemeinert werden, um jeden primitiven Typ zu behandeln, der dieses Problem hat.

+0

wirkt wie ein Zauber, brauchen nur schade, dass wir generierten Code modifizieren, um das Attribut hinzufügen – Luuk

3

Hier ist eine mehr oder weniger Kopie kleistert Version eines blog post ich zu diesem Thema geschrieben.

Zusammenfassung: Sie können entweder die Art ändern, mit der .NET die Ergebnismenge deserialisiert (siehe die obige Lösung von Chris), oder Sie können Axis so rekonfigurieren, dass die Ergebnisse auf eine Weise serialisiert werden, die mit der .NET SOAP-Implementierung kompatibel ist.

Wenn Sie den letzten Weg gehen, gehen Sie wie folgt:

... die generierten Klassen aussehen und erscheinen normal funktionieren, aber wenn man sich den entserialisierten Array auf dem Client aussehen werde (.NET/WCF) Seite werden Sie feststellen, dass die Array wurde falsch deserialisiert, und alle Werte in der Array sind 0. Sie müssen manuell Blick auf die SOAP-Antwort von Achse zurückgegeben, um herauszufinden Was ist los mit dir; hier ist eine Probe Antwort (wieder für Klarheit bearbeitet):

<?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> 

Sie werden feststellen, dass Achse nicht Werte direkt im zurück Element erzeugen, sondern Referenzen externe Elemente für Werte. Dies könnte sinnvoll sein, wenn es viele Hinweise auf relativ wenige diskrete Werte sind, aber was auch immer der Fall ist dies nicht richtig ist von der WCF Basichttpbinding Anbieter behandelt (und angeblich von gSOAP und klassischen .NET Web-Referenzen als auch).

Es dauerte eine Weile, um eine Lösung zu finden: bearbeitet Axis Datei server-config.wsdd unter Deployment und finden Sie die folgenden Parameter:

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

ändert es falsch, dann reploy über die Befehlszeile, die (unter Windows) etwas aussieht wie folgt:

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

Der Antwort des Web-Service soll jetzt von Ihrem .NET-Client deserializable sein.