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 映射到:

  1. 一个字符串属性,并且return内部XML作为字符串,或者
  2. 消费应用程序传入的通用类型 属性,例如下载销售订单(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);
        }


    }
}

我找不到简单的解决方案,但以下方法有效:

  1. 对于每个需要扩展属性 属性 的类型,创建一个部分 class 添加 属性 字符串类型(即扩展 class 内的现有部分 class =25=]).例如:
    public partial class SalesDataOrderInterface
    {
        public string extensionAttributes { get; set; }
    }
  1. 创建一个 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 并根据需要反序列化字符串。