XmlSerializer: elimine espacios de nombres xsi y xsd innecesarios

Respuestas:

63

Como Dave me pidió que repitiera mi respuesta a Omitir todos los espacios de nombres xsi y xsd al serializar un objeto en .NET , actualicé esta publicación y repetí mi respuesta aquí desde el enlace mencionado anteriormente. El ejemplo usado en esta respuesta es el mismo ejemplo usado para la otra pregunta. Lo que sigue es copiado, literalmente.


Después de leer la documentación de Microsoft y varias soluciones en línea, descubrí la solución a este problema. Funciona con la XmlSerializerserialización XML incorporada y personalizada a través de IXmlSerialiazble.

Para hacerlo, utilizaré la misma MyTypeWithNamespacesmuestra XML que se ha utilizado en las respuestas a esta pregunta hasta ahora.

[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;
}

Eso es todo para esta clase. Ahora, algunos se opusieron a tener un XmlSerializerNamespacesobjeto en algún lugar dentro de sus clases; pero como puede ver, lo guardé cuidadosamente en el constructor predeterminado y expuse una propiedad pública para devolver los espacios de nombres.

Ahora, cuando llegue el momento de serializar la clase, usaría el siguiente 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);

Una vez que haya hecho esto, debería obtener el siguiente resultado:

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

He utilizado con éxito este método en un proyecto reciente con una gran jerarquía de clases que se serializan en XML para llamadas de servicio web. La documentación de Microsoft no es muy clara sobre qué hacer con el XmlSerializerNamespacesmiembro de acceso público una vez que lo ha creado, y muchos piensan que es inútil. Pero siguiendo su documentación y usándola de la manera que se muestra arriba, puede personalizar cómo XmlSerializer genera XML para sus clases sin recurrir a comportamientos no admitidos o "rodar su propia" serialización mediante la implementación IXmlSerializable.

Espero que esta respuesta deje de lado, de una vez por todas, cómo deshacerse del estándar xsiy los xsdespacios de nombres generados por XmlSerializer.

ACTUALIZACIÓN: Solo quiero asegurarme de haber respondido la pregunta del OP sobre la eliminación de todos los espacios de nombres. Mi código anterior funcionará para esto; Déjame enseñarte como. Ahora, en el ejemplo anterior, realmente no puede deshacerse de todos los espacios de nombres (porque hay dos espacios de nombres en uso). En algún lugar de su documento XML, necesitará tener algo como esto xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo. Si la clase en el ejemplo es parte de un documento más grande, entonces se debe declarar en algún lugar sobre un espacio de nombres para uno de (o ambos) Abracadbray Whoohoo. De lo contrario, el elemento en uno o en ambos espacios de nombres debe estar decorado con un prefijo de algún tipo (no puede tener dos espacios de nombres predeterminados, ¿verdad?). Entonces, para este ejemplo, Abracadabraes el espacio de nombres predeterminado. Podría dentro de mi MyTypeWithNamespacesclase agregar un prefijo de espacio de nombres para el Whoohooespacio de nombres de esta manera:

public MyTypeWithNamespaces
{
    this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
        new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
        new XmlQualifiedName("w", "urn:Whoohoo")
    });
}

Ahora, en mi definición de clase, indiqué que el <Label/>elemento está en el espacio de nombres "urn:Whoohoo", por lo que no necesito hacer nada más. Cuando ahora serializo la clase usando mi código de serialización anterior sin cambios, esta es la salida:

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

Debido a que <Label>está en un espacio de nombres diferente del resto del documento, debe, de alguna manera, estar "decorado" con un espacio de nombres. Tenga en cuenta que todavía no hay espacios de nombres xsiy xsd.


Esto termina mi respuesta a la otra pregunta. Pero quería asegurarme de responder a la pregunta del OP sobre no usar espacios de nombres, ya que siento que realmente no lo aborde todavía. Suponga que <Label>forma parte del mismo espacio de nombres que el resto del documento, en este caso urn:Abracadabra:

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

Su constructor se vería como se vería en mi primer ejemplo de código, junto con la propiedad pública para recuperar el espacio de nombres predeterminado:

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

Luego, más tarde, en su código que usa el MyTypeWithNamespacesobjeto para serializarlo, lo llamaría como lo hice anteriormente:

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

Y XmlSerializervolvería a escupir el mismo XML que se muestra inmediatamente arriba sin espacios de nombres adicionales en la salida:

<MyTypeWithNamespaces>
    <Label>myLabel<Label>
    <Epoch>42</Epoch>
