it-swarm.dev

XmlSerializer: remove namespaces xsi e xsd desnecessários

Existe uma maneira de configurar o XmlSerializer para que ele não escreva namespaces padrão no elemento raiz?

O que eu recebo é isto:

<?xml ...>
<rootelement xmlns:xsi="..." xmlns:xsd="...">
</rootelement>

e eu quero remover ambas as declarações xmlns.

Duplicado de : Como serializar um objeto para XML sem obter xmlns = ”…”?

121
Dave Van den Eynde

Desde que Dave me pediu para repetir minha resposta para Omitindo todos os namespaces xsi e xsd ao serializar um objeto no .NET , atualizei este post e repeti minha resposta aqui a partir do link acima mencionado. O exemplo usado nesta resposta é o mesmo exemplo usado para a outra questão. O que segue é copiado, literalmente.


Depois de ler a documentação da Microsoft e várias soluções on-line, descobri a solução para esse problema. Ele funciona com o XmlSerializer interno e a serialização XML customizada via IXmlSerialiazble.

Para resumir, usarei a mesma amostra XML MyTypeWithNamespaces que foi usada nas respostas a esta pergunta 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 para essa classe. Agora, alguns se opuseram a ter um objeto XmlSerializerNamespaces em algum lugar dentro de suas classes; mas como você pode ver, limpei-o 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 ter feito isso, você deve obter a seguinte saída:

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

Eu usei com sucesso este método em um projeto recente com uma hierarquia profunda de classes que são serializadas para XML para chamadas de serviço da web. A documentação da Microsoft não é muito clara sobre o que fazer com o membro XmlSerializerNamespaces publicamente acessível assim que você o criou, e muitos acham que é inútil. Mas, seguindo sua documentação e usando-a da maneira mostrada acima, você pode personalizar como o XmlSerializer gera XML para suas classes sem recorrer ao comportamento sem suporte ou à serialização de "rolar sua própria" implementando IXmlSerializable.

É minha esperança que esta resposta ponha de lado, de uma vez por todas, como se livrar dos namespaces padrão xsi e xsd gerados pelo XmlSerializer.

ATUALIZAÇÃO: Eu só quero ter certeza de que respondi a pergunta do OP sobre a remoção de todos os namespaces. 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 há dois namespaces em uso). Em algum lugar no seu documento XML, você precisará ter algo como xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Se a classe no exemplo fizer parte de um documento maior, algum lugar acima de um namespace deve ser declarado para um dos (ou ambos) Abracadbra e Whoohoo. Caso contrário, o elemento em um ou ambos os namespaces deve ser decorado com um prefixo de algum tipo (você não pode ter dois namespaces padrão, certo?). Portanto, para este exemplo, Abracadabra é o namespace padrão. Eu poderia dentro da minha classe MyTypeWithNamespaces adicionar um prefixo de namespace para o namespace Whoohoo assim:

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 elemento <Label/> está no namespace "urn:Whoohoo", então não preciso fazer mais nada. Quando eu agora serializar a classe usando o 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 namespace diferente do resto do documento, ele deve, de alguma forma, ser "decorado" com um namespace. Observe que ainda não há namespaces xsi e xsd.


Isso termina minha resposta à outra pergunta. Mas eu queria ter certeza de que respondi a pergunta do OP sobre o uso de namespaces, já que sinto que ainda não resolvi isso. Suponha que <Label> faça parte do mesmo espaço de nomes que o resto do documento, neste caso urn:Abracadabra:

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

Seu construtor ficaria como no meu primeiro exemplo de código, junto com a propriedade pública para recuperar o namespace padrão:

// 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;

Então, mais tarde, no seu código que usa o objeto MyTypeWithNamespaces para serializá-lo, você o chamaria como eu fiz acima:

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);

E o XmlSerializer cuspia de volta o mesmo XML mostrado acima, sem namespaces adicionais na saída:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>
56
fourpastmidnight
//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)
245
Jeremy

Existe uma alternativa - você pode fornecer um membro do tipo XmlSerializerNamespaces no tipo a ser serializado. Decore com o atributo XmlNamespaceDeclarations . Adicione os prefixos de namespace e os URIs a esse membro. Em seguida, qualquer serialização que não forneça explicitamente um XmlSerializerNamespaces usará os pares de prefixo + URI de namespace que você colocou em seu tipo.

Exemplo de código, suponha que este seja seu tipo:

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

Você consegue fazer isso:

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");

E isso significa que qualquer serialização dessa instância que não especifique seu próprio conjunto de pares prefixo + URI usará o prefixo "p" para o namespace "urn: minhaempresa.2009". Ele também omitirá os namespaces xsi e xsd.

A diferença aqui é que você está adicionando o XmlSerializerNamespaces ao próprio tipo, em vez de empregá-lo explicitamente em uma chamada para XmlSerializer.Serialize (). Isso significa que, se uma instância do seu tipo for serializada por código que você não possui (por exemplo, em uma pilha de serviços da Web) e esse código não fornecer explicitamente um XmlSerializerNamespaces, esse serializador usará os namespaces fornecidos na instância.

6
Cheeso