将 XmlDocument 的一部分反序列化为 object

Deserialize portion of XmlDocument into object

我经常看到这个问题,但没有人的标题似乎真的描述了他们的问题。我从 Web API 收到大量响应 object,其中包含一般响应信息以及我要反序列化的数据 object。

完整 XML:

<?xml version="1.0"?>
<root>
  <status>
      <apiErrorCode>0</apiErrorCode>
      <apiErrorMessage/>
      <dbErrorCode>0</dbErrorCode>
      <dbErrorMessage/>
      <dbErrorList/>
  </status>
<data>
    <modelName>ReportXDTO</modelName>
    <modelData>
        <id>1780</id>
        <reportTitle>Access Level (select) with Door Assignment</reportTitle>
        <hasParameters>true</hasParameters>
        <parameters>
            <dataType>STRING</dataType>
            <title>Access Level:</title>
            <index>1</index>
            <allowMulti>true</allowMulti>
            <selectSql>SELECT DISTINCT [Name] FROM dbo.[Levels] WHERE [PrecisionFlag] = '0' ORDER BY [Name] </selectSql>
            <values>
                <value>Door 1</value>
                <used>1</used>
            </values>
            <values>
                <value>Door 2</value>
                <used>1</used>
            </values>
            <values>
                <value>Door 3</value>
                <used>1</used>
            </values>
       </parameters>
       <sourceSql>SELECT [Name], [SData] FROM [Schedules]</sourceSql>
       <report/>
   </modelData>
   <itemReturned>1</itemReturned>
   <itemTotal>1</itemTotal>
</data>
<listInfo>
    <pageIdRequested>1</pageIdRequested>
    <pageIdCurrent>1</pageIdCurrent>
    <pageIdFirst>1</pageIdFirst>
    <pageIdPrev>1</pageIdPrev>
    <pageIdNext>1</pageIdNext>
    <pageIdLast>1</pageIdLast>
    <itemRequested>1</itemRequested>
    <itemReturned>1</itemReturned>
    <itemStart>1</itemStart>
    <itemEnd>1</itemEnd>
    <itemTotal>1</itemTotal>
</listInfo>
</root>

我只想反序列化 modelData 元素。 modelData object 类型是动态的,取决于 API 调用。

我在其他应用程序中反序列化 xml,并创建了以下方法,但不知道具体如何只获取 modelData 元素:

    public static T ConvertXmltoClass<T>(HttpResponseMessage http, string elementName) where T : new()
    {
        var newClass = new T();

        try
        {
            var doc = JsonConvert.DeserializeXmlNode(http.Content.ReadAsStringAsync().Result, "root");

            XmlReader reader = new XmlNodeReader(doc);
            reader.ReadToFollowing(elementName);

            //The xml needs to show the proper object name
            var xml = reader.ReadOuterXml().Replace(elementName, newClass.GetType().Name);

            using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xml)))
            {
                var serializer = new XmlSerializer(typeof(T));
                newClass = (T)serializer.Deserialize(stream);
            }
        }
        catch (Exception e)
        {
            AppLog.LogException(System.Reflection.MethodBase.GetCurrentMethod().Name, e);
        }

        return newClass;
    }

我已经多次更新此线程以保持最新状态。我开始用第一个解决方案更新它。但是这个解决方案本身并没有解决问题。使用现在的代码,我没有例外,但不要将 xml 反序列化为我的 object。相反,我得到了一个新的空白 object。想法?

虽然 object 类型可以更改,但这是我当前正在处理的 object:(请注意,我反序列化了 modelData 中的确切 xml,在 Web API)

namespace WebApiCommon.DataObjects
{
    [Serializable]
    public class ReportXDto
    {
        public ReportXDto()
        {
            Parameters = new List<ReportParameterXDto>();
        }

        public int Id { get; set; }
        public string ReportTitle { get; set; }
        public bool HasParameters { get; set; } = false;
        public List<ReportParameterXDto> Parameters { get; set; }
        public string SourceSql { get; set; }
        public DataTable Report { get; set; }
    }

    [Serializable]
    public class ReportXDto
    {
        public ReportXDto()
        {
            Parameters = new List<ReportParameterXDto>();
        }

        public int Id { get; set; }
        public string ReportTitle { get; set; }
        public bool HasParameters { get; set; } = false;
        public List<ReportParameterXDto> Parameters { get; set; }
        public string SourceSql { get; set; }
        public DataTable Report { get; set; }
    }

    [Serializable]
    public class ReportParameterValuesXDto
    {
        public string Value { get; set; } = "";
        public bool Used { get; set; } = false;
    }


}

