web-dev-qa-db-fra.com

Omettre tous les espaces de noms xsi et xsd lors de la sérialisation d'un objet dans .NET?

Le code ressemble à ceci:

StringBuilder builder = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
using (XmlWriter xmlWriter = XmlWriter.Create(builder, settings))
{
    XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
    s.Serialize(xmlWriter, objectToSerialize);
}

Le document sérialisé résultant comprend des espaces de noms, comme suit:

<message xmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\" 
    xmlns:xsd=\"http://www.w3.org/2001/XMLSchema\" 
    xmlns="urn:something">
 ...
</message>

Pour supprimer les espaces de noms xsi et xsd, je peux suivre la réponse de Comment sérialiser un objet en XML sans obtenir xmlns = "…"? .

Je veux que mon tag de message soit <message> (sans attribut d’espace de nom). Comment puis-je faire ceci?

118
NetSide
...
XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("","");
s.Serialize(xmlWriter, objectToSerialize, ns);
223
Thomas Levesque

C'est la deuxième des deux réponses.

Si vous souhaitez simplement supprimer de manière arbitraire tous les espaces de nom d'un document lors de la sérialisation, vous pouvez le faire en implémentant votre propre XmlWriter.

Le moyen le plus simple consiste à dériver de XmlTextWriter et à remplacer la méthode StartElement qui émet des espaces de nom. La méthode StartElement est appelée par XmlSerializer lors de l’émission d’éléments, y compris la racine. En redéfinissant l'espace de noms de chaque élément et en le remplaçant par la chaîne vide, vous avez supprimé les espaces de noms de la sortie.

public class NoNamespaceXmlWriter : XmlTextWriter
{
    //Provide as many contructors as you need
    public NoNamespaceXmlWriter(System.IO.TextWriter output)
        : base(output) { Formatting= System.Xml.Formatting.Indented;}

    public override void WriteStartDocument () { }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        base.WriteStartElement("", localName, "");
    }
}

Supposons que ce soit le type:

// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
    // private fields backing the properties
    private int _Epoch;
    private string _Label;

    // explicitly define a distinct namespace for this element
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        set {  _Label= value; } 
        get { return _Label; } 
    }

    // this property will be implicitly serialized to XML using the
    // member name for the element name, and inheriting the namespace from
    // the type.
    public int Epoch
    {
        set {  _Epoch= value; } 
        get { return _Epoch; } 
    }
}

Voici comment vous utiliseriez une telle chose lors de la sérialisation:

        var o2= new MyTypeWithNamespaces { ..intializers.. };
        var builder = new System.Text.StringBuilder();
        using ( XmlWriter writer = new NoNamespaceXmlWriter(new System.IO.StringWriter(builder)))
        {
            s2.Serialize(writer, o2, ns2);
        }            
        Console.WriteLine("{0}",builder.ToString());

XmlTextWriter est en quelque sorte cassé. Selon le référence doc , quand il écrit, il ne vérifie pas ce qui suit:

  • Caractères non valides dans les noms d'attributs et d'éléments.

  • Caractères Unicode qui ne correspondent pas au codage spécifié. Si les caractères Unicode ne correspondent pas à l'encodage spécifié, XmlTextWriter n'échappe pas aux caractères Unicode en entités de caractères.

  • Attributs en double.

  • Caractères dans l'identificateur public DOCTYPE ou l'identificateur système.

Ces problèmes avec XmlTextWriter existent depuis la version 1.1 du .NET Framework et ils le resteront pour des raisons de compatibilité ascendante. Si vous ne vous inquiétez pas de ces problèmes, utilisez bien certainement XmlTextWriter. Mais la plupart des gens souhaiteraient un peu plus de fiabilité.

Pour l'obtenir, tout en supprimant les espaces de noms lors de la sérialisation, au lieu de dériver de XmlTextWriter, définissez une implémentation concrète de l'abstrait XmlWriter et de ses 24 méthodes.

Un exemple est ici:

