C# IXmlSerializable ReadXml实现

C# IXmlSerializable ReadXml实现,c#,.net,deserialization,xml-deserialization,ixmlserializable,C#,.net,Deserialization,Xml Deserialization,Ixmlserializable,在执行XML反序列化时,我试图将XML标记的名称放入类属性中。我需要将名称作为属性,因为多个XML标记共享同一个类。XML和相关的类定义如下 我收到的XML响应格式如下: <Data totalExecutionTime="00:00:00.0467241"> <ItemNumber id="1234" order="0" createdDate="2017-03-24T12:07:09.07" modifiedDate="2018-08-29T16:59:19.127

在执行XML反序列化时,我试图将XML标记的名称放入类属性中。我需要将名称作为属性,因为多个XML标记共享同一个类。XML和相关的类定义如下

我收到的XML响应格式如下:

<Data totalExecutionTime="00:00:00.0467241">
    <ItemNumber id="1234" order="0" createdDate="2017-03-24T12:07:09.07" modifiedDate="2018-08-29T16:59:19.127">
        <Value modifiedDate="2017-03-24T12:07:12.77">ABC1234</Value>
        <Category id="5432" parentID="9876" itemOrder="0" modifiedDate="2017-03-24T12:16:23.687">The best category</Category>
        ... <!-- like 100 other elements -->
    </ItemNumber>
</Data>
和一个顶级类,
ItemData

[Serializable]
[XmlRoot("Data")]
public class ItemData
{
    [XmlAttribute("totalExecutionTime")]
    public string ExecutionTime { get; set; }

    [XmlElement("ItemNumber", Type = typeof(ItemBase))]
    public List<ItemBase> Items { get; set; }
}
最后是
ItemProperty

public class ItemProperty : IXmlSerializable
{
    public static ItemProperty Empty { get; } = new ItemProperty();

    public ItemProperty()
    {
        this.Name = string.Empty;
        this.Value = string.Empty;
        this.Id = 0;
        this.Order = 0;
    }

    public string Name { get; set; }

    [XmlText] // no effect while using IXmlSerializable
    public string Value { get; set; }

    [XmlAttribute("id")] // no effect while using IXmlSerializable
    public int Id { get; set; }

    [XmlAttribute("itemOrder")] // no effect while using IXmlSerializable
    public int Order { get; set; }

    public XmlSchema GetSchema()
    {
        return null;
    }

    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();

        string name = reader.Name;
        this.Name = name;

        string val = reader.ReadElementString();
        this.Value = val;

        if (reader.HasAttributes)
        {
            string id = reader.GetAttribute("id");
            this.Id = Convert.ToInt32(id);

            string itemOrder = reader.GetAttribute("itemOrder");
            this.Order = Convert.ToInt32(itemOrder);

            string sequence = reader.GetAttribute("seq");
            this.Sequence = Convert.ToInt32(sequence);
        }

        // it seems the reader doesn't advance to the next element after reading
        if (reader.NodeType == XmlNodeType.EndElement && !reader.IsEmptyElement)
        {
            reader.Read();
        }
    }

    public void WriteXml(XmlWriter writer)
    {
        throw new NotImplementedException();
    }
}
实现
IXmlSerializable
接口的意义在于,我最终需要存储为
ItemProperty
的XML标记的名称,并且在使用XML类/属性时不会捕获该信息。我认为情况就是这样,因为属性决定了反序列化使用哪个类,通常每个XML标记都有一个关联的类。我不想这样做,因为响应中可能有大量不同的标记,它们都具有相似的属性。因此,
ItemProperty

我还尝试通过
ItemProperty
中的参数化构造函数传递标记的名称,并在其中设置name属性,但在执行反序列化时,它使用默认构造函数,然后设置属性值,因此这不是一个选项

反射也不起作用,因为类总是
ItemProperty
,因此没有唯一的名称

也许我构造XML属性的方式可以用不同的方式来实现我想做的事情,但我看不到这一点

我对解决这个问题的任何方法都持开放态度,但我非常确定这需要实现
IXmlSerializable.ReadXml()
。我知道
XmlReader
的工作是阅读整个XML并将读者推进到文本末尾,但我不太清楚如何做到这一点

