java - read - ¿Por qué no.NET deserializará mi matriz primitiva desde un servicio web?
sockets example java (3)
¡Ayuda! Tengo un servicio web de Axis que está siendo consumido por una aplicación C #. Todo funciona muy bien, excepto que las matrices de valores largos siempre aparecen como [0,0,0,0] - la longitud correcta, pero los valores no se deserializan. Lo he intentado con otros primitivos (ints, dobles) y sucede lo mismo. ¿Qué debo hacer? No quiero cambiar la semántica de mi servicio.
Esto es con lo que terminé. Nunca he encontrado otra solución para esto, así que si tienes algo mejor, por supuesto, contribuye.
En primer lugar, la definición de matriz larga en el área wsdl: tipos:
<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>
A continuación, creamos un SoapExtensionAttribute que realizará la corrección. Parece que el problema era que .NET no estaba siguiendo el ID multirrep al elemento que contenía el doble valor. Entonces, procesamos el elemento del arreglo, buscamos el valor y luego lo insertamos en el 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();
}
}
Finalmente, etiquetamos todos los métodos en el archivo Reference.cs que deserializará una matriz larga con nuestro atributo:
[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]));
}
Esta solución es larga específica, pero probablemente podría generalizarse para manejar cualquier tipo primitivo que tenga este problema.
Encontrado este enlace que puede ofrecer una mejor alternativa: http://www.tomergabel.com/GettingWCFAndApacheAxisToBeFriendly.aspx
Aquí hay una versión más o menos copiada de una publicación de blog que escribí sobre el tema.
Resumen ejecutivo: puede cambiar la forma en que .NET deserializa el conjunto de resultados (vea la solución de Chris anterior), o puede reconfigurar Axis para serializar sus resultados de una manera que sea compatible con la implementación .NET SOAP.
Si vas por la última ruta, aquí te mostramos cómo:
... las clases generadas se ven y parecen funcionar normalmente, pero si observa la matriz deserializada en el lado del cliente (.NET / WCF) encontrará que la matriz se ha deserializado incorrectamente, y todos los valores en el array es 0. Tendrá que mirar manualmente la respuesta SOAP devuelta por Axis para descubrir qué está mal; Aquí hay una respuesta de muestra (nuevamente, editada para mayor claridad):
<?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>
Notará que Axis no genera valores directamente en el elemento devuelto, sino que hace referencia a elementos externos para los valores. Esto puede tener sentido cuando hay muchas referencias a relativamente pocos valores discretos, pero cualquiera que sea el caso, esto no es manejado adecuadamente por el proveedor WCF basicHttpBinding (y según se informa por las referencias web gSOAP y classic .NET también).
Me tomó un tiempo encontrar una solución: edite el archivo server-config.wsdd de su implementación de Axis y encuentre el siguiente parámetro:
<parameter name="sendMultiRefs" value="true"/>
Cámbielo a falso, luego vuelva a implementarlo a través de la línea de comando, que se ve (en Windows) algo como esto:
java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient server-config.wsdl
La respuesta del servicio web ahora debería ser deserializable por su cliente .NET.