【问题标题】:How can I force the use of an xsi:type attribute?如何强制使用 xsi:type 属性?
【发布时间】:2009-12-22 00:55:26
【问题描述】:

如何强制 .NET 的 XmlSerializer 将 xsi:type="FooClass" 添加到 FooClass 类型的成员/节点?

场景是一个当前发布的应用,在 v.1 中:

  • FooClass 继承 FooBaseClass
  • FooPropertyA 在 FooBaseClass 上
  • FooPropertyB 在 FooClass 上
  • FooBaseClass 用 [XmlInclude(typeof(FooClass))] 修饰
  • BazClass 有 FooBaseClass 类型的成员 Foo
  • 所有的 Baz.Foo 集合都指向一个 FooClass 实例
  • Baz.Foo 的所有用法都需要 FooPropertyB(以及 FooClass 实例与 FooBaseClass)

目标:完全移除 FooBaseClass,将 FooBaseClass 的成员推入 FooClass,同时保持向后序列化兼容性

问题:然后我在 Baz.Foo 序列化中丢失了 xsi:type="FooClass" 属性。

换句话说,XmlSerializer.Serialize 的输出

public class BazClass
{
    public BazClass()
    {
        Foo = new FooClass { A = 5, B = "Hello" };
    }
    public FooClass Foo { get; set; }
}

public class FooClass
{
    public int FooPropertyA { get; set; }
    public string FooPropertyB { get; set; }
}

需要

<Baz>
    <Foo xsi:type="FooClass">
        <FooPropertyA>Hello</FooPropertyA>
        <FooPropertyB>5</FooPropertyB>
    </Foo>
</Baz>

删除 FooBasClass 很容易,但是 XmlSerializer 不再将 xsi:type="FooClass" 放在 Baz/Foo 上,因此 v.1 XmlSerializer.Deserialize 实例化了一个 FooBaseClass 实例,而不是设置 FooPropertyB,并将其分配给 Foo 属性父 Baz 实例的。因此,任何检查 Baz.Foo 是否为 FooClass 或直接强制转换的代码都会失败。

xsi:type 属性被自动放置在 v.1 代码中

public class BazClass
{
    public BazClass()
    {
        Foo = new FooClass { A = 5, B = "Hello" };
    }
    public FooBaseClass Foo { get; set; }
}

public class FooClass : FooBaseClass
{
    public string FooPropertyB { get; set; }
}

[XmlInclude(typeof(FooClass))]    
public class FooBaseClass
{
    public int FooPropertyA { get; set; }
}

我认为简短的回答是你不能——至少在没有实现 I(Xml)Serializable 或编写自定义序列化代码的情况下不能。但是,我愿意接受好的建议。同时,我在下面实现了一个 workaround hack,并希望有更优雅的东西,或者至少可以让我以某种方式完全删除 FooBaseClass。

BazClass
{
    [XmlElement("Foo")]
    public FooBaseClass XmlFoo { get { return Foo; } set { Foo = (StartPicture)value; } }

    [XmlIgnore]
    public FooClass Foo { get; set; }
}    

FooClass : FooBaseClass
{
    public int FooPropertyB { get; set; }
    public string FooPropertyA { get; set; }
}

[XmlInclude("FooClass")]
FooBaseClass
{
}

【问题讨论】:

    标签: .net xml-serialization


    【解决方案1】:

    XmlSerializer 有时可能非常愚蠢和直截了当,这在这种情况下对您有利。只需手动将其放在那里:

    public class FooClass
    {
        public int FooPropertyA { get; set; }
        public string FooPropertyB { get; set; }
    
        [XmlAttribute("type", Namespace="http://www.w3.org/2001/XMLSchema-instance")]
        public string XsiType
        {
            get { return "Foo"; }
            set { }
        }
    }
    

    【讨论】:

    • 工作就像一个魅力!第二双眼睛的价值... :-)
    • 此声明中的命名空间用于type 属性,而不是用于类型本身。如果您需要一个类型位于与元素本身不同的命名空间中,那么我认为您可以通过手动注入 xmlns 属性(通过使用虚拟 `[XmlAttribute]' 属性)以类似的方式破解它,然后在类型名中引用它。
    【解决方案2】:

    我没有遇到以下问题:

    <?xml version="1.0" encoding="utf-8"?>
    <BazClass xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
       xmlns:xsd="http://www.w3.org/2001/XMLSchema">
        <Foo xsi:type="FooClass">
            <FooPropertyA>Hello</FooPropertyA>
            <FooPropertyB>5</FooPropertyB>
        </Foo>
    </BazClass>
    

    来自

    [XmlInclude(typeof(FooClass))]
    //[XmlType(TypeName = "FooBase", Namespace = "urn:namespace", IncludeInSchema = true)]
    public class FooBaseClass
    {
        public string FooPropertyA { get; set; }
    }
    
    //[XmlType(TypeName = "Foo", Namespace = "urn:namespace", IncludeInSchema = true)]
    public class FooClass : FooBaseClass
    {
        public int FooPropertyB { get; set; } 
    }
    
    public class BazClass
    {
        public FooBaseClass Foo { get; set; }
    }
    

    (注意XmlType属性被注释掉了。我想看看如果指定了命名空间会发生什么)

    请显示您使用的代码,以及它生成的 XML。

    【讨论】:

    • 我已经(希望)澄清了我上面的问题。是的,你的例子可以工作,但它没有做我想要的 - 即删除 FooBaseClass,将 FooPropertyB 推入 FooClass。您的示例实际上反映了我当前的状态 - 不是我想要的状态。
    • 我刚刚看到我的问题的第一行实际上是如何误导的 - 我很抱歉。
    【解决方案3】:

    要支持其他命名空间中的类型继承,您需要使用类似于 Pavel Minaev 建议但使用 XmlQualifiedName 类型化属性而不是字符串的解决方案,例如

    using System.Xml;
    using System.Xml.Schema;
    using System.Xml.Serialization;
    
    namespace Test
    {
        /// <summary>
        /// Base class which is XML serializable and extensible.
        /// </summary>
        [XmlRoot(XmlRootName, Namespace = XmlRootNamespace)]
        public abstract class BaseClassInOtherNamespace
        {
            /// <summary>
            /// Name of the XML element 
            /// </summary>
            public const string XmlRootName = "Base";
    
            /// <summary>
            /// XML namespace in which this type is defined.
            /// </summary>
            public const string XmlRootNamespace = "urn:base";
    
            /// <summary>
            /// Creates an instance which serializes as the correct inherited XML type.
            /// </summary>
            protected BaseClassInOtherNamespace(XmlQualifiedName xsiType)
            {
                XsiType = xsiType;
            }
    
            /// <summary>
            /// XML type for serialization.
            /// </summary>
            [XmlAttribute("type", Namespace = XmlSchema.InstanceNamespace)]
            public XmlQualifiedName XsiType { get; set; }
    
            /// <summary>
            /// Some base property.
            /// </summary>
            public int BaseProperty { get; set; }
        }
    
        /// <summary>
        /// Inheriting class extending the base class, created in a different XML namespace.
        /// </summary>
        [XmlRoot(XmlRootName, Namespace = XmlRootNamespace)]
        [XmlType(XmlTypeName, Namespace = XmlTypeNamespace)]
        public class InheritingClass : BaseClassInOtherNamespace
        {
            /// <summary>
            /// Name of the XML element 
            /// </summary>
            public const string XmlTypeName = "Inheriting";
    
            /// <summary>
            /// XML namespace in which this type is defined.
            /// </summary>
            public const string XmlTypeNamespace = "urn:other";
    
            /// <summary>
            /// Creates an instance.
            /// </summary>
            public InheritingClass() : base(new XmlQualifiedName(XmlTypeName, XmlTypeNamespace))
            {
            }
    
            /// <summary>
            /// Some new property in a different (inheriting) namespace.
            /// </summary>
            public int InheritingProperty { get; set; }
        }
    }
    

    将正确序列化(和反序列化)为:

    <Base xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:q1="urn:other" xsi:type="q1:Inheriting" xmlns="urn:base">
      <BaseProperty>0</BaseProperty>
      <q1:InheritingProperty>0</q1:InheritingProperty>
    </Base>
    

    这满足了真正可扩展的多态 XML 类型的要求,即基类可以在任何地方使用,以后的附加组件可以在 .NET 和 XSD 验证中正确分配、序列化和反序列化。

    更进一步,您还可以添加带有 XmlNamespaceDeclarationsAttribute 的 XmlSerializerNamespaces 属性来指定首选前缀并删除任何不需要的命名空间,例如 xmlns:xsd(我们只使用 xmlns:xsi)甚至 XSI 命名空间继承 XML 序列化类。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多