解决WCF DataContract DataMember Order反序列化问题

问题描述 投票:0回答:1

与遗留 WCF 接口的一个用例是将所有 .NET 服务代码隐藏在更现代的 RESTful API 后面,我们将(看似正确)XML 发布到该 API,并将其反序列化为我们需要的 WCF 类。

一个常见问题是由于 XML 元素的顺序错误而导致的反序列化错误。其中最微妙的是反序列化过程中无序元素被默默地丢弃,并且导致子类或属性值为空。

我们需要一个可行的解决方案,不涉及冒险删除

Order
属性或对服务类进行其他操作,因为它可能会对 WCF 服务的其余部分产生意想不到的后果。

我尝试使用反射来为给定的类创建这个有序列表。它似乎有效,但类继承会导致父元素以错误的顺序出现。

c# xml wcf xml-deserialization datacontractserializer
1个回答
1
投票

下面是我选择的方法 - 虽然有点繁重,但您只需为每个数据类执行一次,并且在故障排除时高度透明。

游戏的目的是在反序列化之前使用元素/XPath 的引用顺序对发布的 XML 进行排序。

如果您找到这篇文章,步骤 1 可能已经完成了

  • myservicename.wsdl
    文件一起使用,使用
    DataContractSerializer
    作为首选序列化器来生成所有服务引用类。
  • 找出您要将 XML 反序列化到哪个顶级数据类。

步骤2模式提取

  • 在 XML 编辑器中打开 WCF
    myservicename.wsdl
    ,然后找到包含数据类定义的架构,请注意,您需要通过它的
    DataContractAttribute Name
  • 来搜索它
  • 将架构保存到文件,并对任何子元素架构执行相同的操作(我有 6 个要处理!)

第 3 步 生成示例 Xml 文件(包括每个可能的元素)

  • 我使用了
    Altova XmlSpy
    ,但还有其他选择
  • 打开架构后,验证架构。这可能会涉及到跳过一些步骤,例如从
    wsdl
    中提取子架构/命名空间并保存它们,使用
    xs:import
    将它们添加到类架构中,并将
    schemaLocation
    设置为文件路径
  • 在验证架构之前不要继续操作!
  • 数据/架构菜单 -> 生成示例 Xml 文件
  • 它会提示要求元素用作示例的根 - 这是您选择类的地方
  • 确保生成的示例通过模式验证

步骤 4 在 C# 中准备 Xml 排序。将示例 XML 转换为 XPath + 订单字典

    internal class SampleXmlHelpers
    {
        /// <summary>
        /// Use a model sample Xml to obtain the order/sequencing of elements that comply with a given Xsd
        /// This will be sorted later to sort incoming Xml before deserializing
        /// </summary>
        /// <returns></returns>
        public static Dictionary<string, int> GetXmlXPathOrderDic(Type classType)
        {

            const string subFolder = "Schemas";

            var binDirectory = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);            
            var baseDirectory = Path.GetFullPath(Path.Combine(binDirectory, subFolder));

            var xmlFileName = string.Empty;

            if (classType.Name == "HhClaimAddRequestType")
            {
                xmlFileName = "HhClaimAddRequestType-XmlSpy-Sample.xml";
            }
            else if (classType.Name == "HhClaimUpdateRequestType")
            {
                xmlFileName = "HhClaimUpdateRequestType-XmlSpy-Sample.xml";
            }
            else
            {
                throw new ArgumentOutOfRangeException(nameof(classType));
            }

            string xml = File.ReadAllText(Path.Combine(baseDirectory, xmlFileName));

            var doc = new XmlDocument();
            //doc.LoadXml(Path.Combine(baseDirectory, xmlFileName));
            doc.LoadXml(xml);

            var dic = ExtractXPathsAndSequence(doc);

            return dic;
        }
        private static Dictionary<string, int> ExtractXPathsAndSequence(XmlDocument document)
        {
            Dictionary<string, int> xpaths = new Dictionary<string, int>();
            int sequenceNumber = 0;  // Initial sequence number

            // Recursive function to traverse the XML nodes
            void Traverse(XmlNode node, string currentPath)
            {
                if (node.NodeType == XmlNodeType.Element)
                {
                    int index = GetNodePosition(node);
                    //string newPath = $"{currentPath}/{node.Name}[{index}]";
                    string newPath = $"{currentPath}/{node.Name}";

                    if (!xpaths.ContainsKey(newPath))
                    {
                        xpaths[newPath] = sequenceNumber++;
                    }

                    // Recurse for child nodes
                    foreach (XmlNode childNode in node.ChildNodes)
                    {
                        Traverse(childNode, newPath);
                    }
                }
            }

            Traverse(document.DocumentElement, "");
            return xpaths;
        }

        // Helper function to get the position of a node among its siblings of the same name
        private static int GetNodePosition(XmlNode node)
        {
            if (node.ParentNode == null)
            {
                return 1;
            }

            int index = 1;  // XPath index starts from 1
            foreach (XmlNode sibling in node.ParentNode.ChildNodes)
            {
                if (sibling == node)
                {
                    return index;
                }
                if (sibling.Name == node.Name)
                {
                    index++;
                }
            }

            return -1;  // Should not reach here unless there's a problem with the XML structure
        }
    }

Step5 在反序列化之前对传入的 Posted XML 进行排序

        public static string SortXmlElements(string xmlContent, Dictionary<string, int> sortingDic)
        {
            XDocument xmlDoc = XDocument.Parse(xmlContent);

            XDocument sortedXmlDoc = new XDocument(
                xmlDoc.Declaration,
                SortElementsByXPathOrder(xmlDoc.Root, sortingDic)
            );

            return sortedXmlDoc.ToString();
        }

        static XElement SortElementsByXPathOrder(XElement element, Dictionary<string, int> sortingDic)
        {
            if (!element.HasElements)
            {
                return new XElement(element.Name, element.Attributes(), element.Value); // Leaf node with its value
            }

            var sortedElements = element.Elements()
                .OrderBy(e => sortingDic[GetSimpleXPathWithNamespacePrefix(e)]) // Sort by Schema order 
                .Select(e => SortElementsByXPathOrder(e, sortingDic));

            XElement newElement = new XElement(
                element.Name,
                element.Attributes(),
                sortedElements
            );

            // Remove the original nodes after copying them to the sorted element
            element.Nodes().Remove();

            return newElement;
        }

        static string GetSimpleXPathWithNamespacePrefix(XElement element)
        {
            return "/" + string.Join("/", element.AncestorsAndSelf().Reverse().Select(e =>
            {
                string prefix = e.GetPrefixOfNamespace(e.Name.Namespace);
                return !string.IsNullOrEmpty(e.Name.NamespaceName) && !string.IsNullOrEmpty(prefix)
                       ? prefix + ":" + e.Name.LocalName
                       : e.Name.LocalName;
            }));
        }
© www.soinside.com 2019 - 2024. All rights reserved.