在反序列化派生类型时,XMLSerializer会警告未知节点/属性

时间:2017-02-20 11:04:50

标签: c# xml xmlserializer xsi xsitype

我最近使用XMLSerializer为未知节点,元素和属性注册了事件处理程序,用于从类型层次结构中反序列化复杂类型。我这样做是因为我收到的一些XML来自第三方;我对数据格式的变化感兴趣,这可能会给我带来麻烦。

在XML中,XMLSerializer生成它使用标准XML属性xsi:type="somederivedtypename"来标识由XML元素表示的实际派生类型。

我很惊讶地看到同一个序列化程序在反序列化时将它刚刚生成的非常相同的属性视为未知。有趣的是,反序列化是正确和完整的(在我的真实世界程序中也有更复杂的类型和数据)。这意味着序列化程序在反序列化的早期阶段正确评估类型信息。但是在后来的数据提取阶段,该属性显然被误认为是对象的真实数据部分,这当然是未知的。

在我的应用程序中,无偿警告最终会混乱一个不受欢迎的通用日志文件。在我看来,序列化程序应该回读它生成的XML而不会出现打嗝。我的问题:

  • 我做错了吗?
  • 有解决方法吗?

这里有一个最小的例子:

using System;
using System.IO;
using System.Xml.Serialization;

namespace XsiTypeAnomaly
{
    /// <summary>
    /// A trivial base type.
    /// </summary>
    [XmlInclude(typeof(DerivedT))]
    public class BaseT{}

    /// <summary>
    /// A trivial derived type to demonstrate a serialization issue.
    /// </summary>
    public class DerivedT : BaseT
    {
        public int anInt { get; set; }
    }

    class Program
    {
        private static void serializer_UnknownAttribute
            (   object sender, 
                XmlAttributeEventArgs e )
        {
            Console.Error.WriteLine("Warning: Deserializing " 
                    + e.ObjectBeingDeserialized
                    + ": Unknown attribute "
                    + e.Attr.Name);
                }

        private static void serializer_UnknownNode(object sender, XmlNodeEventArgs e)
        {
            Console.Error.WriteLine("Warning: Deserializing "
                    + e.ObjectBeingDeserialized
                    + ": Unknown node "
                    + e.Name);
        }

        private static void serializer_UnknownElement(object sender, XmlElementEventArgs e)
        {
            Console.Error.WriteLine("Warning: Deserializing "
                    + e.ObjectBeingDeserialized
                    + ": Unknown element "
                    + e.Element.Name);
        }

        /// <summary>
        /// Serialize, display the xml, and deserialize a trivial object.
        /// </summary>
        /// <param name="args"></param>
        static void Main(string[] args)
        {
            BaseT aTypeObj = new DerivedT() { anInt = 1 };
            using (MemoryStream stream = new MemoryStream())
            {
                var serializer = new XmlSerializer(typeof(BaseT));

                // register event handlers for unknown XML bits
                serializer.UnknownAttribute += serializer_UnknownAttribute;
                serializer.UnknownElement += serializer_UnknownElement;
                serializer.UnknownNode += serializer_UnknownNode;

                serializer.Serialize(stream, aTypeObj);
                stream.Flush();

                // output the xml
                stream.Position = 0;
                Console.Write((new StreamReader(stream)).ReadToEnd() + Environment.NewLine);
                stream.Position = 0;
                var serResult = serializer.Deserialize(stream) as DerivedT;

                Console.WriteLine(
                        (serResult.anInt == 1 ? "Successfully " : "Unsuccessfully ")
                    + "read back object");
            }
        }
    }
}

输出:

<?xml version="1.0"?>
<BaseT xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xsi:type="DerivedT">
  <anInt>1</anInt>
</BaseT>
Warning: Deserializing XsiTypeAnomaly.DerivedT: Unknown node xsi:type
Warning: Deserializing XsiTypeAnomaly.DerivedT: Unknown attribute xsi:type
Successfully read back object

2 个答案:

答案 0 :(得分:4)

  

我做错了吗?

我不这么认为。我同意你的意见,XmlSerializer应该在没有任何警告的情况下反序列化自己的输出。此外,xsi:typeXML Schema specification中定义的标准属性,显然它受XmlSerializer支持,如您的示例所示,并记录在MSDN Library中。

因此,这种行为看起来像是一种疏忽。我可以想象一群Microsoft开发人员在.NET Framework开发过程中处理XmlSerializer的不同方面,而不是同时测试xsi:type和事件。

  

这意味着序列化程序在反序列化的早期阶段正确评估类型信息。但是在后来的数据提取阶段,该属性显然被误认为是对象的真实数据部分,这当然是未知的。

你的观察是正确的。

XmlSerializer类生成一个动态程序集以序列化和反序列化对象。在您的示例中,生成的反序列化DerivedT实例的方法如下所示:

private DerivedT Read2_DerivedT(bool isNullable, bool checkType)
{
    // [Code that uses isNullable and checkType omitted...]

    DerivedT derivedT = new DerivedT();
    while (this.Reader.MoveToNextAttribute())
    {
        if (!this.IsXmlnsAttribute(this.Reader.Name))
            this.UnknownNode(derivedT);
    }

    this.Reader.MoveToElement();
    // [Code that reads child elements and populates derivedT.anInt omitted...]
    return derivedT;
}

反序列化器在读取xsi:type属性并决定创建DerivedT实例后调用此方法。如您所见,while循环为除xmlns属性之外的所有属性引发UnknownNode事件。这就是为什么你得到xsi:type的UnknownNode(和UnknownAttribute)事件。

while循环由内部XmlSerializationReaderILGen.WriteAttributes方法生成。代码相当复杂,但我看不到会导致xsi:type属性被跳过的代码路径(除了我在下面描述的第二种解决方法)。

  

有解决方法吗?

我会忽略xsi:type

的UnknownNode和UnknownAttribute事件
private static void serializer_UnknownNode(object sender, XmlNodeEventArgs e)
{
    if (e.NodeType == XmlNodeType.Attribute &&
        e.NamespaceURI == XmlSchema.InstanceNamespace && e.LocalName == "type")
    {
        // Ignore xsi:type attributes.
    }
    else
    {
        // [Log it...]
    }
}

// [And similarly for UnknownAttribute using e.Attr instead of e...]

另一个(hackier,IMO)解决方法是将xsi:type映射到BaseT类中的虚拟属性:

[XmlInclude(typeof(DerivedT))]
public class BaseT
{
    [XmlAttribute("type", Namespace = XmlSchema.InstanceNamespace)]
    [DebuggerBrowsable(DebuggerBrowsableState.Never)] // Hide this useless property
    public string XmlSchemaType
    {
        get { return null; } // Must return null for XmlSerializer.Serialize to work
        set { }
    }
}

答案 1 :(得分:0)

我不认为这是使用XmlSerializer的正确方法,即使你有正确的输出和警告,在更高级的情况下,没有告诉可能出错的地方。

你应该使用实际的派生类型(aTypeObj.GetType())甚至是泛型来对它进行排序。