The reason that the XmlNamespaceDeclarationsAttribute
is not affecting the namespace prefix of the root element is explained, albeit unclearly, in its documentation:
Also note that the member to which the attribute is applied contains only the prefix-namespace pairs that belong to the XML element defined by the class. For example, in the following XML document, only the prefix pair "cal" is captured, but not the "x" prefix. To get that data, add a member with the XmlNamespaceDeclarationsAttribute to the class that represents the root
element.
<?xml version="1.0"?>
<x:root xmlns:x="http://www.cohowinery.com/x/">
<x:select xmlns:cal="http://www.cohowinery.com/calendar/" path="cal:appointments/@cal:startTime" />
</x:root>
The implication is that the prefix-namespace pairs returned will be added to the attributes of the current element, and used when serializing child elements (those that belong to the current element) -- but will not affect the namespace prefix of the current element itself. To do that, one must add an XmlNamespaceDeclarationsAttribute
member to the parent -- but of course, the root element has no parent.
In the absence of an attribute that controls the namespace prefix of the root element, one must manually invoke the XmlSerializer
using one of its XmlSerializer.Serialize(Writer, Object, XmlSerializerNamespaces)
overloads. If you use an overload of Serialize()
that doesn't include an XmlSerializerNamespaces
, such as XmlSerializer.Serialize(XmlWriter, Object)
, then the serializer will always helpfully add a default set of namespaces to the root element, including:
- The namespace required by the root element itself;
- The standard "xsd" and "xsi" namespaces;
- Additional namespaces required by child nodes;
- Additional namespaces returned by an
XmlNamespaceDeclarations
member.
This is the behavior you are seeing.
Thus the following extension method will serialize your root object as required:
public static partial class XmlSerializationHelper
{
public static string GetXml<T>(this T obj, XmlSerializer serializer = null, XmlSerializerNamespaces ns = null)
{
ns = ns ?? obj.GetXmlNamespaceDeclarations();
using (var textWriter = new StringWriter())
{
var settings = new XmlWriterSettings() { Indent = true }; // For cosmetic purposes.
using (var xmlWriter = XmlWriter.Create(textWriter, settings))
(serializer ?? new XmlSerializer(obj.GetType())).Serialize(xmlWriter, obj, ns);
return textWriter.ToString();
}
}
public static XmlSerializerNamespaces GetXmlNamespaceDeclarations<T>(this T obj)
{
if (obj == null)
return null;
var type = obj.GetType();
return type.GetFields()
.Where(f => Attribute.IsDefined(f, typeof(XmlNamespaceDeclarationsAttribute)))
.Select(f => f.GetValue(obj))
.Concat(type.GetProperties()
.Where(p => Attribute.IsDefined(p, typeof(XmlNamespaceDeclarationsAttribute)))
.Select(p => p.GetValue(obj, null)))
.OfType<XmlSerializerNamespaces>()
.SingleOrDefault();
}
public static XmlSerializerNamespaces With(this XmlSerializerNamespaces xmlns, string prefix, string ns)
{
if (xmlns == null)
throw new ArgumentNullException();
xmlns.Add(prefix, ns);
return xmlns;
}
}
Then if you serialize your type as follows:
var root = new TimbreFiscalDigital();
var xml = root.GetXml();
The following XML is generated:
<tfd:TimbreFiscalDigital xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd" xmlns:tfd="http://www.sat.gob.mx/TimbreFiscalDigital">
<tfd:version>1.1</tfd:version>
</tfd:TimbreFiscalDigital>
Sample fiddle.
Incidentally, if the namespaces returned by TimbreFiscalDigital.xmlns
are fixed and you don't need to capture them during deserialization, you can replace the field with a property that has [XmlNamespaceDeclarations]
applied, like so:
[System.Xml.Serialization.XmlTypeAttribute(AnonymousType = true, Namespace = "http://www.sat.gob.mx/TimbreFiscalDigital")]
[System.Xml.Serialization.XmlRootAttribute(Namespace = "http://www.sat.gob.mx/TimbreFiscalDigital", IsNullable = false)]
public class TimbreFiscalDigital
{
string versionField;
//[XmlAttribute]
public string version { get { return versionField; } set { versionField = value; } }
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces Xmlns
{
get
{
return new XmlSerializerNamespaces()
.With("tfd", "http://www.sat.gob.mx/TimbreFiscalDigital")
.With("xsi", XmlSchema.InstanceNamespace);
}
set { /* Do nothing */ }
}
[XmlAttribute("schemaLocation", Namespace = XmlSchema.InstanceNamespace)]
public string xsiSchemaLocation = "http://www.sat.gob.mx/TimbreFiscalDigital http://www.sat.gob.mx/sitio_internet/cfd/TimbreFiscalDigital/TimbreFiscalDigitalv11.xsd";
public TimbreFiscalDigital()
{
this.versionField = "1.1";
}
}
The property must have both a getter and a setter, but the setter can do nothing while the getter always returns a fresh instance of XmlSerializerNamespaces
. By doing this you can reduce the permanent memory footprint of your class.
Sample fiddle #2.