Perché .NET non deserializza il mio array primitivo da un servizio Web?
Domanda
Aiuto!Ho un servizio Web Axis utilizzato da un'applicazione C#.Tutto funziona alla grande, tranne per il fatto che gli array di valori lunghi vengono sempre visualizzati come [0,0,0,0]: la lunghezza giusta, ma i valori non vengono deserializzati.Ho provato con altri primitivi (int, double) e succede la stessa cosa.Cosa devo fare?Non voglio cambiare la semantica del mio servizio.
Soluzione
Ecco cosa ho ottenuto.Non ho mai trovato un'altra soluzione là fuori per questo, quindi se hai qualcosa di meglio, contribuisci con tutti i mezzi.
Innanzitutto, la definizione dell'array lungo nell'area wsdl:types:
<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>
Successivamente, creiamo un SoapExtensionAttribute che eseguirà la correzione.Sembra che il problema fosse che .NET non seguiva l'id multiref nell'elemento contenente il valore double.Quindi, elaboriamo l'elemento dell'array, troviamo il valore e quindi inseriamo il valore nell'elemento:
[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();
}
}
Infine, contrassegniamo tutti i metodi nel file Reference.cs che deserializzeranno un lungo array con il nostro attributo:
[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]));
}
Questa correzione è specifica per molto tempo, ma probabilmente potrebbe essere generalizzata per gestire qualsiasi tipo primitivo che presenta questo problema.
Altri suggerimenti
Ecco una versione più o meno copia-incollata di a post sul blog Ho scritto sull'argomento.
Sintesi:Puoi modificare il modo in cui .NET deserializza il set di risultati (vedi la soluzione di Chris sopra) oppure riconfigurare Axis per serializzare i risultati in un modo compatibile con l'implementazione SOAP .NET.
Se segui quest'ultima strada, ecco come:
...Le classi generate sembrano e sembrano funzionare normalmente, ma se guarderai l'array deserializzato sul lato client (.NET/WCF) scoprirai che l'array è stato deserializzato in modo errato e tutti i valori nell'array sono 0 .Dovrai guardare manualmente la risposta del sapone restituita dall'asse per capire cosa non va;Ecco una risposta di esempio (di nuovo, modificata per chiarezza):
<?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>
Noterai che l'asse non genera valori direttamente nell'elemento restituito, ma fa invece riferimenti a elementi esterni per i valori.Ciò potrebbe avere senso quando ci sono molti riferimenti a relativamente pochi valori discreti, ma in ogni caso questo non è gestito correttamente dal fornitore WCF BasichttpBinding (e secondo quanto riferito da GSOAP e anche classici riferimenti Web .NET).
Ci ho messo un po' a trovare una soluzione:Modifica il file server-config.wsdd di Disployment Axis e trova il seguente parametro:
<parameter name="sendMultiRefs" value="true"/>
Cambialo in falso, quindi ridistribuita tramite la riga di comando, che assomiglia (sotto Windows) qualcosa del genere:
java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient server-config.wsdl
La risposta del servizio Web dovrebbe ora essere deserializzabile dal tuo client .NET.
Ho trovato questo link che potrebbe offrire un'alternativa migliore: http://www.tomergabel.com/GettingWCFEndApacheAxisToBeFriendly.aspx