.net - serializar - system xml serialization xmlserializer vb net
XmlSerializer: elimine espacios de nombres xsi y xsd innecesarios (3)
Como Dave me pidió que repitiera mi respuesta a Omitir todos los espacios de nombres xsi y xsd al serializar un objeto en .NET , he actualizado esta publicación y repito mi respuesta aquí desde el enlace mencionado anteriormente. El ejemplo utilizado en esta respuesta es el mismo ejemplo utilizado para la otra pregunta. Lo que sigue se copia, textualmente.
Después de leer la documentación de Microsoft y varias soluciones en línea, descubrí la solución a este problema. Funciona tanto con el XmlSerializer
como con la serialización XML personalizada a través de IXmlSerialiazble
.
Para MyTypeWithNamespaces
la misma muestra de MyTypeWithNamespaces
XML que se ha usado en las respuestas a esta pregunta hasta el momento.
[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 objetaron tener un objeto XmlSerializerNamespaces
algún lugar dentro de sus clases; pero como pueden ver, lo metí cuidadosamente en el constructor predeterminado y expuse una propiedad pública para devolver los espacios de nombres.
Ahora, cuando llega el momento de serializar la clase, usarías 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, debe 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 jerarquía profunda de clases que se serializan a XML para llamadas al servicio web. La documentación de Microsoft no es muy clara acerca de qué hacer con el miembro XmlSerializerNamespaces
público una vez que lo haya creado, y muchos piensan que es inútil. Pero al seguir su documentación y usarla de la manera que se muestra arriba, puede personalizar cómo XmlSerializer genera XML para sus clases sin recurrir al comportamiento no compatible o a la serialización "rodando su propio" al implementar IXmlSerializable
.
Espero que esta respuesta ponga fin, de una vez por todas, a cómo deshacerse de los xsd
nombres xsi
y xsd
estándar generados por el 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 tu documento XML, vas a necesitar tener algo como xmlns="urn:Abracadabra" xmlns:w="urn:Whoohoo
. Si la clase en el ejemplo es parte de un documento más grande, entonces en algún lugar arriba de un espacio de nombres debe declararse para cualquiera de (o ambos) Abracadbra
y Whoohoo
. De lo contrario, el elemento en uno o ambos espacios de nombres debe estar decorado con un prefijo de algún tipo (no puede tener dos espacios de nombres predeterminados, ¿no?) Así que, para este ejemplo, Abracadabra
es el espacio de nombres predeterminado. Podría dentro de mi clase MyTypeWithNamespaces
agregar un prefijo de espacio de nombres para el espacio de nombres de Whoohoo
así:
public MyTypeWithNamespaces
{
this._namespaces = new XmlSerializerNamespaces(new XmlQualifiedName[] {
new XmlQualifiedName(string.Empty, "urn:Abracadabra"), // Default Namespace
new XmlQualifiedName("w", "urn:Whoohoo")
});
}
Ahora, en la definición de mi clase, indiqué que el elemento <Label/>
está en el espacio de nombres "urn:Whoohoo"
, así que no necesito hacer nada más. Cuando ahora serializo la clase sin cambiar el código de serialización anterior, este es el resultado:
<MyTypeWithNamespaces xmlns:w="urn:Whoohoo">
<w:Label>myLabel</w:Label>
<Epoch>42</Epoch>
</MyTypeWithNamespaces>
Como <Label>
está en un espacio de nombre diferente del resto del documento, debe, de alguna manera, estar "decorado" con un espacio de nombre. Observe que todavía no hay xsd
nombres xsi
y xsd
.
Esto termina mi respuesta a la otra pregunta. Pero quería asegurarme de haber respondido la pregunta del OP sobre el uso de espacios de nombres, ya que creo que todavía no lo abordo. Supongamos que <Label>
es 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 lo harí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 adelante, en su código que usa el objeto MyTypeWithNamespaces
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 el XmlSerializer
nuevamente el mismo XML que se muestra inmediatamente arriba sin espacios de nombres adicionales en el resultado:
<MyTypeWithNamespaces>
<Label>myLabel<Label>
<Epoch>42</Epoch>
</MyTypeWithNamespaces>
¿Hay alguna manera de configurar el XmlSerializer para que no escriba espacios de nombres predeterminados en el elemento raíz?
Lo que obtengo es esto:
<?xml ...>
<rootelement xmlns:xsi="..." xmlns:xsd="...">
</rootelement>
y quiero eliminar ambas declaraciones xmlns.
Duplicado de : ¿cómo serializar un objeto a XML sin obtener xmlns = "..."?
Existe una alternativa: puede proporcionar un miembro de tipo XmlSerializerNamespaces en el tipo que se serializará. XmlNamespaceDeclarations con el atributo XmlNamespaceDeclarations . Agregue los prefijos de espacio de nombres y los URI a ese miembro. Entonces, cualquier serialización que no proporcione explícitamente un XmlSerializerNamespaces usará el prefijo de espacio de nombres + pares de URI que haya puesto en su tipo.
Código de ejemplo, supongamos 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 usará 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 es serializada por código que no es de su propiedad (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.
//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)