对于巨大的 xml 文件始终使用 XmlReader,因此您不会遇到内存不足的问题。请参阅下面的代码以将元素作为字符串获取:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;

namespace ConsoleApplication1
{
    class Program
    {
        const string FILENAME = @"c:\temp\test.xml";
        static void Main(string[] args)
        {
            //or Create(Stream)
            XmlReader reader = XmlReader.Create(FILENAME);

            reader.ReadToFollowing("modelData");
            if (!reader.EOF)
            {
                string modelDataStr = reader.ReadOuterXml();
            }
        }
    }
}

首先,XmlSerializer区分大小写。因此,您的 属性 名称需要与 XML 元素名称完全匹配——除非被 attribute that controls XML serialization such as [XmlElement(ElementName="id")]. To generate a data model with the correct casing I used http://xmltocsharp.azurewebsites.net/ 覆盖,导致:

public class ReportParameterValuesXDto 
{
    [XmlElement(ElementName="value")]
    public string Value { get; set; }
    [XmlElement(ElementName="used")]
    public string Used { get; set; }
}

public class ReportParametersXDto 
{
    [XmlElement(ElementName="dataType")]
    public string DataType { get; set; }
    [XmlElement(ElementName="title")]
    public string Title { get; set; }
    [XmlElement(ElementName="index")]
    public string Index { get; set; }
    [XmlElement(ElementName="allowMulti")]
    public string AllowMulti { get; set; }
    [XmlElement(ElementName="selectSql")]
    public string SelectSql { get; set; }
    [XmlElement(ElementName="values")]
    public List<ReportParameterValuesXDto> Values { get; set; }
}

public class ReportXDto 
{
    [XmlElement(ElementName="id")]
    public string Id { get; set; }
    [XmlElement(ElementName="reportTitle")]
    public string ReportTitle { get; set; }
    [XmlElement(ElementName="hasParameters")]
    public string HasParameters { get; set; }
    [XmlElement(ElementName="parameters")]
    public ReportParametersXDto Parameters { get; set; }
    [XmlElement(ElementName="sourceSql")]
    public string SourceSql { get; set; }
    [XmlElement(ElementName="report")]
    public string Report { get; set; }
}

(生成模型后,我修改了 class 名称以符合您的命名约定。)

给定正确的数据模型,您可以使用 XmlNodeReader as shown in 从选定的 XmlNode 直接反序列化,而无需重新序列化为中间 XML 字符串。以下扩展方法可以解决问题:

public static partial class XmlExtensions
{
    public static IEnumerable<T> DeserializeElements<T>(this XmlNode root, string localName, string namespaceUri)
    {
        return new XmlNodeReader(root).DeserializeElements<T>(localName, namespaceUri);
    }

    public static IEnumerable<T> DeserializeElements<T>(this XmlReader reader, string localName, string namespaceUri)
    {
        var serializer = XmlSerializerFactory.Create(typeof(T), localName, namespaceUri);
        while (!reader.EOF)
        {
            if (!(reader.NodeType == XmlNodeType.Element && reader.LocalName == localName && reader.NamespaceURI == namespaceUri))
                reader.ReadToFollowing(localName, namespaceUri);

            if (!reader.EOF)
            {
                yield return (T)serializer.Deserialize(reader);
                // Note that the serializer will advance the reader past the end of the node
            }               
        }
    }
}

public static class XmlSerializerFactory
{
    // To avoid a memory leak the serializer must be cached.
    // 
    // This factory taken from 
    // 

    readonly static Dictionary<Tuple<Type, string, string>, XmlSerializer> cache;
    readonly static object padlock;

    static XmlSerializerFactory()
    {
        padlock = new object();
        cache = new Dictionary<Tuple<Type, string, string>, XmlSerializer>();
    }

    public static XmlSerializer Create(Type serializedType, string rootName, string rootNamespace)
    {
        if (serializedType == null)
            throw new ArgumentNullException();
        if (rootName == null && rootNamespace == null)
            return new XmlSerializer(serializedType);
        lock (padlock)
        {
            XmlSerializer serializer;
            var key = Tuple.Create(serializedType, rootName, rootNamespace);
            if (!cache.TryGetValue(key, out serializer))
                cache[key] = serializer = new XmlSerializer(serializedType, new XmlRootAttribute { ElementName = rootName, Namespace = rootNamespace });
            return serializer;
        }
    }
}

然后你将反序列化如下:

var modelData = doc.DeserializeElements<ReportXDto>("modelData", "").FirstOrDefault();

工作示例 .Net fiddle here.