skip to Main Content

My C# project is a class library that provides an easy to use interface to Magento 2. Consumers of the library call methods such as DownloadSalesOrder(string orderId) etc. Internally, the library has a Service Reference to a standard Magento 2 SOAP API WSDL. Visual Studio generates all the code necessary to interact with the service and deserialize the XML in the file Reference.cs. All classes in Reference.cs are declared partial.

All was working well until I discovered that one of the elements (called <extensionAttributes>) within the sales order type was customisable in Magento. For example, one API will return this:

            <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>

… while another API could return a completely different structure.

The library is intended to be usable with any API, and consuming applications need this data, but obviously the library has no knowledge of the possible data types. The consuming application however would know the data type.

Could I somehow map extensionAttributes to:

  1. a string property, and return the inner XML as string, or
  2. a generically typed property, that the consuming application passes in, e.g. DownloadSalesOrder<MyExtensionAttributes>(string orderId)

All I have access to is hand editing Reference.cs and/or extending via partial classes.

I tried idea #1 by adding a string property like below, but this causes an exception: Error in deserializing body of reply message for operation ‘salesOrderRepositoryV1GetList’ (which I am not surprised by as <extensionAttributes> contains un-escaped 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");
            }
        }
    }

Re. idea #2, I cannot make the above partial class generic, i.e. public partial class SalesDataOrderInterface<T> where T : class, because then it is no longer an extension of the original class.

2

Answers


  1. Chosen as BEST ANSWER

    I could not find a simple solution, but the following worked:

    1. For every type needing an extensionAttributes property, create a partial class that adds the property with string type (i.e. that extends the existing partial class inside Reference.cs). For example:
        public partial class SalesDataOrderInterface
        {
            public string extensionAttributes { get; set; }
        }
    
    1. Create a WCF message inspector that intercepts the incoming XML and rewrites it, finding any elements and escaping the inner XML by wrapping it in a CDATA block. All elements therefore map to the string property created above. Here is the relevant method of the 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);
            }
    
    

    Consuming applications can un-wrap and deserialise the string as needed.


  2. Try following :

    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:temptest.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; }
        }
    
     
    }
    

    Here is 2nd solution

    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:temptest.xml";
            const string OUTPUT_FILENAME = @"c:temptest1.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);
            }
    
    
        }
    }
    
    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search