XmlSerializer: remova os namespaces xsi e xsd desnecessários

Respostas:

63

Como Dave pediu que eu repetisse minha resposta para omitir todos os namespaces xsi e xsd ao serializar um objeto no .NET , atualizei esta postagem e repeti minha resposta aqui no link mencionado acima. O exemplo usado nesta resposta é o mesmo exemplo usado para a outra pergunta. O que se segue é copiado, literalmente.


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 XmlSerializerserialização XML interna e personalizada via IXmlSerialiazble.

Para saber mais, usarei a mesma MyTypeWithNamespacesamostra 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 XmlSerializerNamespacesobjeto 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 XmlSerializerNamespacesmembro publicamente acessível depois que você a criou, e muitos pensam que é inútil. Mas, 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 "serializar sua própria" serialização implementando IXmlSerializable.

Espero que esta resposta descanse, de uma vez por todas, como se livrar do padrão xsie dos xsdnamespaces 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, em algum lugar acima de um espaço para nome deverá ser declarado para um (ou ambos) Abracadbrae 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 espaço para nome padrão. Eu poderia dentro da minha MyTypeWithNamespacesclasse adicionar um prefixo de namespace para o Whoohoonamespace 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 , então 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 forma, ser "decorado" com um espaço para nome. Observe que ainda não existem espaços para nome xsie xsd.


Isso encerra minha resposta para a outra pergunta. Mas eu queria ter certeza de responder à pergunta do OP sobre não usar namespaces, pois acho que ainda não a resolvi. Suponha que faça <Label>parte do mesmo espaço para nome que o restante 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 espaço para nome 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 MyTypeWithNamespacesobjeto 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 XmlSerializerisso cuspiria de volta o mesmo XML, como mostrado imediatamente acima, sem espaços para nome adicionais na saída:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>
meia-noite
fonte
Para ser completo, talvez você deva incluir a resposta certa aqui, em vez de simplesmente se referir a ela, e também estou interessado em saber como você conclui que é 'comportamento não suportado'.
Dave Van den Eynde
1
Vim aqui novamente para verificar isso, já que é a explicação mais direta que encontrei. Obrigado @fourpastmidnight
Andre Albuquerque
2
Não entendi, para a resposta do seu OP final, você ainda está usando um espaço para nome durante a serialização "urn: Abracadabra" (construtor), por que isso não está incluído na saída final. O OP não deve usar: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
Dparkar #
2
Esta é a resposta correta, embora não seja a mais votada. A coisa complicada que não funcionou para mim foi XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);a substituição var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva
1
Já faz um tempo desde que escrevi esta resposta. XmlTextWriter.Createretorna uma XmlWriterinstância (abstrata?) . Portanto, o @ Preza8 está correto, você perderia a capacidade de definir outras XmlTextWriterpropriedades específicas (pelo menos, não sem a rebaixar), portanto, a conversão específica para XmlTextWriter.
Fourpastmidnight 27/06/19
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)
Jeremy
fonte
24
Hmmm ... vocês são rebeldes. Diz explicitamente em msdn.microsoft.com/en-us/library/… que você não pode fazer isso.
Ralph Lavelle
Bool Yah! (Para superar o ms dizer é que você não pode fazer)
granadaCoder
3
Não sei por que "não há suporte", mas isso faz exatamente o que eu queria.
precisa saber é o seguinte
8
Esta resposta gera os espaços para nome "xmlns: d1p1" e "xmlns: q1". O que é isso?
Leonel Sanches da Silva
2
Bem, esse código funciona para serializações realmente muito simples, sem outras definições de namespace. Para várias definições de namespace, a resposta de trabalho é a aceita.
Leonel Sanches da Silva
6

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 espaço para nome e URIs a esse membro. Em seguida, qualquer serialização que não forneça explicitamente um XmlSerializerNamespaces usará o prefixo do namespace + os pares URI que você colocou no seu tipo.

Código de exemplo, suponha que este seja o 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 de prefixo + URI usará o prefixo "p" para o espaço de nome "urn: mycompany.2009". Ele também omitirá os namespaces xsi e xsd.

A diferença aqui é que você está adicionando os 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 espaços de nome fornecidos na instância.

Cheeso
fonte
1. Eu não vejo a diferença. Você ainda está adicionando o espaço para nome padrão a uma instância do XmlSerializerNamespaces.
Dave Van den Eynde 19/05/09
3
2. Isso polui mais as classes. Meu objetivo não é usar determinado espaço para nome, meu objetivo não é usar espaços para nome.
Dave Van den Eynde 19/05/09
Adicionei uma observação sobre a diferença entre essa abordagem e a de especificar os XmlSerializerNamespaces apenas durante a serialização.
#
0

Estou a usar:

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

Para obter o seguinte XML:

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

Se você não quiser o espaço para nome, defina DEFAULT_NAMESPACE como "".

Maxence
fonte
Embora essa pergunta tenha mais de 10 anos, o objetivo dela naquela época era ter um corpo XML que não continha nenhuma declaração de namespace.
Dave Van den Eynde 13/03
1
Se adiciono minha própria resposta a uma pergunta de 10 anos, é porque a resposta aceita é mais longa do que a Bíblia em sua edição completa.
Maxence
E a resposta mais votada promove uma abordagem (espaço para nome vazio) que não é recomendada.
Maxence
Eu não posso evitar isso. Só posso fazer com que a resposta aceita seja a mais correta.
Dave Van den Eynde 14/03