How can I force .NET\'s XmlSerializer to add an xsi:type=\"FooClass\" to a member/node of type FooClass
?
The scenario
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.
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 { }
}
}
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.