XmlSerializer: supprimez les espaces de noms xsi et xsd inutiles

Réponses:

63

Puisque Dave m'a demandé de répéter ma réponse à Omettre tous les espaces de noms xsi et xsd lors de la sérialisation d'un objet dans .NET , j'ai mis à jour ce post et répété ma réponse ici à partir du lien susmentionné. L'exemple utilisé dans cette réponse est le même que celui utilisé pour l'autre question. Ce qui suit est copié, textuellement.


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

Pour tout dire, j'utiliserai le même MyTypeWithNamespacesexemple XML qui a été utilisé dans les réponses à cette question jusqu'à présent.

[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 à avoir un XmlSerializerNamespacesobjet quelque part dans leurs classes; mais comme vous pouvez le voir, 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 que vous avez fait cela, vous devriez obtenir le résultat suivant:

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

J'ai utilisé avec succès cette méthode 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 n'est pas très claire sur ce qu'il faut faire avec le XmlSerializerNamespacesmembre accessible au public une fois que vous l'avez créé, et beaucoup pensent que c'est inutile. Mais en suivant leur documentation et en l'utilisant de la manière illustrée ci-dessus, vous pouvez personnaliser la façon dont XmlSerializer génère du XML pour vos classes sans recourir à un comportement non pris en charge ou "lancer votre propre" sérialisation en implémentant IXmlSerializable.

J'espère que cette réponse mettra fin, une fois pour toutes, à la manière de se débarrasser du standard xsiet des xsdespaces de noms générés par le XmlSerializer.

MISE À JOUR: 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 en cours d'utilisation). Quelque part dans votre document XML, vous devrez avoir quelque chose comme xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Si la classe de l'exemple fait partie d'un document plus volumineux, alors quelque part au-dessus d'un espace de noms doit être déclaré pour l'un des (ou les deux) Abracadbraet Whoohoo. Sinon, l'élément dans l'un ou les deux espaces de noms doit être décoré avec un préfixe quelconque (vous ne pouvez pas avoir deux espaces de noms par défaut, n'est-ce pas?). Donc, pour cet exemple, Abracadabraest l'espace de noms par défaut. Je pourrais dans ma MyTypeWithNamespacesclasse ajouter un préfixe d'espace de noms pour l' Whoohooespace de noms comme ceci:

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 l' <Label/>élément est dans l'espace de noms "urn:Whoohoo", donc je n'ai rien à faire de plus. Lorsque je sérialise maintenant la classe en utilisant mon code de sérialisation ci-dessus inchangé, voici le résultat:

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

Étant donné qu'il se <Label>trouve dans un espace de noms différent du reste du document, il doit, en quelque sorte, être «décoré» d'un espace de noms. Notez qu'il n'y a toujours pas d' espaces de noms xsiet xsd.


Ceci termine ma réponse à l'autre question. Mais je voulais m'assurer de répondre à la question du PO sur l'utilisation d'aucun espace de noms, car je pense que je ne l'ai pas encore vraiment abordé. Supposons que cela <Label>fasse partie du même espace de noms que le reste du document, dans ce cas urn:Abracadabra:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>

Votre constructeur ressemblerait à ce qu'il serait dans mon tout premier exemple de code, avec la propriété publique pour récupérer l'espace de noms par défaut:

// 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[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra") // Default Namespace
    });
}

[XmlNamespaceDeclarations]
public XmlSerializerNamespaces Namespaces
{
    get { return this._namespaces; }
}
private XmlSerializerNamespaces _namespaces;

Ensuite, plus tard, dans votre code qui utilise l' MyTypeWithNamespacesobjet pour le sérialiser, vous l'appelleriez comme je l'ai fait ci-dessus:

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

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

...

// Above, you'd setup your XmlTextWriter.

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

Et le XmlSerializercracherait le même XML que celui indiqué immédiatement ci-dessus sans espaces de noms supplémentaires dans la sortie:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>
quatre heures et demie
la source
Pour être complet, vous devriez peut-être inclure la bonne réponse ici au lieu de simplement y faire référence, et aussi, je suis intéressé de savoir comment vous concluez qu'il s'agit d'un «comportement non pris en charge».
Dave Van den Eynde
1
Je suis revenu ici pour vérifier cela, car c'est l'explication la plus simple que j'ai trouvée. Merci @fourpastmidnight
Andre Albuquerque
2
Je ne comprends pas, pour la réponse finale de votre OP, vous utilisez toujours un espace de noms lors de la sérialisation "urn: Abracadabra" (constructeur), pourquoi n'est-il pas inclus dans la sortie finale. L'OP ne devrait pas utiliser: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar
2
C'est la bonne réponse, bien qu'elle ne soit pas la plus votée. Ce qui n'a pas fonctionné pour moi, c'est que XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);j'ai dû remplacer par var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva
1
Cela fait un moment que j'ai écrit cette réponse. XmlTextWriter.Createrenvoie une XmlWriterinstance (abstraite?) . Donc @ Preza8 est correct, vous perdriez la possibilité de définir d'autres XmlTextWriterpropriétés spécifiques (du moins, pas sans la réduire), d'où la conversion spécifique en XmlTextWriter.
fourpastmidnight
257
//Create our own namespaces for the output
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();

