将XmlDocument的一部分反序列化为对象

时间:2018-04-20 01:49:44

标签: c# xml serialization xmlserializer

我经常看到这个问题,但没有人的标题似乎真的描绘了他们的问题。我从包含一般响应信息的Web API获取了一个大型响应对象,以及我想要反序列化的数据对象。

完整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对象类型是动态的,具体取决于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反序列化到我的对象。相反,我得到一个新的空白对象。想法?

对象类型可以更改,这是我正在处理的当前对象:(请注意,我在Web API中对modelData中的确切xml进行反序列化)

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


}

2 个答案:

答案 0 :(得分:0)

对于巨大的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();
            }
        }
    }
}

答案 1 :(得分:0)

Firstly, XmlSerializer is case sensitive. Thus your property names need to match the XML element names exactly -- unless overridden with an 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/ which resulted in:

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

(After generating the model, I modified the class names to match your naming convention.)

Given the correct data model, you can deserialize directly from a selected XmlNode using an XmlNodeReader as shown in How to deserialize a node in a large document using XmlSerializer without having to re-serialize to an intermediate XML string. The following extension method does the trick:

public static partial class XmlNodeExtensions
{
    public static IEnumerable<T> DeserializeElements<T>(this XmlNode root, string localName, string namespaceUri)
    {
        var serializer = XmlSerializerFactory.Create(typeof(T), localName, namespaceUri);
        var reader = new XmlNodeReader(root);
        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.
    // https://stackoverflow.com/questions/23897145/memory-leak-using-streamreader-and-xmlserializer
    // This factory taken from 
    // https://stackoverflow.com/questions/34128757/wrap-properties-with-cdata-section-xml-serialization-c-sharp/34138648#34138648

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

Then you would deserialize as follows:

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

Working sample .Net fiddle here.