.Net Add namespace to XML document as default and

2019-03-06 03:45发布

问题:

When using XMLSerializer to create an XML string of a serialization of a class oXML in vb.net as follows:

Dim x As New Xml.Serialization.XmlSerializer(oXML.GetType, "urn:oecd:blah:blah")
Dim xmlns = New XmlSerializerNamespaces()

xmlns.Add(String.Empty, "urn:oecd:blah:blah")
xmlns.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance")
xmlns.Add("sfa", "urn:oecd:blah:blah1")
xmlns.Add("iso", "urn:oecd:blah:blah2")
xmlns.Add("ftc", "urn:oecd:blah:blah")

Dim sw As New IO.StringWriter()
x.Serialize(sw, oXML, xmlns)

You will notice that I have repeated the addition of namespace "urn:oecd:blah:blah" as both an empty "default" namespace on the xmlns object and also in the definition of x As New Xml.Serialization.XmlSerializer.

The problem is that this particular namespace only ever gets rendered with a prefix of ftc and the default is not shown. If I comment out the addition of the ftc namespace, and run it again, then a default namespace is rendered correctly. Is there a way to tell the serializer that this namespace is used as default and also with a prefix of ftc? I am assuming it is because the namespace is used twice, both as default and also with a prefix that it is ignoring it?

回答1:

XmlSerializer has no defined behavior in the case of passing an XmlSerializerNamespaces with two prefixes for identical namespaces. It could choose either prefix and the result would be semantically identical. Further, from the reference source for the class it appears the namespaces are stored in a hash table so their order is unpredictable. But since the XML would have the same meaning either way, the simplest thing for you to do is to not worry about it.

If for some reason you must have duplicated namespaces in your XML, and must use the default prefix in preference to an equivalent named prefix for elements in that namespace, you could serialize to an XDocument then add the missing duplicates manually:

Public Module XObjectExtensions
    <System.Runtime.CompilerServices.Extension> 
    Public Function SerializeToXDocument(Of T)(obj As T, serializer As XmlSerializer, ns As XmlSerializerNamespaces) As XDocument
        Dim doc = New XDocument()
        Using writer = doc.CreateWriter()
            serializer = If(serializer, New XmlSerializer(obj.GetType()))
            serializer.Serialize(writer, obj, ns)
        End Using
        If doc.Root IsNot Nothing AndAlso ns IsNot Nothing Then
            ' Add missing namespaces
            For Each name In ns.ToArray().Except(doc.Root.Attributes().Where(Function(a) a.IsNamespaceDeclaration).Select((Function(a) New XmlQualifiedName(a.Name.LocalName, a.Value))))
                doc.Root.Add(New XAttribute("xmlns" + (If(String.IsNullOrEmpty(name.Name), String.Empty, ":" + name.Name)), name.Namespace))
            Next
        End If

        Return doc
    End Function
End Module

And then use it like:

        Dim xDoc = oXML.SerializeToXDocument(x, xmlns)

        Dim xml2 as String
        Using sw = New IO.StringWriter()
            xDoc.Save(sw)
            xml2 = sw.ToString()
        End Using

Example dotnetfiddle.

Now, in your case the default namespace was omitted in favor of a duplicated prefixed namespace. If for some reason XmlSerializer chose to do the opposite (and it's not documented which it will choose), then adding the missing named namespace at the end of the root document's namespace list would cause all elements to be written with the named prefix. That's because it turns out that, when writing an XElement, name/namespace pairs are pushed onto a stack during serialization and so the lattermost is used. To work around this, you could adapt the code from XDocument Save Removing Node Prefixes and move the default prefix to the end of the attribute list rather than the beginning.