public class XmlWriterWrapper : XmlWriter
{
    protected XmlWriter writer;

    public XmlWriterWrapper(XmlWriter baseWriter)
    {
        this.Writer = baseWriter;
    }

    public override void Close()
    {
        this.writer.Close();
    }

    protected override void Dispose(bool disposing)
    {
        ((IDisposable) this.writer).Dispose();
    }

    public override void Flush()
    {
        this.writer.Flush();
    }

    public override string LookupPrefix(string ns)
    {
        return this.writer.LookupPrefix(ns);
    }

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

    public override void WriteCData(string text)
    {
        this.writer.WriteCData(text);
    }

    public override void WriteCharEntity(char ch)
    {
        this.writer.WriteCharEntity(ch);
    }

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

    public override void WriteComment(string text)
    {
        this.writer.WriteComment(text);
    }

    public override void WriteDocType(string name, string pubid, string sysid, string subset)
    {
        this.writer.WriteDocType(name, pubid, sysid, subset);
    }

    public override void WriteEndAttribute()
    {
        this.writer.WriteEndAttribute();
    }

    public override void WriteEndDocument()
    {
        this.writer.WriteEndDocument();
    }

    public override void WriteEndElement()
    {
        this.writer.WriteEndElement();
    }

    public override void WriteEntityRef(string name)
    {
        this.writer.WriteEntityRef(name);
    }

    public override void WriteFullEndElement()
    {
        this.writer.WriteFullEndElement();
    }

    public override void WriteProcessingInstruction(string name, string text)
    {
        this.writer.WriteProcessingInstruction(name, text);
    }

    public override void WriteRaw(string data)
    {
        this.writer.WriteRaw(data);
    }

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

    public override void WriteStartAttribute(string prefix, string localName, string ns)
    {
        this.writer.WriteStartAttribute(prefix, localName, ns);
    }

    public override void WriteStartDocument()
    {
        this.writer.WriteStartDocument();
    }

