简体   繁体   中英

c# XML Serialization: Order of namespace declarations

I have a very odd situation. I serialize my namespaces like this:

var namespaces = new XmlSerializerNamespaces();
namespaces.Add("xsd", "http://www.w3.org/2001/XMLSchema");
namespaces.Add("xsi", "http://www.w3.org/2001/XMLSchema-instance");

serializer.Serialize(writer, config, namespaces);

On my machine I get the following xml (one line I just added linebreaks):

<SystemConfiguration 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns="http://www.schaeffler.com/sara/systemconfiguration/">

On the buildserver I get with the same software this:

<SystemConfiguration 
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
      xmlns:xsd="http://www.w3.org/2001/XMLSchema" 
      xmlns="http://www.schaeffler.com/sara/systemconfiguration/">

You see the order of xsd and xsi is swapped. I checked the implementation of the serializer and saw that the order is determined with a hashtable and there is not interface for XmlSerializerNamespaces to implement my own serializer for the namespaces.

This is the method in XmlSerializationWriter:

protected void WriteNamespaceDeclarations(XmlSerializerNamespaces xmlns)
    {
      if (xmlns != null)
      {
        foreach (DictionaryEntry dictionaryEntry in xmlns.Namespaces)
        {
          string localName = (string) dictionaryEntry.Key;
          string ns = (string) dictionaryEntry.Value;
          if (this.namespaces != null)
          {
            string str = this.namespaces.Namespaces[(object) localName] as string;
            if (str != null && str != ns)
              throw new InvalidOperationException(Res.GetString("XmlDuplicateNs", (object) localName, (object) ns));
          }
          string str1 = ns == null || ns.Length == 0 ? (string) null : this.Writer.LookupPrefix(ns);
          if (str1 == null || str1 != localName)
            this.WriteAttribute("xmlns", localName, (string) null, ns);
        }
      }
      this.namespaces = (XmlSerializerNamespaces) null;
    }

What can cause the different order of the namespaces within the hashmap?

From msdn:

The elements are sorted according to the hash value of the key, and each key can exist only once in the collection.

The hash value for DictionaryEntry (a struct ) is extracted from ValueType.GetHashCode() . It is likely returning an in-determinable key - potentially based upon the underlying reference value. You would need to do some further reflection to find out for certain how the hash is being calculated. It may just be using the default object implementation.

Also from msdn:

A hash code is intended for efficient insertion and lookup in collections that are based on a hash table. A hash code is not a permanent value.

I've implemented a custom XmlWriter that ensures that the namespaces in the root element are sorted before they're written out:

/// <summary>
/// XmlWriter that ensures the namespace declarations in the root element are always sorted.
/// </summary>
class SortedNamespaceXmlWriter : XmlWriter
{
    private readonly XmlWriter _baseWriter;
    private readonly List<(string prefix, string uri)> _namespaces = new List<(string prefix, string uri)>();
    private int _elementIndex;
    private string _nsPrefix;
    private bool _inXmlNsAttribute;


    public SortedNamespaceXmlWriter(XmlWriter baseWriter) => _baseWriter = baseWriter;


    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        _elementIndex++;
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteStartElement(prefix, localName, ns);
    }

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        if (prefix == "xmlns")
        {
            _inXmlNsAttribute = true;
            _nsPrefix = localName;
        }
        else
        {
            FlushRootElementAttributesIfNeeded();
            _baseWriter.WriteStartAttribute(prefix, localName, ns);
        }
    }

    public override void WriteEndAttribute()
    {
        if (_inXmlNsAttribute)
            _inXmlNsAttribute = false;
        else
            _baseWriter.WriteEndAttribute();
    }

    public override void WriteString(string text)
    {
        if (_inXmlNsAttribute)
            _namespaces.Add((_nsPrefix, text));
        else
            _baseWriter.WriteString(text);
    }

    private void FlushRootElementAttributesIfNeeded()
    {
        if (_elementIndex != 1 || _namespaces.Count == 0)
            return;

        _namespaces.Sort((a, b) => StringComparer.Ordinal.Compare(a.prefix, b.prefix));

        foreach (var (prefix, uri) in _namespaces)
        {
            _baseWriter.WriteStartAttribute("xmlns", prefix, null);
            _baseWriter.WriteString(uri);
            _baseWriter.WriteEndAttribute();
        }

        _namespaces.Clear();
    }


    public override WriteState WriteState => _baseWriter.WriteState;

    public override void Flush() => _baseWriter.Flush();

    public override string LookupPrefix(string ns) => _baseWriter.LookupPrefix(ns);

    public override void WriteBase64(byte[] buffer, int index, int count)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteBase64(buffer, index, count);
    }

    public override void WriteCData(string text)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteCharEntity(ch);
    }

    public override void WriteChars(char[] buffer, int index, int count)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteChars(buffer, index, count);
    }

    public override void WriteComment(string text)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset) => _baseWriter.WriteDocType(name, pubid, sysid, subset);

    public override void WriteEndDocument() => _baseWriter.WriteEndDocument();

    public override void WriteEndElement()
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(char[] buffer, int index, int count)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteRaw(buffer, index, count);
    }

    public override void WriteRaw(string data)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteRaw(data);
    }

    public override void WriteStartDocument() => _baseWriter.WriteStartDocument();

    public override void WriteStartDocument(bool standalone) => _baseWriter.WriteStartDocument();

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteWhitespace(string ws)
    {
        FlushRootElementAttributesIfNeeded();
        _baseWriter.WriteWhitespace(ws);
    }
}

I ended up loading the generated XML document into an XDocument and swapping around the attributes using the "ReplaceAttributes" method:

var xDoc = XDocument.Load(ms);
    
var originalAttributes = xDoc.Root.Attributes();
var orderedAttributes = originalAttributes.OrderBy(a => a.Name.ToString());

xDoc.Root.ReplaceAttributes(orderedAttributes);

In my example I ordered the attributes alphabetically but you can swap the attributes around the way you want. It will follow the order of the IEnumerable.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM