3

我最近使用 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
4

3 回答 3

6

难道我做错了什么?

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

因此,这种行为看起来只是一种疏忽。我可以想象在 .NET Framework 的开发过程中,一群 Microsoft 开发人员致力于 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循环为除属性之外的所有属性引发 UnknownNode 事件xmlns。这就是为什么您会收到 UnknownNode(和 UnknownAttribute)事件xsi:type

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

有解决方法吗?

我会忽略 UnknownNode 和 UnknownAttribute 事件xsi:type

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 { }
    }
}
于 2017-02-23T04:39:32.797 回答
0

我认为这不是使用 XmlSerializer 的正确方法,即使您有带有警告的正确输出,但在更高级的场景中,不知道会出现什么问题。

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

于 2017-02-22T16:15:38.217 回答
0

您是否尝试过XMLSerializer可以将派生类型作为其中之一传递的构造函数extraTypes

看这里:https ://msdn.microsoft.com/en-us/library/e5aakyae%28v=vs.110%29.aspx

你可以像这样使用它:

var serializer = new XmlSerializer(typeof(BaseT), new Type[] { typeof(DerivedT) });

当然,一般来说,您可能希望从其他地方检索派生类型的列表。例如来自另一个程序集。

于 2017-02-23T09:59:01.950 回答