为什么 .NET 不会从 Web 服务反序列化我的原始数组?
题
帮助!我有一个 C# 应用程序正在使用的 Axis Web 服务。一切都很好,除了长值数组总是显示为 [0,0,0,0] - 正确的长度,但值没有反序列化。我尝试过使用其他原语(整数、双精度数),并且发生了同样的事情。我该怎么办?我不想改变我的服务的语义。
解决方案
这就是我最终得到的结果。我从来没有找到其他解决方案,所以如果你有更好的东西,请务必贡献。
首先,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>
接下来,我们创建一个将执行修复的 SoapExtensionAttribute。问题似乎在于 .NET 没有遵循 multiref id 到包含 double 值的元素。因此,我们处理数组项,找到值,然后将值插入到元素中:
[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();
}
}
最后,我们在 Reference.cs 文件中标记所有将使用我们的属性反序列化长数组的方法:
[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]));
}
此修复是特定于 long 的,但它可能可以推广到处理具有此问题的任何原始类型。
其他提示
这是或多或少复制粘贴的版本 博客文章 我写了关于这个主题的文章。
执行摘要:您可以更改 .NET 反序列化结果集的方式(请参阅上面 Chris 的解决方案),也可以重新配置 Axis 以与 .NET SOAP 实现兼容的方式序列化其结果。
如果您选择后一条路线,请按以下步骤操作:
...生成的类看起来和看起来正常工作,但是如果您查看客户端(.NET/WCF)侧的避难数组。您必须手动查看Axis返回的肥皂反应,以找出出了什么问题。这是一个示例响应(再次为清晰而编辑):
<?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>
您会注意到轴不会直接在返回的元素中生成值,而是引用值的外部元素。当有很多引用相对较少的离散值的引用时,这可能是有道理的,但是无论如何,WCF basichttpbinding提供商都无法正确处理这种情况(据报道,GSOAP和Classic .NET Web Reforence也是如此)。
我花了一段时间才找到解决方案:编辑轴部署的server-config.wsdd文件并找到以下参数:
<parameter name="sendMultiRefs" value="true"/>
将其更改为false,然后通过命令行重新部署,该命令行(在Windows下)看起来像这样:
java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient server-config.wsdl
现在,您的.NET客户端应该可以应对Web服务的响应。
发现这个链接可能提供更好的选择: http://www.tomergabel.com/GettingWCFAndApacheAxisToBeFriendly.aspx