与遗留 WCF 接口的一个用例是将所有 .NET 服务代码隐藏在更现代的 RESTful API 后面,我们将(看似正确)XML 发布到该 API,并将其反序列化为我们需要的 WCF 类。
一个常见问题是由于 XML 元素的顺序错误而导致的反序列化错误。其中最微妙的是反序列化过程中无序元素被默默地丢弃,并且导致子类或属性值为空。
我们需要一个可行的解决方案,不涉及冒险删除
Order
属性或对服务类进行其他操作,因为它可能会对 WCF 服务的其余部分产生意想不到的后果。
我尝试使用反射来为给定的类创建这个有序列表。它似乎有效,但类继承会导致父元素以错误的顺序出现。
下面是我选择的方法 - 虽然有点繁重,但您只需为每个数据类执行一次,并且在故障排除时高度透明。
游戏的目的是在反序列化之前使用元素/XPath 的引用顺序对发布的 XML 进行排序。
如果您找到这篇文章,步骤 1 可能已经完成了
myservicename.wsdl
文件一起使用,使用 DataContractSerializer
作为首选序列化器来生成所有服务引用类。步骤2模式提取
myservicename.wsdl
,然后找到包含数据类定义的架构,请注意,您需要通过它的 DataContractAttribute Name
第 3 步 生成示例 Xml 文件(包括每个可能的元素)
Altova XmlSpy
,但还有其他选择wsdl
中提取子架构/命名空间并保存它们,使用 xs:import
将它们添加到类架构中,并将 schemaLocation
设置为文件路径步骤 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;
}));
}