c# - example - Omitir todos los espacios de nombres xsi y xsd al serializar un objeto en.NET?
system xml serialization xmlserializer vb net (5)
El código se ve así:
StringBuilder builder = new StringBuilder();
XmlWriterSettings settings = new XmlWriterSettings();
settings.OmitXmlDeclaration = true;
using (XmlWriter xmlWriter = XmlWriter.Create(builder, settings))
{
XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
s.Serialize(xmlWriter, objectToSerialize);
}
El documento serializado resultante incluye espacios de nombres, así:
<message xmlns:xsi=/"http://www.w3.org/2001/XMLSchema-instance/"
xmlns:xsd=/"http://www.w3.org/2001/XMLSchema/"
xmlns="urn:something">
...
</message>
Para eliminar los espacios de nombres xsi y xsd, puedo seguir la respuesta de Cómo serializar un objeto a XML sin obtener xmlns = "..."? .
Quiero mi etiqueta de mensaje como <message>
(sin ningún atributo de espacio de nombres). ¿Cómo puedo hacer esto?
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
.
A saber, MyTypeWithNamespaces
el mismo MyTypeWithNamespaces
XML MyTypeWithNamespaces
que se ha utilizado 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 defalt. 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
.
Esta es la primera de mis dos respuestas a la pregunta.
Si desea un control preciso sobre los espacios de nombres, por ejemplo, si desea omitir algunos de ellos pero no otros, o si desea reemplazar un espacio de nombres por otro, puede hacerlo utilizando XmlAttributeOverrides .
Supongamos que tiene esta definición de tipo:
// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
// private fields backing the properties
private int _Epoch;
private string _Label;
// explicitly define a distinct namespace for this element
[XmlElement(Namespace="urn:Whoohoo")]
public string Label
{
set { _Label= value; }
get { return _Label; }
}
// this property will be implicitly serialized to XML using the
// member name for the element name, and inheriting the namespace from
// the type.
public int Epoch
{
set { _Epoch= value; }
get { return _Epoch; }
}
}
Y este pseudo-código de serialización:
var o2= new MyTypeWithNamespaces() { ..initializers...};
ns.Add( "", "urn:Abracadabra" );
XmlSerializer s2 = new XmlSerializer(typeof(MyTypeWithNamespaces));
s2.Serialize(System.Console.Out, o2, ns);
Obtendrás algo así como este XML:
<MyTypeWithNamespaces xmlns="urn:Abracadabra">
<Label xmlns="urn:Whoohoo">Cimsswybclaeqjh</Label>
<Epoch>97</Epoch>
</MyTypeWithNamespaces>
Observe que hay un espacio de nombres predeterminado en el elemento raíz, y también hay un espacio de nombres distinto en el elemento "Etiqueta". Estos espacios de nombres fueron dictados por los atributos que decoran el tipo, en el código anterior.
El marco de Serialización Xml en .NET incluye la posibilidad de anular explícitamente los atributos que decoran el código real. Haga esto con la clase y amigos XmlAttributesOverrides. Supongamos que tengo el mismo tipo, y lo serializo de esta manera:
// instantiate the container for all attribute overrides
XmlAttributeOverrides xOver = new XmlAttributeOverrides();
// define a set of XML attributes to apply to the root element
XmlAttributes xAttrs1 = new XmlAttributes();
// define an XmlRoot element (as if [XmlRoot] had decorated the type)
// The namespace in the attribute override is the empty string.
XmlRootAttribute xRoot = new XmlRootAttribute() { Namespace = ""};
// add that XmlRoot element to the container of attributes
xAttrs1.XmlRoot= xRoot;
// add that bunch of attributes to the container holding all overrides
xOver.Add(typeof(MyTypeWithNamespaces), xAttrs1);
// create another set of XML Attributes
XmlAttributes xAttrs2 = new XmlAttributes();
// define an XmlElement attribute, for a type of "String", with no namespace
var xElt = new XmlElementAttribute(typeof(String)) { Namespace = ""};
// add that XmlElement attribute to the 2nd bunch of attributes
xAttrs2.XmlElements.Add(xElt);
// add that bunch of attributes to the container for the type, and
// specifically apply that bunch to the "Label" property on the type.
xOver.Add(typeof(MyTypeWithNamespaces), "Label", xAttrs2);
// instantiate a serializer with the overrides
XmlSerializer s3 = new XmlSerializer(typeof(MyTypeWithNamespaces), xOver);
// serialize
s3.Serialize(System.Console.Out, o2, ns2);
El resultado se ve así;
<MyTypeWithNamespaces>
<Label>Cimsswybclaeqjh</Label>
<Epoch>97</Epoch>
</MyTypeWithNamespaces>
Ha eliminado los espacios de nombres.
Una pregunta lógica es, ¿puedes eliminar todos los espacios de nombres de los tipos arbitrarios durante la serialización, sin pasar por las anulaciones explícitas? La respuesta es SÍ, y cómo hacerlo está en mi próxima respuesta.
Esta es la segunda de dos respuestas.
Si desea eliminar todos los espacios de nombres arbitrariamente de un documento durante la serialización, puede hacerlo implementando su propio XmlWriter.
La forma más fácil es derivar de XmlTextWriter y anular el método StartElement que emite espacios de nombres. El XmlSerializer invoca el método StartElement al emitir cualquier elemento, incluida la raíz. Al anular el espacio de nombres para cada elemento y reemplazarlo con la cadena vacía, ha eliminado los espacios de nombres del resultado.
public class NoNamespaceXmlWriter : XmlTextWriter
{
//Provide as many contructors as you need
public NoNamespaceXmlWriter(System.IO.TextWriter output)
: base(output) { Formatting= System.Xml.Formatting.Indented;}
public override void WriteStartDocument () { }
public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement("", localName, "");
}
}
Supongamos que este es el tipo:
// explicitly specify a namespace for this type,
// to be used during XML serialization.
[XmlRoot(Namespace="urn:Abracadabra")]
public class MyTypeWithNamespaces
{
// private fields backing the properties
private int _Epoch;
private string _Label;
// explicitly define a distinct namespace for this element
[XmlElement(Namespace="urn:Whoohoo")]
public string Label
{
set { _Label= value; }
get { return _Label; }
}
// this property will be implicitly serialized to XML using the
// member name for the element name, and inheriting the namespace from
// the type.
public int Epoch
{
set { _Epoch= value; }
get { return _Epoch; }
}
}
Así es como usarías tal cosa durante la serialización:
var o2= new MyTypeWithNamespaces { ..intializers.. };
var builder = new System.Text.StringBuilder();
using ( XmlWriter writer = new NoNamespaceXmlWriter(new System.IO.StringWriter(builder)))
{
s2.Serialize(writer, o2, ns2);
}
Console.WriteLine("{0}",builder.ToString());
El XmlTextWriter está algo roto, sin embargo. De acuerdo con el documento de referencia , cuando escribe no verifica lo siguiente:
Caracteres no válidos en los nombres de atributos y elementos.
Caracteres Unicode que no se ajustan a la codificación especificada. Si los caracteres Unicode no se ajustan a la codificación especificada, XmlTextWriter no escapa de los caracteres Unicode en entidades de caracteres.
Atributos duplicados
Caracteres en el identificador público DOCTYPE o identificador de sistema.
Estos problemas con XmlTextWriter han existido desde la v1.1 de .NET Framework, y se mantendrán, por compatibilidad con versiones anteriores. Si no tiene preocupaciones sobre esos problemas, entonces use el XmlTextWriter. Pero a la mayoría de la gente le gustaría un poco más de fiabilidad.
Para obtener eso, mientras se siguen suprimiendo los espacios de nombres durante la serialización, en lugar de derivar de XmlTextWriter, defina una implementación concreta del XmlWriter abstracto y sus 24 métodos.
Un ejemplo está aquí:
public class XmlWriterWrapper : XmlWriter
{
protected XmlWriter writer;
public XmlWriterWrapper(XmlWriter baseWriter)
{
this.Writer = baseWriter;
}
public override void Close()
{
this.writer.Close();
}
protected override void Dispose(bool disposing)
{
((IDisposable) this.writer).Dispose();
}
public override void Flush()
{
this.writer.Flush();
}
public override string LookupPrefix(string ns)
{
return this.writer.LookupPrefix(ns);
}
public override void WriteBase64(byte[] buffer, int index, int count)
{
this.writer.WriteBase64(buffer, index, count);
}
public override void WriteCData(string text)
{
this.writer.WriteCData(text);
}
public override void WriteCharEntity(char ch)
{
this.writer.WriteCharEntity(ch);
}
public override void WriteChars(char[] buffer, int index, int count)
{
this.writer.WriteChars(buffer, index, count);
}
public override void WriteComment(string text)
{
this.writer.WriteComment(text);
}
public override void WriteDocType(string name, string pubid, string sysid, string subset)
{
this.writer.WriteDocType(name, pubid, sysid, subset);
}
public override void WriteEndAttribute()
{
this.writer.WriteEndAttribute();
}
public override void WriteEndDocument()
{
this.writer.WriteEndDocument();
}
public override void WriteEndElement()
{
this.writer.WriteEndElement();
}
public override void WriteEntityRef(string name)
{
this.writer.WriteEntityRef(name);
}
public override void WriteFullEndElement()
{
this.writer.WriteFullEndElement();
}
public override void WriteProcessingInstruction(string name, string text)
{
this.writer.WriteProcessingInstruction(name, text);
}
public override void WriteRaw(string data)
{
this.writer.WriteRaw(data);
}
public override void WriteRaw(char[] buffer, int index, int count)
{
this.writer.WriteRaw(buffer, index, count);
}
public override void WriteStartAttribute(string prefix, string localName, string ns)
{
this.writer.WriteStartAttribute(prefix, localName, ns);
}
public override void WriteStartDocument()
{
this.writer.WriteStartDocument();
}
public override void WriteStartDocument(bool standalone)
{
this.writer.WriteStartDocument(standalone);
}
public override void WriteStartElement(string prefix, string localName, string ns)
{
this.writer.WriteStartElement(prefix, localName, ns);
}
public override void WriteString(string text)
{
this.writer.WriteString(text);
}
public override void WriteSurrogateCharEntity(char lowChar, char highChar)
{
this.writer.WriteSurrogateCharEntity(lowChar, highChar);
}
public override void WriteValue(bool value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(DateTime value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(decimal value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(double value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(int value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(long value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(object value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(float value)
{
this.writer.WriteValue(value);
}
public override void WriteValue(string value)
{
this.writer.WriteValue(value);
}
public override void WriteWhitespace(string ws)
{
this.writer.WriteWhitespace(ws);
}
public override XmlWriterSettings Settings
{
get
{
return this.writer.Settings;
}
}
protected XmlWriter Writer
{
get
{
return this.writer;
}
set
{
this.writer = value;
}
}
public override System.Xml.WriteState WriteState
{
get
{
return this.writer.WriteState;
}
}
public override string XmlLang
{
get
{
return this.writer.XmlLang;
}
}
public override System.Xml.XmlSpace XmlSpace
{
get
{
return this.writer.XmlSpace;
}
}
}
A continuación, proporcione una clase derivada que anule el método StartElement, como antes:
public class NamespaceSupressingXmlWriter : XmlWriterWrapper
{
//Provide as many contructors as you need
public NamespaceSupressingXmlWriter(System.IO.TextWriter output)
: base(XmlWriter.Create(output)) { }
public NamespaceSupressingXmlWriter(XmlWriter output)
: base(XmlWriter.Create(output)) { }
public override void WriteStartElement(string prefix, string localName, string ns)
{
base.WriteStartElement("", localName, "");
}
}
Y luego usa este escritor así:
var o2= new MyTypeWithNamespaces { ..intializers.. };
var builder = new System.Text.StringBuilder();
var settings = new XmlWriterSettings { OmitXmlDeclaration = true, Indent= true };
using ( XmlWriter innerWriter = XmlWriter.Create(builder, settings))
using ( XmlWriter writer = new NamespaceSupressingXmlWriter(innerWriter))
{
s2.Serialize(writer, o2, ns2);
}
Console.WriteLine("{0}",builder.ToString());
Gracias a Oleg Tkachenko .
...
XmlSerializer s = new XmlSerializer(objectToSerialize.GetType());
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add("","");
s.Serialize(xmlWriter, objectToSerialize, ns);
XmlSerializer sr = new XmlSerializer(objectToSerialize.GetType());
TextWriter xmlWriter = new StreamWriter(filename);
XmlSerializerNamespaces namespaces = new XmlSerializerNamespaces();
namespaces.Add(string.Empty, string.Empty);
sr.Serialize(xmlWriter, objectToSerialize, namespaces);