    public override void WriteStartDocument(bool standalone)
    {
        this.writer.WriteStartDocument(standalone);
    }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        this.writer.WriteStartElement(prefix, localName, ns);
    }

    public override void WriteString(string text)
    {
        this.writer.WriteString(text);
    }

    public override void WriteSurrogateCharEntity(char lowChar, char highChar)
    {
        this.writer.WriteSurrogateCharEntity(lowChar, highChar);
    }

    public override void WriteValue(bool value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(DateTime value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(decimal value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(double value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(int value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(long value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(object value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(float value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteValue(string value)
    {
        this.writer.WriteValue(value);
    }

    public override void WriteWhitespace(string ws)
    {
        this.writer.WriteWhitespace(ws);
    }


    public override XmlWriterSettings Settings
    {
        get
        {
            return this.writer.Settings;
        }
    }

    protected XmlWriter Writer
    {
        get
        {
            return this.writer;
        }
        set
        {
            this.writer = value;
        }
    }

    public override System.Xml.WriteState WriteState
    {
        get
        {
            return this.writer.WriteState;
        }
    }

    public override string XmlLang
    {
        get
        {
            return this.writer.XmlLang;
        }
    }

    public override System.Xml.XmlSpace XmlSpace
    {
        get
        {
            return this.writer.XmlSpace;
        }
    }        
}

Ensuite, fournissez une classe dérivée qui remplace la méthode StartElement, comme précédemment:

public class NamespaceSupressingXmlWriter : XmlWriterWrapper
{
    //Provide as many contructors as you need
    public NamespaceSupressingXmlWriter(System.IO.TextWriter output)
        : base(XmlWriter.Create(output)) { }

    public NamespaceSupressingXmlWriter(XmlWriter output)
        : base(XmlWriter.Create(output)) { }

    public override void WriteStartElement(string prefix, string localName, string ns)
    {
        base.WriteStartElement("", localName, "");
    }
}

Et puis utilisez cet écrivain comme ceci:

        var o2= new MyTypeWithNamespaces { ..intializers.. };
        var builder = new System.Text.StringBuilder();
        var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent= true };
        using ( XmlWriter innerWriter = XmlWriter.Create(builder, settings))
            using ( XmlWriter writer = new NamespaceSupressingXmlWriter(innerWriter))
            {
                s2.Serialize(writer, o2, ns2);
            }            
        Console.WriteLine("{0}",builder.ToString());

Crédit pour cela à Oleg Tkachenko .

27
Cheeso

Après avoir lu la documentation de Microsoft et plusieurs solutions en ligne, j'ai découvert la solution à ce problème. Il fonctionne à la fois avec la XmlSerializer intégrée et la sérialisation XML personnalisée via IXmlSerialiazble.

En d'autres termes, j'utiliserai le même exemple XML MyTypeWithNamespaces que celui utilisé dans les réponses à cette question.

[XmlRoot("MyTypeWithNamespaces", Namespace="urn:Abracadabra", IsNullable=false)]
public class MyTypeWithNamespaces
{
    // As noted below, per Microsoft's documentation, if the class exposes a public
    // member of type XmlSerializerNamespaces decorated with the 
    // XmlNamespacesDeclarationAttribute, then the XmlSerializer will utilize those
    // namespaces during serialization.
    public MyTypeWithNamespaces( )
    {
        this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
            // Don't do this!! Microsoft's documentation explicitly says it's not supported.
            // It doesn't throw any exceptions, but in my testing, it didn't always work.

            // new XmlQualifiedName(string.Empty, string.Empty),  // And don't do this:
            // new XmlQualifiedName("", "")

            // DO THIS:
            new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
            // Add any other namespaces, with prefixes, here.
        });
    }

    // If you have other constructors, make sure to call the default constructor.
    public MyTypeWithNamespaces(string label, int Epoch) : this( )
    {
        this._label = label;
        this._Epoch = Epoch;
    }

    // An element with a declared namespace different than the namespace
    // of the enclosing type.
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        get { return this._label; }
        set { this._label = value; }
    }
    private string _label;

    // An element whose tag will be the same name as the property name.
    // Also, this element will inherit the namespace of the enclosing type.
    public int Epoch
    {
        get { return this._Epoch; }
        set { this._Epoch = value; }
    }
    private int _Epoch;

    // Per Microsoft's documentation, you can add some public member that
    // returns a XmlSerializerNamespaces object. They use a public field,
    // but that's sloppy. So I'll use a private backed-field with a public
    // getter property. Also, per the documentation, for this to work with
    // the XmlSerializer, decorate it with the XmlNamespaceDeclarations
    // attribute.
    [XmlNamespaceDeclarations]
    public XmlSerializerNamespaces Namespaces
    {
        get { return this._namespaces; }
    }
    private XmlSerializerNamespaces _namespaces;
}

C'est tout pour cette classe. Maintenant, certains se sont opposés à la présence d'un objet XmlSerializerNamespaces dans leurs classes; mais comme vous pouvez le constater, je l'ai soigneusement rangé dans le constructeur par défaut et exposé une propriété publique pour renvoyer les espaces de noms.

Maintenant, quand vient le temps de sérialiser la classe, vous utiliserez le code suivant:

MyTypeWithNamespaces myType = new MyTypeWithNamespaces("myLabel", 42);

/******
   OK, I just figured I could do this to make the code shorter, so I commented out the
   below and replaced it with what follows:

// You have to use this constructor in order for the root element to have the right namespaces.
// If you need to do custom serialization of inner objects, you can use a shortened constructor.
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces), new XmlAttributeOverrides(),
    new Type[]{}, new XmlRootAttribute("MyTypeWithNamespaces"), "urn:Abracadabra");

******/
XmlSerializer xs = new XmlSerializer(typeof(MyTypeWithNamespaces),
    new XmlRootAttribute("MyTypeWithNamespaces") { Namespace="urn:Abracadabra" });

// I'll use a MemoryStream as my backing store.
MemoryStream ms = new MemoryStream();

// This is extra! If you want to change the settings for the XmlSerializer, you have to create
// a separate XmlWriterSettings object and use the XmlTextWriter.Create(...) factory method.
// So, in this case, I want to omit the XML declaration.
XmlWriterSettings xws = new XmlWriterSettings();
xws.OmitXmlDeclaration = true;
xws.Encoding = Encoding.UTF8; // This is probably the default
// You could use the XmlWriterSetting to set indenting and new line options, but the
// XmlTextWriter class has a much easier method to accomplish that.

// The factory method returns a XmlWriter, not a XmlTextWriter, so cast it.
XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);
// Then we can set our indenting options (this is, of course, optional).
xtw.Formatting = Formatting.Indented;

// Now serialize our object.
xs.Serialize(xtw, myType, myType.Namespaces);

Une fois cela fait, vous devriez obtenir le résultat suivant:

<MyTypeWithNamespaces>
    <Label xmlns="urn:Whoohoo">myLabel</Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

J'ai utilisé cette méthode avec succès dans un projet récent avec une hiérarchie profonde de classes sérialisées en XML pour les appels de service Web. La documentation de Microsoft ne dit pas très bien quoi faire avec le membre publiquement accessible XmlSerializerNamespaces une fois que vous l'avez créé et nombreux sont ceux qui pensent que c'est inutile. Mais en suivant leur documentation et en l’utilisant de la manière indiquée ci-dessus, vous pouvez personnaliser la manière dont XmlSerializer génère XML pour vos classes sans recourir à un comportement non pris en charge ou à une "sérialisation sans suivi" en implémentant IXmlSerializable.

J'espère que cette réponse résoudra une fois pour toutes la suppression des espaces de noms standard xsi et xsd générés par XmlSerializer.

UPDATE: Je veux juste m'assurer d'avoir répondu à la question du PO sur la suppression de tous les espaces de noms. Mon code ci-dessus fonctionnera pour cela; Laisse moi te montrer comment. Maintenant, dans l'exemple ci-dessus, vous ne pouvez vraiment pas vous débarrasser de tous les espaces de noms (car il y a deux espaces de noms utilisés). Quelque part dans votre document XML, vous allez avoir besoin de quelque chose comme xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Si la classe de l'exemple fait partie d'un document plus volumineux, un endroit au-dessus d'un espace de noms doit être déclaré pour l'un des deux (ou les deux) Abracadbra et Whoohoo. Si ce n'est pas le cas, l'élément d'un ou des deux espaces de noms doit être décoré d'un préfixe (vous ne pouvez pas avoir deux espaces de noms par défaut, n'est-ce pas?). Donc, pour cet exemple, Abracadabra est l’espace de noms defalt. Je pourrais à l'intérieur de ma classe MyTypeWithNamespaces ajouter un préfixe d'espace de nommage pour l'espace de nommage Whoohoo comme suit:

public MyTypeWithNamespaces
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
        new XmlQualifiedName("w", "urn:Whoohoo")
    });
}

