问题
How can I force .NET's XmlSerializer to add an xsi:type="FooClass" to a member/node of type FooClass
?
The scenario is a currently-released app, where in v.1 :
- FooClass inherits FooBaseClass
- FooPropertyA is on FooBaseClass
- FooPropertyB is on FooClass
- FooBaseClass is decorated with [XmlInclude(typeof(FooClass))]
- BazClass has member Foo of type FooBaseClass
- All sets of Baz.Foo are to a FooClass instance
- All usages of Baz.Foo expect FooPropertyB (and so a FooClass instance vs FooBaseClass)
The goal: Remove FooBaseClass entirely, pushing FooBaseClass's members up into FooClass, while maintaining backward serialization compatibility
The problem: Then I lose the xsi:type="FooClass" attribute on the Baz.Foo serialization.
In other words the XmlSerializer.Serialize output of
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; }
}
needs to be
<Baz>
<Foo xsi:type="FooClass">
<FooPropertyA>Hello</FooPropertyA>
<FooPropertyB>5</FooPropertyB>
</Foo>
</Baz>
Removing FooBasClass is easy, but then XmlSerializer no longer places xsi:type="FooClass" on Baz/Foo, and so v.1 XmlSerializer.Deserialize instantiates a FooBaseClass instance, not setting FooPropertyB, and assigns it to the Foo property of the parent Baz instance. Thus, any code which checks whether Baz.Foo is FooClass, or casts directly, fails.
The xsi:type attribute was placed automatically in the v.1 code which was
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 think the short answer is that you can't - at least not without implementing I(Xml)Serializable or writing custom serialization code. However, I'm open to good suggestions. Meanwhile I have implemented a workaround hack below, and am hoping for something more elegant, or that at least allows me somehow to remove FooBaseClass entirely.
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
{
}
回答1:
XmlSerializer
can be pretty dumb and straightforward at times, which works to your advantage in this case. Just put it there manually:
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 { }
}
}
回答2:
I had no trouble producing the following:
<?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>
from
[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; }
}
(note the XmlType
attributes are commented out. I wanted to see what happened if a namespace was specified)
Please show the code you used, and the XML it produced.
回答3:
To support inheritance of types in other namespaces you need to use a solution similar to what Pavel Minaev suggested but with the XmlQualifiedName typed property instead of string, e.g.
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; }
}
}
Will serialize (and de-serialize) correctly as:
<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>
This satisfies the requirement of truly extensible polymorphic XML types, i.e. the base class can be used anywhere and later add-ons can be assigned, serialized and de-serialized correctly in both .NET and with XSD validation.
Taking it further, you can also add a XmlSerializerNamespaces property with the XmlNamespaceDeclarationsAttribute to specify preferred prefixes and remove any unwanted namespaces, such as the xmlns:xsd (we only use xmlns:xsi) or even the XSI namespace for your non-inheriting XML serialized classes.
来源:https://stackoverflow.com/questions/1943597/how-can-i-force-the-use-of-an-xsitype-attribute