Depois de ler a documentação da Microsoft e várias soluções online, descobri a solução para esse problema. Ele funciona com a XmlSerializer
serialização XML interna e personalizada via IXmlSerialiazble
.
Ou seja, usarei a mesma MyTypeWithNamespaces
amostra XML usada nas respostas até agora.
[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;
}
Isso é tudo nessa classe. Agora, alguns se opuseram a ter um XmlSerializerNamespaces
objeto em algum lugar dentro de suas classes; mas, como você pode ver, coloquei-o ordenadamente no construtor padrão e expus uma propriedade pública para retornar os namespaces.
Agora, quando chegar a hora de serializar a classe, você usaria o seguinte código:
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);
Depois de fazer isso, você deverá obter a seguinte saída:
<MyTypeWithNamespaces>
<Label xmlns="urn:Whoohoo">myLabel</Label>
<Epoch>42</Epoch>
</MyTypeWithNamespaces>
Usei esse método com êxito em um projeto recente com uma hierarquia profunda de classes serializadas em XML para chamadas de serviço da web. A documentação da Microsoft não é muito clara sobre o que fazer com o XmlSerializerNamespaces
membro publicamente acessível depois que você a criou, e muitos pensam que é inútil. Porém, seguindo a documentação e usando-a da maneira mostrada acima, você pode personalizar como o XmlSerializer gera XML para suas classes sem recorrer a um comportamento não suportado ou a serialização "rolando sua própria" implementandoIXmlSerializable
.
Espero que esta resposta descanse, de uma vez por todas, como se livrar do padrão xsi
e dos xsd
namespaces gerados pelo XmlSerializer
.
ATUALIZAÇÃO: Eu só quero ter certeza de que respondi à pergunta do OP sobre como remover todos os espaços para nome. Meu código acima funcionará para isso; Deixa-me mostrar-te como. Agora, no exemplo acima, você realmente não pode se livrar de todos os namespaces (porque existem dois namespaces em uso). Em algum lugar do seu documento XML, você precisará ter algo parecido xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo
. Se a classe no exemplo fizer parte de um documento maior, algum lugar acima de um espaço para nome deverá ser declarado para um (ou ambos) Abracadbra
e Whoohoo
. Caso contrário, o elemento em um ou em ambos os espaços para nome deve ser decorado com um prefixo de algum tipo (você não pode ter dois espaços para nome padrão, certo?). Portanto, para este exemplo, Abracadabra
é o namespace defalt. Eu poderia dentro da minha MyTypeWithNamespaces
classe adicionar um prefixo de namespace para o Whoohoo
namespace da seguinte forma:
public MyTypeWithNamespaces
{
this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
new XmlQualifiedName("w", "urn:Whoohoo")
});
}
Agora, na minha definição de classe, indiquei que o <Label/>
elemento está no espaço "urn:Whoohoo"
para nome , portanto, não preciso fazer mais nada. Quando agora serializo a classe usando meu código de serialização acima inalterado, esta é a saída:
<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
<w:Label>myLabel</w:Label>
<Epoch>42</Epoch>
</MyTypeWithNamespaces>
Como <Label>
está em um espaço para nome diferente do restante do documento, ele deve, de alguma maneira, ser "decorado" com um espaço para nome. Observe que ainda não existem espaços para nome xsi
e xsd
.