Maintenant, dans ma définition de classe, j'ai indiqué que le <Label/> L'élément est dans l'espace de noms "urn:Whoohoo", je n'ai donc rien d'autre à faire. Quand je sérialise maintenant la classe en utilisant le code de sérialisation ci-dessus tel quel, voici le résultat:

<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
    <w:Label>myLabel</w:Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Car <Label> est dans un espace de noms différent du reste du document, il doit en quelque sorte être "décoré" avec un espace de noms. Notez qu'il n'y a toujours pas de xsi et xsd espaces de noms.

15
fourpastmidnight

Ceci est la première de mes deux réponses à la question.

Si vous souhaitez contrôler avec précision les espaces de noms, par exemple si vous souhaitez en omettre certains mais pas les autres, ou si vous souhaitez remplacer un espace de noms par un autre, vous pouvez le faire en utilisant XmlAttributeOverrides .

Supposons que vous ayez cette définition de type:

// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
    // private fields backing the properties
    private int _Epoch;
    private string _Label;

    // explicitly define a distinct namespace for this element
    [XmlElement(Namespace="urn:Whoohoo")]
    public string Label
    {
        set {  _Label= value; } 
        get { return _Label; } 
    }

    // this property will be implicitly serialized to XML using the
    // member name for the element name, and inheriting the namespace from
    // the type.
    public int Epoch
    {
        set {  _Epoch= value; } 
        get { return _Epoch; } 
    }
}

Et ce pseudo-code de sérialisation:

        var o2= new MyTypeWithNamespaces() { ..initializers...};
        ns.Add( "", "urn:Abracadabra" );
        XmlSerializer s2 = new XmlSerializer(typeof(MyTypeWithNamespaces));
        s2.Serialize(System.Console.Out, o2, ns);

Vous obtiendrez quelque chose comme ce XML:

<MyTypeWithNamespaces xmlns="urn:Abracadabra">
  <Label xmlns="urn:Whoohoo">Cimsswybclaeqjh</Label>
  <Epoch>97</Epoch>
</MyTypeWithNamespaces>

Notez qu'il existe un espace de noms par défaut sur l'élément racine et qu'il existe également un espace de noms distinct sur l'élément "Label". Ces espaces de noms ont été dictés par les attributs décorant le type, dans le code ci-dessus.

Le framework de sérialisation XML dans .NET inclut la possibilité de remplacer explicitement les attributs qui décorent le code réel. Vous faites cela avec la classe XmlAttributesOverrides et ses amis. Supposons que j'ai le même type et que je le sérialise de la manière suivante:

        // instantiate the container for all attribute overrides
        XmlAttributeOverrides xOver = new XmlAttributeOverrides();

        // define a set of XML attributes to apply to the root element
        XmlAttributes xAttrs1 = new XmlAttributes();

        // define an XmlRoot element (as if [XmlRoot] had decorated the type)
        // The namespace in the attribute override is the empty string. 
        XmlRootAttribute xRoot = new XmlRootAttribute() { Namespace = ""};

        // add that XmlRoot element to the container of attributes
        xAttrs1.XmlRoot= xRoot;

        // add that bunch of attributes to the container holding all overrides
        xOver.Add(typeof(MyTypeWithNamespaces), xAttrs1);

        // create another set of XML Attributes
        XmlAttributes xAttrs2 = new XmlAttributes();

        // define an XmlElement attribute, for a type of "String", with no namespace
        var xElt = new XmlElementAttribute(typeof(String)) { Namespace = ""};

        // add that XmlElement attribute to the 2nd bunch of attributes
        xAttrs2.XmlElements.Add(xElt);

        // add that bunch of attributes to the container for the type, and
        // specifically apply that bunch to the "Label" property on the type.
        xOver.Add(typeof(MyTypeWithNamespaces), "Label", xAttrs2);

        // instantiate a serializer with the overrides 
        XmlSerializer s3 = new XmlSerializer(typeof(MyTypeWithNamespaces), xOver);

        // serialize
        s3.Serialize(System.Console.Out, o2, ns2);

Le résultat ressemble à ceci;

<MyTypeWithNamespaces>
  <Label>Cimsswybclaeqjh</Label>
  <Epoch>97</Epoch>
</MyTypeWithNamespaces>

Vous avez dépouillé les espaces de noms.

Une question logique est, pouvez-vous retirer tous les espaces de noms de types arbitraires lors de la sérialisation, sans passer par les substitutions explicites? La réponse est OUI et comment procéder. c'est dans ma prochaine réponse.

6
Cheeso
XmlSerializer sr = new XmlSerializer(objectToSerialize.GetType());
TextWriter xmlWriter = new StreamWriter(filename);
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
sr.Serialize(xmlWriter, objectToSerialize, namespaces);
6
Tejas