//Add an empty namespace and empty value
ns.Add("", "");

//Create the serializer
XmlSerializer slz = new XmlSerializer(someType);

//Serialize the object with our own namespaces (notice the overload)
slz.Serialize(myXmlTextWriter, someObject, ns)
Jérémie
la source
24
Hmmm ... vous êtes des rebelles. Il dit explicitement à msdn.microsoft.com/en-us/library/ ... que vous ne pouvez pas faire cela.
Ralph Lavelle
Bool Yah! (Pour surmonter ce que Mme dit que vous ne pouvez pas faire)
granadaCoder
3
Je ne sais pas pourquoi c'est "non pris en charge", mais cela fait exactement ce que je voulais.
Dan Bechard
8
Cette réponse génère des espaces de noms "xmlns: d1p1" et "xmlns: q1". Qu'est-ce que c'est?
Leonel Sanches da Silva
2
Eh bien, ce code fonctionne pour des sérialisations vraiment très simples, sans autres définitions d'espace de noms. Pour plusieurs définitions d'espace de noms, la réponse de travail est celle acceptée.
Leonel Sanches da Silva
6

Il existe une alternative - vous pouvez fournir un membre de type XmlSerializerNamespaces dans le type à sérialiser. Décorez-le avec l' attribut XmlNamespaceDeclarations . Ajoutez les préfixes d'espace de noms et les URI à ce membre. Ensuite, toute sérialisation qui ne fournit pas explicitement un XmlSerializerNamespaces utilisera le préfixe d'espace de noms + les paires URI que vous avez placées dans votre type.

Exemple de code, supposons que ce soit votre type:

[XmlRoot(Namespace = "urn:mycompany.2009")]
public class Person {
  [XmlAttribute] 
  public bool Known;
  [XmlElement]
  public string Name;
  [XmlNamespaceDeclarations]
  public XmlSerializerNamespaces xmlns;
}

Tu peux le faire:

var p = new Person
  { 
      Name = "Charley",
      Known = false, 
      xmlns = new XmlSerializerNamespaces()
  }
p.xmlns.Add("",""); // default namespace is emoty
p.xmlns.Add("c", "urn:mycompany.2009");

Et cela signifie que toute sérialisation de cette instance qui ne spécifie pas son propre ensemble de paires préfixe + URI utilisera le préfixe "p" pour l'espace de noms "urn: mycompany.2009". Il omettra également les espaces de noms xsi et xsd.

La différence ici est que vous ajoutez les XmlSerializerNamespaces au type lui-même, plutôt que de l'utiliser explicitement lors d'un appel à XmlSerializer.Serialize (). Cela signifie que si une instance de votre type est sérialisée par un code que vous ne possédez pas (par exemple dans une pile de services Web) et que ce code ne fournit pas explicitement un XmlSerializerNamespaces, ce sérialiseur utilisera les espaces de noms fournis dans l'instance.

Cheeso
la source
1. Je ne vois pas la différence. Vous ajoutez toujours l'espace de noms par défaut à une instance de XmlSerializerNamespaces.
Dave Van den Eynde
3
2. Cela pollue davantage les classes. Mon objectif n'est pas d'utiliser certains espaces de noms, mon objectif n'est pas du tout d'utiliser des espaces de noms.
Dave Van den Eynde
J'ai ajouté une note sur la différence entre cette approche et celle de la spécification des XmlSerializerNamespaces pendant la sérialisation uniquement.
Cheeso
0

J'utilise:

public class Person
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
}

class Program
{
    static void Main(string[] args)
    {
        const string DEFAULT_NAMESPACE = "http://www.something.org/schema";
        var serializer = new XmlSerializer(typeof(Person), DEFAULT_NAMESPACE);
        var namespaces = new XmlSerializerNamespaces();
        namespaces.Add("", DEFAULT_NAMESPACE);

        using (var stream = new MemoryStream())
        {
            var someone = new Person
            {
                FirstName = "Donald",
                LastName = "Duck"
            };
            serializer.Serialize(stream, someone, namespaces);
            stream.Position = 0;
            using (var reader = new StreamReader(stream))
            {
                Console.WriteLine(reader.ReadToEnd());
            }
        }
    }
}

Pour obtenir le XML suivant:

<?xml version="1.0"?>
<Person xmlns="http://www.something.org/schema">
  <FirstName>Donald</FirstName>
  <LastName>Duck</LastName>
</Person>

Si vous ne voulez pas d'espace de noms, définissez simplement DEFAULT_NAMESPACE sur "".

Maxence
la source
Bien que cette question ait plus de 10 ans, le but était à l'époque d'avoir un corps XML qui ne contenait aucune déclaration d'espace de noms.
Dave Van den Eynde
1
Si j'ajoute ma propre réponse à une question vieille de 10 ans, c'est parce que la réponse acceptée est plus longue à lire que la Bible dans son édition complète.
Maxence
Et la réponse la plus votée favorise une approche (espace de noms vide) qui n'est pas recommandée.
Maxence
Je ne peux pas m'en empêcher. Je ne peux donner la réponse acceptée que celle que je crois la plus correcte.
Dave Van den Eynde