</MyTypeWithNamespaces>
fourpastmidnight
fuente
Para completar, tal vez debería incluir la respuesta correcta aquí en lugar de simplemente referirse a ella, y también, me interesa saber cómo concluye que se trata de un "comportamiento no compatible".
Dave Van den Eynde
1
Vine aquí nuevamente para verificar esto, ya que es la explicación más directa que he encontrado. Gracias @fourpastmidnight
Andre Albuquerque
2
No lo entiendo, para la respuesta final de su OP, todavía está usando un espacio de nombres durante la serialización "urn: Abracadabra" (constructor), ¿por qué eso no está incluido en la salida final? ¿No debería utilizar el OP: XmlSerializerNamespaces EmptyXmlSerializerNamespaces = new XmlSerializerNamespaces (new [] {XmlQualifiedName.Empty});
dparkar 01 de
2
Esta es la respuesta correcta, aunque no es la más votada. Lo onky que no funcionó para mí fue XmlTextWriter xtw = (XmlTextWriter)XmlTextWriter.Create(ms, xws);que tuve que reemplazarlo var xtw = XmlTextWriter.Create(memStm, xws);.
Leonel Sanches da Silva
1
Ha pasado un tiempo desde que escribí esta respuesta. XmlTextWriter.Createdevuelve una XmlWriterinstancia (¿abstracta?) Entonces @ Preza8 es correcto, perdería la capacidad de establecer otras XmlTextWriterpropiedades específicas (al menos, no sin reducirlo ), por lo tanto, el lanzamiento específico a XmlTextWriter.
fourpastmidnight
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
fuente
24
Hmmm ... ustedes son rebeldes. Dice explícitamente en msdn.microsoft.com/en-us/library/… que no puedes hacer eso.
Ralph Lavelle
Bool Yah! (Para superar lo que ms dice que no puedes hacer)
granadaCoder
3
No estoy seguro de por qué es "no compatible", pero esto hace exactamente lo que quería.
Dan Bechard el
8
Esta respuesta genera espacios de nombres "xmlns: d1p1" y "xmlns: q1". ¿Que es esto?
Leonel Sanches da Silva
2
Bueno, este código funciona para serializaciones realmente muy simples, sin otras definiciones de espacio de nombres. Para múltiples definiciones de espacio de nombres, la respuesta de trabajo es la aceptada.
Leonel Sanches da Silva
6

Existe una alternativa: puede proporcionar un miembro de tipo XmlSerializerNamespaces en el tipo que se va a serializar. Decorarlo con el atributo XmlNamespaceDeclarations . Agregue los prefijos de espacio de nombres y los URI a ese miembro. Luego, cualquier serialización que no proporcione explícitamente un XmlSerializerNamespaces utilizará el prefijo de espacio de nombres + pares de URI que ha puesto en su tipo.

Código de ejemplo, suponga que este es su tipo:

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

Puedes hacerlo:

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

Y eso significará que cualquier serialización de esa instancia que no especifique su propio conjunto de pares de prefijo + URI utilizará el prefijo "p" para el espacio de nombres "urn: mycompany.2009". También omitirá los espacios de nombres xsi y xsd.

La diferencia aquí es que está agregando XmlSerializerNamespaces al tipo en sí mismo, en lugar de emplearlo explícitamente en una llamada a XmlSerializer.Serialize (). Esto significa que si una instancia de su tipo está serializada por un código que no posee (por ejemplo, en una pila de servicios web), y ese código no proporciona explícitamente un XmlSerializerNamespaces, ese serializador utilizará los espacios de nombres proporcionados en la instancia.

Cheeso
fuente
1. No veo la diferencia. Todavía está agregando el espacio de nombres predeterminado a una instancia de XmlSerializerNamespaces.
Dave Van den Eynde
3
2. Esto contamina más las clases. Mi objetivo no es usar cierto espacio de nombres, mi objetivo no es usar espacios de nombres en absoluto.
Dave Van den Eynde
Agregué una nota sobre la diferencia entre este enfoque y el de especificar XmlSerializerNamespaces solo durante la serialización.
Cheeso
0

Estoy usando:

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 obtener el siguiente XML:

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

Si no desea el espacio de nombres, simplemente configure DEFAULT_NAMESPACE en "".

Maxence
fuente
Aunque esta pregunta tiene más de 10 años, el objetivo en ese entonces era tener un cuerpo XML que no contuviera ninguna declaración de espacio de nombres.
Dave Van den Eynde
1
Si agrego mi propia respuesta a una pregunta que tiene 10 años es porque la respuesta aceptada es más larga de leer que la Biblia en su edición completa.
Maxence
Y la respuesta más votada promueve un enfoque (espacio de nombres vacío) que no se recomienda.
Maxence
No puedo evitar eso. Solo puedo hacer que la respuesta aceptada sea la más correcta.
Dave Van den Eynde