Dopo aver letto la documentazione di Microsoft e diverse soluzioni online, ho scoperto la soluzione a questo problema. Funziona con la XmlSerializer
serializzazione XML integrata e personalizzata tramite IXmlSerialiazble
.
Per intenderci, userò lo stesso MyTypeWithNamespaces
campione XML che è stato usato finora nelle risposte a questa domanda.
[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;
}
Questo è tutto per questa classe. Ora, alcuni hanno obiettato di avere unXmlSerializerNamespaces
oggetto da qualche parte all'interno delle loro classi; ma come puoi vedere, l'ho riposto ordinatamente nel costruttore predefinito ed esposto una proprietà pubblica per restituire gli spazi dei nomi.
Ora, quando arriva il momento di serializzare la classe, useresti il seguente codice:
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);
Una volta fatto questo, dovresti ottenere il seguente output:
<MyTypeWithNamespaces>
<Label xmlns="urn:Whoohoo">myLabel</Label>
<Epoch>42</Epoch>
</MyTypeWithNamespaces>
Ho usato con successo questo metodo in un recente progetto con una profonda gerarchia di classi serializzate in XML per le chiamate al servizio web. La documentazione di Microsoft non è molto chiara su cosa fare con il XmlSerializerNamespaces
membro accessibile pubblicamente dopo averlo creato, e così tanti pensano che sia inutile. Ma seguendo la loro documentazione e utilizzandola nel modo mostrato sopra, puoi personalizzare il modo in cui XmlSerializer genera XML per le tue classi senza ricorrere a comportamenti non supportati o "far rotolare la tua" serializzazione implementandoIXmlSerializable
.
Spero che questa risposta ponga fine, una volta per tutte, a come sbarazzarsi dello standard xsi
e degli xsd
spazi dei nomi generati da XmlSerializer
.
AGGIORNAMENTO: Voglio solo assicurarmi di aver risposto alla domanda del PO sulla rimozione di tutti gli spazi dei nomi. Il mio codice sopra funzionerà per questo; lascia che ti mostri come. Ora, nell'esempio sopra, non puoi davvero sbarazzarti di tutti gli spazi dei nomi (perché ci sono due spazi dei nomi in uso). Da qualche parte nel tuo documento XML, dovrai avere qualcosa di simile xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo
. Se la classe nell'esempio fa parte di un documento più grande, allora da qualche parte sopra uno spazio dei nomi deve essere dichiarato per uno di (o entrambi) Abracadbra
e Whoohoo
. In caso contrario, l'elemento in uno o entrambi gli spazi dei nomi deve essere decorato con un prefisso di qualche tipo (non è possibile avere due spazi dei nomi predefiniti, giusto?). Quindi, per questo esempio, Abracadabra
è lo spazio dei nomi defalt. Potrei all'interno della mia MyTypeWithNamespaces
classe aggiungere un prefisso dello Whoohoo
spazio dei nomi per lo spazio dei nomi in questo modo:
public MyTypeWithNamespaces
{
this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
new XmlQualifiedName("w", "urn:Whoohoo")
});
}
Ora, nella mia definizione di classe, ho indicato che l' <Label/>
elemento si trova nello spazio dei nomi "urn:Whoohoo"
, quindi non ho bisogno di fare altro. Quando ora serializzo la classe usando il mio codice di serializzazione sopra invariato, questo è l'output:
<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
<w:Label>myLabel</w:Label>
<Epoch>42</Epoch>
</MyTypeWithNamespaces>
Poiché si <Label>
trova in uno spazio dei nomi diverso dal resto del documento, deve, in qualche modo, essere "decorato" con uno spazio dei nomi. Si noti che non ci sono ancora xsi
e xsd
spazi dei nomi.