Reputation: 1079
I have created a serialization object using xsd2code++. It works fine, except for the following:
For some reason, the XML I need to send needs to have some namespaces, but not the xsd one (even if, for what I understand, there should be no problem in listing it, but that's not my call). I have in the generated c# class the following code:
[XmlNamespaceDeclarations]
public XmlSerializerNamespaces xmlns;
[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";
this.xmlns = new XmlSerializerNamespaces();
this.xmlns.Add("tfd", "http://www.sat.gob.mx/TimbreFiscalDigital");
this.xmlns.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");
}
In the attributes for the class itself I have the following:
[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)]
But the XML includes the xmlns:xsd="http://www.w3.org/2001/XMLSchema"
namespace declaration.
I've seen different answers here on how to remove completely the namespaces used to serialize, but it's not what I'm looking for, I just want those that I setup to be the only ones serialized.
Any way to remove the xsd one, without resorting to a custom xml serializer or something like that? I feel that there is maybe some atttribute or option I could set (or I am setting incorrectly) that affects this, but I don't see any reference to xsd
in the object's code. I'm already calling the serializer directly through XmlSerializer.Serialize()
.
Upvotes: 3
Views: 3904
Reputation: 116805
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:
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.
Upvotes: 3