XML 反序列化为已知类型,其中一个元素的结构是可变的(在 SOAP 服务参考中)
XML deserialization to known type where structure of one element is variable (in SOAP Service Reference)
我的 C# 项目是一个 class 库,它提供了一个易于使用的 Magento 2 接口。库调用方法的消费者,例如 DownloadSalesOrder(string orderId) 等。在内部,库有一个服务参考到标准的 Magento 2 SOAP API WSDL。 Visual Studio 生成与服务交互和反序列化文件 Reference.cs 中的 XML 所需的所有代码。 Reference.cs 中的所有 class 都声明为部分。
在我发现销售订单类型中的元素之一(称为 )可以在 Magento 中自定义之前,一切都运行良好。例如,一个 API 将 return 这个:
<extensionAttributes>
<paymentAdditionalInfo>
<item>
<key>processorResponseText</key>
<value>Approved</value>
</item>
<item>
<key>cc_type</key>
<value>Visa</value>
</item>
</paymentAdditionalInfo>
<giftCards>
<item>
<id>14</id>
<code>0BVH6GOQ291C</code>
<amount>20</amount>
<baseAmount>20</baseAmount>
</item>
</giftCards>
</extensionAttributes>
...而另一个 API 可以 return 一个完全不同的结构。
该库旨在与任何 API 一起使用,并且使用该数据的应用程序需要此数据,但显然该库不知道可能的数据类型。然而,消费应用程序会知道数据类型。
我能否以某种方式将 extensionAttributes 映射到:
- 一个字符串属性,并且return内部XML作为字符串,或者
- 消费应用程序传入的通用类型 属性,例如下载销售订单
(string orderId)
我只能手动编辑 Reference.cs and/or 通过部分 classes 扩展。
我通过添加如下所示的字符串 属性 尝试了想法 #1,但这会导致异常:反序列化操作 'salesOrderRepositoryV1GetList'[=36 的回复消息正文时出错=](我对此并不感到惊讶,因为 包含未转义的 XML)。
public partial class SalesDataOrderInterface
{
private string extensionAttributesField;
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=136)]
public string extensionAttributes
{
get {
return this.extensionAttributesField;
}
set {
this.extensionAttributesField = value;
this.RaisePropertyChanged("extensionAttributes");
}
}
}
回复。想法 #2,我不能使上面的部分 class 通用,即 public 部分 class SalesDataOrderInterface where T : class,因为那样它就不再是扩展原来的 class.
尝试以下操作:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication167
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(extensionAttributes));
extensionAttributes attributes = (extensionAttributes)serializer.Deserialize(reader);
}
}
public class extensionAttributes
{
[XmlArray("paymentAdditionalInfo")]
[XmlArrayItem("item")]
public List<paymentAdditionalInfo> paymentAdditionalInfo { get; set; }
[XmlArray("giftCards")]
[XmlArrayItem("item")]
public List<giftCardsItem> giftCarItem { get; set; }
}
public class paymentAdditionalInfo
{
public string key { get; set; }
public string value { get; set; }
}
public class giftCardsItem
{
public int id { get;set;}
public string code { get; set; }
public int amount { get; set; }
public int baseAmount { get; set; }
}
}
这是第二种解决方案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string INPUT_FILENAME = @"c:\temp\test.xml";
const string OUTPUT_FILENAME = @"c:\temp\test1.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(INPUT_FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(extensionAttributes));
extensionAttributes attributes = (extensionAttributes)serializer.Deserialize(reader);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(OUTPUT_FILENAME, settings);
serializer.Serialize(writer, attributes);
}
}
public class extensionAttributes : IXmlSerializable
{
public Dictionary<string, List<Dictionary<string,string>>> dict { get;set;}
// Xml Serialization Infrastructure
public void WriteXml (XmlWriter writer)
{
foreach (KeyValuePair<string, List<Dictionary<string, string>>> key in dict)
{
XElement attribute = new XElement(key.Key);
foreach (Dictionary<string, string> itemDict in key.Value)
{
XElement items = new XElement("item");
attribute.Add(items);
foreach (KeyValuePair<string, string> item in itemDict)
{
items.Add(new XElement(item.Key, item.Value));
}
}
attribute.WriteTo(writer);
}
}
public void ReadXml (XmlReader reader)
{
XElement elements = (XElement)XElement.ReadFrom(reader);
foreach (XElement element in elements.Elements())
{
string name = element.Name.LocalName;
List<Dictionary<string, string>> childDict = element.Elements("item")
.Select(x => x.Elements()
.GroupBy(y => y.Name.LocalName, z => (string)z)
.ToDictionary(y => y.Key, z => z.FirstOrDefault())
).ToList();
if (dict == null)
{
dict = new Dictionary<string,List<Dictionary<string,string>>>();
}
dict.Add(name, childDict);
}
}
public XmlSchema GetSchema()
{
return(null);
}
}
}
我找不到简单的解决方案,但以下方法有效:
- 对于每个需要扩展属性 属性 的类型,创建一个部分 class 添加 属性 字符串类型(即扩展 class 内的现有部分 class =25=]).例如:
public partial class SalesDataOrderInterface
{
public string extensionAttributes { get; set; }
}
- 创建一个 WCF 消息检查器,拦截传入的 XML 并重写它,查找任何元素并通过将其包装在 CDATA 块中来转义内部 XML。因此,所有元素都映射到上面创建的字符串 属性。下面是inspector的相关方法:
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Read reply XML
var replyAsXml = new XmlDocument();
var ms = new MemoryStream();
var writer = XmlWriter.Create(ms);
reply.WriteMessage(writer);
writer.Flush();
ms.Position = 0;
replyAsXml.Load(ms);
// Find any <extensionAttributes> elements and escape the XML so it can be deserialised as a single string property
// The contents of <extensionAttributes> are customisable in Magento, so we cannot know the type they map to
foreach (XmlNode extensionAttributesElement in replyAsXml.GetElementsByTagName("extensionAttributes"))
{
var elementXml = extensionAttributesElement.OuterXml;
foreach (XmlNode childNode in extensionAttributesElement.ChildNodes)
extensionAttributesElement.RemoveChild(childNode);
extensionAttributesElement.InnerText = string.Format("<![CDATA[{0}]]>", elementXml);
}
// Create the modified reply
ms.SetLength(0);
writer = XmlWriter.Create(ms);
replyAsXml.WriteTo(writer);
writer.Flush();
ms.Position = 0;
var reader = XmlReader.Create(ms);
reply = Message.CreateMessage(reader, int.MaxValue, reply.Version);
}
消费应用程序可以 un-wrap 并根据需要反序列化字符串。
我的 C# 项目是一个 class 库,它提供了一个易于使用的 Magento 2 接口。库调用方法的消费者,例如 DownloadSalesOrder(string orderId) 等。在内部,库有一个服务参考到标准的 Magento 2 SOAP API WSDL。 Visual Studio 生成与服务交互和反序列化文件 Reference.cs 中的 XML 所需的所有代码。 Reference.cs 中的所有 class 都声明为部分。
在我发现销售订单类型中的元素之一(称为
<extensionAttributes>
<paymentAdditionalInfo>
<item>
<key>processorResponseText</key>
<value>Approved</value>
</item>
<item>
<key>cc_type</key>
<value>Visa</value>
</item>
</paymentAdditionalInfo>
<giftCards>
<item>
<id>14</id>
<code>0BVH6GOQ291C</code>
<amount>20</amount>
<baseAmount>20</baseAmount>
</item>
</giftCards>
</extensionAttributes>
...而另一个 API 可以 return 一个完全不同的结构。
该库旨在与任何 API 一起使用,并且使用该数据的应用程序需要此数据,但显然该库不知道可能的数据类型。然而,消费应用程序会知道数据类型。
我能否以某种方式将 extensionAttributes 映射到:
- 一个字符串属性,并且return内部XML作为字符串,或者
- 消费应用程序传入的通用类型 属性,例如下载销售订单
(string orderId)
我只能手动编辑 Reference.cs and/or 通过部分 classes 扩展。
我通过添加如下所示的字符串 属性 尝试了想法 #1,但这会导致异常:反序列化操作 'salesOrderRepositoryV1GetList'[=36 的回复消息正文时出错=](我对此并不感到惊讶,因为
public partial class SalesDataOrderInterface
{
private string extensionAttributesField;
[System.Xml.Serialization.XmlElementAttribute(Form=System.Xml.Schema.XmlSchemaForm.Unqualified, Order=136)]
public string extensionAttributes
{
get {
return this.extensionAttributesField;
}
set {
this.extensionAttributesField = value;
this.RaisePropertyChanged("extensionAttributes");
}
}
}
回复。想法 #2,我不能使上面的部分 class 通用,即 public 部分 class SalesDataOrderInterface
尝试以下操作:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
namespace ConsoleApplication167
{
class Program
{
const string FILENAME = @"c:\temp\test.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(extensionAttributes));
extensionAttributes attributes = (extensionAttributes)serializer.Deserialize(reader);
}
}
public class extensionAttributes
{
[XmlArray("paymentAdditionalInfo")]
[XmlArrayItem("item")]
public List<paymentAdditionalInfo> paymentAdditionalInfo { get; set; }
[XmlArray("giftCards")]
[XmlArrayItem("item")]
public List<giftCardsItem> giftCarItem { get; set; }
}
public class paymentAdditionalInfo
{
public string key { get; set; }
public string value { get; set; }
}
public class giftCardsItem
{
public int id { get;set;}
public string code { get; set; }
public int amount { get; set; }
public int baseAmount { get; set; }
}
}
这是第二种解决方案
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Serialization;
using System.Xml.Schema;
using System.Xml.Linq;
namespace ConsoleApplication1
{
class Program
{
const string INPUT_FILENAME = @"c:\temp\test.xml";
const string OUTPUT_FILENAME = @"c:\temp\test1.xml";
static void Main(string[] args)
{
XmlReader reader = XmlReader.Create(INPUT_FILENAME);
XmlSerializer serializer = new XmlSerializer(typeof(extensionAttributes));
extensionAttributes attributes = (extensionAttributes)serializer.Deserialize(reader);
XmlWriterSettings settings = new XmlWriterSettings();
settings.Indent = true;
XmlWriter writer = XmlWriter.Create(OUTPUT_FILENAME, settings);
serializer.Serialize(writer, attributes);
}
}
public class extensionAttributes : IXmlSerializable
{
public Dictionary<string, List<Dictionary<string,string>>> dict { get;set;}
// Xml Serialization Infrastructure
public void WriteXml (XmlWriter writer)
{
foreach (KeyValuePair<string, List<Dictionary<string, string>>> key in dict)
{
XElement attribute = new XElement(key.Key);
foreach (Dictionary<string, string> itemDict in key.Value)
{
XElement items = new XElement("item");
attribute.Add(items);
foreach (KeyValuePair<string, string> item in itemDict)
{
items.Add(new XElement(item.Key, item.Value));
}
}
attribute.WriteTo(writer);
}
}
public void ReadXml (XmlReader reader)
{
XElement elements = (XElement)XElement.ReadFrom(reader);
foreach (XElement element in elements.Elements())
{
string name = element.Name.LocalName;
List<Dictionary<string, string>> childDict = element.Elements("item")
.Select(x => x.Elements()
.GroupBy(y => y.Name.LocalName, z => (string)z)
.ToDictionary(y => y.Key, z => z.FirstOrDefault())
).ToList();
if (dict == null)
{
dict = new Dictionary<string,List<Dictionary<string,string>>>();
}
dict.Add(name, childDict);
}
}
public XmlSchema GetSchema()
{
return(null);
}
}
}
我找不到简单的解决方案,但以下方法有效:
- 对于每个需要扩展属性 属性 的类型,创建一个部分 class 添加 属性 字符串类型(即扩展 class 内的现有部分 class =25=]).例如:
public partial class SalesDataOrderInterface
{
public string extensionAttributes { get; set; }
}
- 创建一个 WCF 消息检查器,拦截传入的 XML 并重写它,查找任何元素并通过将其包装在 CDATA 块中来转义内部 XML。因此,所有元素都映射到上面创建的字符串 属性。下面是inspector的相关方法:
public void AfterReceiveReply(ref Message reply, object correlationState)
{
// Read reply XML
var replyAsXml = new XmlDocument();
var ms = new MemoryStream();
var writer = XmlWriter.Create(ms);
reply.WriteMessage(writer);
writer.Flush();
ms.Position = 0;
replyAsXml.Load(ms);
// Find any <extensionAttributes> elements and escape the XML so it can be deserialised as a single string property
// The contents of <extensionAttributes> are customisable in Magento, so we cannot know the type they map to
foreach (XmlNode extensionAttributesElement in replyAsXml.GetElementsByTagName("extensionAttributes"))
{
var elementXml = extensionAttributesElement.OuterXml;
foreach (XmlNode childNode in extensionAttributesElement.ChildNodes)
extensionAttributesElement.RemoveChild(childNode);
extensionAttributesElement.InnerText = string.Format("<![CDATA[{0}]]>", elementXml);
}
// Create the modified reply
ms.SetLength(0);
writer = XmlWriter.Create(ms);
replyAsXml.WriteTo(writer);
writer.Flush();
ms.Position = 0;
var reader = XmlReader.Create(ms);
reply = Message.CreateMessage(reader, int.MaxValue, reply.Version);
}
消费应用程序可以 un-wrap 并根据需要反序列化字符串。