TLDR:如何正确实现
IXmlSerializable.ReadXml()
,同时将XML标记名、标记值和所有属性捕获到类属性中?


编辑:通过更新的
ReadXml
方法,我获得了
ItemProperty
级别所需的所有数据,但是class
ItemData
Items
列表只有一个项。我之所以这样认为是因为我没有正确地推进读者的阅读。

来自以下文档:

调用此方法时,读卡器位于包装类型信息的开始标记上。。。当此方法返回时,它必须从头到尾读取整个元素,包括其所有内容。与
WriteXml
方法不同,框架不会自动处理包装器元素。您的实现必须这样做。未能遵守这些定位规则可能会导致代码生成意外的运行时异常或损坏的数据

您的
ReadXml()
可以修改以满足以下要求:

XmlSerializer serializer = new XmlSerializer(typeof(ItemData));
using (TextReader reader = new StringReader(response))
{
    ItemData itemData = (ItemData)serializer.Deserialize(reader);
}
public void ReadXml(XmlReader reader)
{
    reader.MoveToContent();

    this.Name = reader.LocalName; // Do not include the prefix (if present) in the Name.

    if (reader.HasAttributes)
    {
        var id = reader.GetAttribute("id");
        if (id != null)
            // Since id is missing from some elements you might want to make it nullable
            this.Id = XmlConvert.ToInt32(id);

        var order = reader.GetAttribute("itemOrder");
        if (order != null)
            // Since itemOrder is missing from some elements you might want to make it nullable
            this.Order = XmlConvert.ToInt32(order);

        string sequence = reader.GetAttribute("seq");
        //There is no Sequence property?
        //this.Sequence = Convert.ToInt32(sequence);
    }

    // Read element value.
    // This method reads the start tag, the contents of the element, and moves the reader past the end element tag.
    // thus there is no need for an additional Read()
    this.Value = reader.ReadElementContentAsString();
}
注:

  • 您正在调用其文档的
    ReadElementString()

    我们建议您使用该方法读取文本元素

    根据建议,我修改了您的
    ReadXml()
    以使用此方法。反过来,其文件:

    此方法读取开始标记和元素的内容,并将读取器移过结束元素标记

    因此,此方法应使
    XmlReader
    完全按照
    ReadXml()
    的要求定位,以确保读取器正确前进

  • 每个
    ItemProperty
    元素的XML属性必须在读取该元素的内容之前进行处理,因为读取内容会使读取器超过元素的起始位置及其属性

  • 应该使用类中的实用程序来解析和格式化XML原语,以便不会错误地本地化数值和日期/时间值

  • 您可能不希望在
    Name
    属性中包含名称空间前缀(如果有)


  • 演示小提琴。

    可能相关:。这回答了你的问题吗?它显示了将与
    IXmlSerializable
    节点相对应的整个元素加载到
    XElement
    中的示例。不完全是这样,因为该示例在运行前已知反序列化的元素名称。我确实获得了
    ItemProperty
    级别所需的所有数据,但我相信读者不会进步,因为
    ItemData.Items
    只包含一个项目。请参见底部的编辑。谢谢
    public void ReadXml(XmlReader reader)
    {
        reader.MoveToContent();
    
        this.Name = reader.LocalName; // Do not include the prefix (if present) in the Name.
    
        if (reader.HasAttributes)
        {
            var id = reader.GetAttribute("id");
            if (id != null)
                // Since id is missing from some elements you might want to make it nullable
                this.Id = XmlConvert.ToInt32(id);
    
            var order = reader.GetAttribute("itemOrder");
            if (order != null)
                // Since itemOrder is missing from some elements you might want to make it nullable
                this.Order = XmlConvert.ToInt32(order);
    
            string sequence = reader.GetAttribute("seq");
            //There is no Sequence property?
            //this.Sequence = Convert.ToInt32(sequence);
        }
    
        // Read element value.
        // This method reads the start tag, the contents of the element, and moves the reader past the end element tag.
        // thus there is no need for an additional Read()
        this.Value = reader.ReadElementContentAsString();
    }