.NET が Web サービスからプリミティブ配列を逆シリアル化しないのはなぜですか?
質問
ヘルプ!C# アプリケーションによって使用される Axis Web サービスがあります。長い値の配列が常に [0,0,0,0] (正しい長さ) として表示されることを除いて、すべてがうまく機能しますが、値は逆シリアル化されません。他のプリミティブ(int、double)でも試してみましたが、同じことが起こります。私は何をしますか?自分のサービスのセマンティクスを変更したくありません。
解決
これが私が最終的に得たものです。これ以外の解決策を私は見つけたことがないので、より良い解決策がある場合は、ぜひ貢献してください。
まず、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 が double 値を含む要素への multiref ID をたどっていないことだったようです。したがって、配列項目を処理し、値を見つけて、その値を要素に挿入します。
[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]));
}
この修正は長い間特有のものですが、おそらくこの問題を抱えているあらゆるプリミティブ型を処理できるように一般化できるでしょう。
他のヒント
これは、多かれ少なかれコピー&ペーストしたバージョンです ブログ投稿 というお題で書きました。
エグゼクティブサマリー:.NET が結果セットを逆シリアル化する方法を変更するか (上記の Chris の解決策を参照)、.NET SOAP 実装と互換性のある方法で結果をシリアル化するように Axis を再構成することができます。
後者のルートを選択する場合、その方法は次のとおりです。
...生成された クラスが機能しているように見え、機能しているように見える 通常は クライアント上の逆シリアル化された配列 (.NET/WCF) 側では、 配列が逆シリアル化されました 正しくなく、 array は 0 です。手動で行う必要があります によって返されるSOAP応答を見てください。 何が問題なのかを理解するための軸。以下は応答の例です (ここでも、 わかりやすくするために編集):
<?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>
Axis は 値を直接生成します。 要素を返したのではなく、 外部要素の参照 価値観。これは、次のような場合に意味があります。 への多くの参照があります 離散値は比較的少ないが、 いずれにせよ、これは正しくありません WCF basicHttpBinding によって処理される プロバイダー (と報告されているのは gSOAP と 従来の .NET Web 参照も同様です)。
解決策を見つけるまでにしばらく時間がかかりました。Axis デプロイメントの server-config.wsdd ファイルを開き、 次のパラメータ:
<parameter name="sendMultiRefs" value="true"/>
false に変更し、 次に、コマンドラインから再デプロイします。 これは(Windowsの下で)何かに見えます このように:
java -cp %AXISCLASSPATH% org.apache.axis.client.AdminClient server-config.wsdl
Web サービスの これで、応答が逆シリアル化可能になります NETクライアントによって。
より良い代替手段を提供する可能性のあるこのリンクを見つけました。 http://www.tomergabel.com/GettingWCFAndApacheAxisToBeFriendly.aspx