Etiquetado: xml

Serialización dinámica con el XmlSerializer

Para acabar con esta espontanea serie de posts sobre el XmlSerializer os cuento un par de trucos no demasiado bien documentados para controlar la serialización.

En algunas ocasiones podemos necesitar que una clase hija no persista algunas de las propiedades del base, pero, como ya hemos visto antes, si la propiedad no es virtual se complica un poco el asunto. En estos casos podemos añadir un miembro público a la clase base con el mismo nombre de la propiedad + Specified con el valor a true:

public class C1
{
	public string MyProperty { get; set; }

	[XmlIgnore]
	public bool MyPropertySpecified = true;
}

De esta manera al heredar de la clase podremos especificar su valor a false, lo que hará que no se persista dicha propiedad:

public class C2:C1
{
	public string MyOtherProperty { get; set; }

	public C2()
	{
		MyPropertySpecified = false;
	}
}

En otras ocasiones, lo que querremos es decidir cuando se debe persistir el valor y cuando no. Por ejemplo, si una lista no tiene valores o si un string es null puede que no nos interese que aparezca el valor vacío en el xml. Eso se consigue con un método público llamado ShouldSerialize + el nombre de la propiedad y que devuelva un bool indicando si la propiedad debe persistirse:

public class C3 : C2
{
	public string MyThirdProperty { get; set; }

	public bool ShouldSerializeMyThirdProperty()
	{
		return false;
	}
}

Ahora podéis probar las clases, adivinad qué mostrará cada llamada:

XmlSerializer ser = new XmlSerializer(typeof(C1),
	new Type[] { typeof(C2), typeof(C3) });
ser.Serialize(Console.Out, new C1() { MyProperty = "test"});
Console.WriteLine();
ser.Serialize(Console.Out, new C2() { MyProperty = "test",
	MyOtherProperty = "test 2" });
Console.WriteLine();
ser.Serialize(Console.Out, new C3() { MyProperty = "test",
	MyOtherProperty = "test 2", MyThirdProperty="test 3" });
Console.ReadLine();
Anuncios

XmlSerializer y ocultación de propiedades

Esta mañana tras responder a un post de Eduard Tomàs i Avellana me ha dado por investigar un poco más sobre el tema…
En su caso el problema tenía solución pues podía modificar ambas clases, pero ¿qué pasaría si estamos heredando de una clase que no podemos modificar? Hay múltiples soluciones, desde el uso de un patrón Adapter hasta la implementación del interface IXmlSerializable, pero todas ellas requieren escribir una cantidad considerable de código.
Pero… en algunos casos nos podría servir una sobrecarga del XmlSerializer que admite como argumento una instancia del XmlAttributeOverrides, que, como su propio nombre indica, nos permite sobrecargar los atributos que usa el XmlSerializer sobre una clase para generar el código de serialización. Aunque tiene sus peligros podremos influir en cómo se serializa la clase base sin tener que modificarla.
Por ejemplo, si tuviéramos la siguiente definición de clases:

public class C1
{
List<C1> _myList = new List<C1>();
public List<C1> MyList { get { return _myList; } }
}

public class C2:C1
{
List<C2> _myList = new List<C2>();
public new List<C2> MyList { get { return _myList; } }
}

Al crear el XmlSerializer nos daría un error de reflexión como pasaba en el caso de Eduard. Pero podemos obligar al serializador que ignore la propiedad de la clase base mediante el siguiente código:


XmlAttributeOverrides xOver = new XmlAttributeOverrides();
XmlAttributes atts = new XmlAttributes(){ XmlIgnore=true};
xOver.Add(typeof(C1), "MyList", atts);
XmlSerializer ser = new XmlSerializer(typeof(C2),xOver);
ser.Serialize(Console.Out, new C2());

Aunque hay que usarlo con extremo cuidado, pues cualquier otra instancia del tipo C1 o derivados no serializará la propiedad MyList con ese serializador.
¿Alguien más se anima a encontrar otra solución?

Optimización de XmlSerializer

Ayer, haciendo una de revisión en un servicio,  nos dimos cuenta que se hacían continuamente llamadas al csc.exe. Esto provocaba que el servicio fuera muy lento y además consumiera mucha memoria, así que nos pusimos manos a la obra.

Como sabíamos que el servicio hacía un uso intensivo de la serialización Xml con el XmlSerializer no hizo falta hacer debug sino que fuimos a mirar el código que probablemente invocaba al csc.

Paso directamente a describir cómo funciona y el porqué del comportamiento que encontramos.

En .Net serializar una clase sencilla (sin referencias circulares, con al menos el constructor por defecto, etc…) a Xml es bastante directo, sólo necesitas crear un XmlSerializer para la clase en cuestión, por ejemplo, si tenemos un conjunto de clases así:
public class BaseClass
{
public int BaseProperty { get; set; }
}

public class TestClass
{
public string MyProperty1 { get; set; }
public BaseClass MyProperty2{get;set;}
}

Para serializar TestClass podríamos usar el siguiente código:

TestClass xmlSerializableClass =
new TestClass { MyProperty1 = "test", MyProperty2 = new BaseClass()};
XmlSerializer serializer = new XmlSerializer(typeof(TestClass));
serializer.Serialize(Console.Out, xmlSerializableClass);

Al ejecutarse genera un archivo .cs con el código de serialización de nuestra clase lo compila y lo ejecuta para serializarlo. La siguiente vez la clase XmlSerializer no volverá a compilar pues guarda internamente una lista con (casi) todos los serializadores que va creando.

Si heredamos de BaseClass y queremos que nuestra nueva clase se incluya en el serializador hay que agregar un atributo para que el XmlSerializer la tenga en cuenta automáticamente, en caso contrario nos daría un error en tiempo de ejecución:
public class AnotherClass:BaseClass
{
public int AnotherProperty { get; set; }
}

[XmlInclude(typeof(AnotherClass))]
public class BaseClass
{
public int BaseProperty { get; set; }
}

De esta manera el siguiente código seguirá funcionando y utilizando el caché:

TestClass xmlSerializableClass =
new TestClass { MyProperty1 = "test", MyProperty2 = new AnotherClass()};
XmlSerializer serializer = new XmlSerializer(typeof(TestClass));
serializer.Serialize(Console.Out, xmlSerializableClass);

Hasta aquí bien, pero ¿qué pasa si en tiempo de compilación aún no sabemos exactamente cuantas clases heredarán de BaseClass? Podría ocurrir que tuvieramos una aplicación con plugins y que pudieran añadir más clases ahí. Para eso el XmlSerializer tiene otro constructor donde le podemos pasar una lista de tipos a añadir al serializador aparte de los que haya indicados en el atributo:

XmlSerializer serializer = new XmlSerializer(typeof(TestClass),
new Type[]{typeof(NotIncludedClass)});

El único problema de este constructor es que no guarda la clase autogenerada en el caché. Por lo tanto, cada vez que se ejecute el código se ejecutará un csc.exe y se cargará un nuevo assembly dentro de nuestra aplicación. Eso nos produce dos efectos:

  1. Nuestra aplicación se ralentiza
  2. Como los assemblies no se descargan nunca, la memoria no se libera

En estos casos nos conviene guardar una referencia al serializador. Espero haber ayudado a alguien. Podéis descargar el código de ejemplo aquí.

Para saber más, hace ya unos cuantos años escribí un pequeño  de introducción al XmlSerializer que podéis encontrar aquí.