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 XmlSerializer
sérialisation XML intégrée et personnalisée via IXmlSerialiazble
.
Pour savoir, j'utiliserai le même MyTypeWithNamespaces
exemple 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 à l'idée d'avoir un XmlSerializerNamespaces
objet 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 XmlSerializerNamespaces
membre 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 xsi
et des xsd
espaces 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) Abracadbra
et 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, Abracadabra
est l'espace de noms defalt. Je pourrais dans ma MyTypeWithNamespaces
classe ajouter un préfixe d'espace de noms pour l' Whoohoo
espace 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 xsi
et xsd
.