[XmlSerializer] El atributo XmlInclude: Aplicando conceptos de POO en la serialización de las clases
Imaginaros que tenéis que almacenar información de los medios de transporte que dispone una empresa, como puede puede ser coches, barcos, aviónes… y dicha información ha de ser serializada en un fichero XML. Cada entidad especifica como el coche o el avión tienen caracteristicas diferentes, un coche tiene marchas, cilindrada… mientras que un avión podría tener el número de motores que dispone… La pregunta es, ¿Como podemos serializar esta información, de manera que sí añadimos un nuevo medio de transporte, no tengamos que tocar nada de nuestro código de serialización, ni añadir nuevas colecciones ni propiedades a nuestra clases bases?, es decir, hacerlo de una manera genérica y no tener por cada tipo de transporte una lista que contenga objetos de ese tipo. Pues manos a la obra:
Lo primero será crear una clase abstracta que la llamaremos MedioTransporte, a la que añadiremos un a propiedad que tendrán en común que será la marca del transporte, ya que el tipo nos lo dará la propia entidad:
namespace Geeks.Serializer.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
[Serializable]
[XmlRootAttribute("transporte")]
[XmlInclude(typeof(Coche))]
[XmlInclude(typeof(Avion))]
public abstract class MedioTransporte
{
[XmlAttribute("marca")]
public string Marca
{
get;
set;
}
}
}
Ahora creamos 2 medios de transporte, Coche y Avion, que heredan de MedioTransporte:
namespace Geeks.Serializer.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
[Serializable]
public class Coche : MedioTransporte
{
[XmlAttribute("cilindrada")]
public int Cilindrada
{
get;
set;
}
[XmlAttribute("marchas")]
public int Marchas
{
get;
set;
}
}
}
namespace Geeks.Serializer.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
[Serializable]
public class Avion : MedioTransporte
{
[XmlAttribute("numeromotores")]
public int NumeroMotores
{
get;
set;
}
}
}
Y por supuesto, la clase general que es la Empresa:
namespace Geeks.Serializer.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
[Serializable]
[XmlRootAttribute("empresa")]
public class Empresa
{
[XmlAttribute("nombre")]
public string Nombre
{
get;
set;
}
[XmlArray("mediosdetransporte")]
[XmlArrayItem("mediodetransporte")]
public List<MedioTransporte> Transportes
{
get;
set;
}
}
}
Creamos una aplicación de consolo para probar:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
using System.IO;
using Geeks.Serializer.Entities;
namespace Geeks.Serializer
{
class Program
{
static void Main(string[] args)
{
XmlSerializerNamespaces ns = new XmlSerializerNamespaces();
ns.Add(String.Empty, String.Empty);
XmlSerializer serializer =
new XmlSerializer(typeof(Empresa));
Empresa empresa = new Empresa();
empresa.Nombre = "Transportes Urgentes NET";
empresa.Transportes = new List<MedioTransporte>();
Coche coche = new Coche();
coche.Marca = "Seat";
coche.Cilindrada = 1200;
coche.Marchas = 5;
empresa.Transportes.Add(coche);
Avion avion = new Avion();
avion.Marca = "Airbus 380";
avion.NumeroMotores = 4;
empresa.Transportes.Add(avion);
using (TextWriter writer = new StreamWriter(@"C:\temp\config.xml"))
{
serializer.Serialize(writer, empresa, ns);
}
}
}
}
Ejecutamos y este es el resultado:
<empresa nombre="Transportes Urgentes NET">
<mediosdetransporte>
<mediodetransporte d3p1:type="Coche"
marca="Seat"
cilindrada="1200"
marchas="5"
xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
<mediodetransporte d3p1:type="Avion"
marca="Airbus 380"
numeromotores="4"
xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
</mediosdetransporte>
</empresa>
¿Y donde está el truco para que salga el type de la entidad y la POO que se ha aplicado?
La POO la hemos aplicado al crear la clase abstracta MedioTransporte y heredar de ella en Coche y Avion, porque sino hacemos esto tendríamos que haber hecho algo como esto:
namespace Geeks.Serializer.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
[Serializable]
[XmlRootAttribute("empresa")]
public class Empresa
{
[XmlAttribute("nombre")]
public string Nombre
{
get;
set;
}
[XmlArray("coches")]
[XmlArrayItem("coche")]
public List<Coche> Coches
{
get;
set;
}
[XmlArray("aviones")]
[XmlArrayItem("aviones")]
public List<Avion> Aviones
{
get;
set;
}
}
}
Por cada nuevo medio de transporte, tenemos que estar añadiendo una nueva colección a la clase Empresa y además se serializará en un nuevo elemento, cosa que para mí queda bastante feo, además habría que estar tocando cada 2x3 la clase.
Y aquí es donde entra en juego el atributo XmlInclude, si añadimos un nuevo medio de transporte, solo necesitamos añadir un atributo nuevo a la clase MedioTransporte para que lo serialice, por ejemplo añadimos un Barco:
namespace Geeks.Serializer.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
[Serializable]
public class Barco : MedioTransporte
{
[XmlAttribute("eslora")]
public int Eslora
{
get;
set;
}
}
}
namespace Geeks.Serializer.Entities
{
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Serialization;
[Serializable]
[XmlRootAttribute("transporte")]
[XmlInclude(typeof(Coche))]
[XmlInclude(typeof(Avion))]
[XmlInclude(typeof(Barco))]
public abstract class MedioTransporte
{
[XmlAttribute("marca")]
public string Marca
{
get;
set;
}
}
}
Añadimos a la colección de Transportes un barco:
Barco barco = new Barco();
barco.Eslora = 24;
empresa.Transportes.Add(barco);
Y vemos el XML:
<?xml version="1.0" encoding="utf-8"?>
<empresa nombre="Transportes Urgentes NET">
<mediosdetransporte>
<mediodetransporte d3p1:type="Coche"
marca="Seat"
cilindrada="1200"
marchas="5"
xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
<mediodetransporte d3p1:type="Avion"
marca="Airbus 380"
numeromotores="4"
xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
<mediodetransporte d3p1:type="Barco"
eslora="24"
xmlns:d3p1="http://www.w3.org/2001/XMLSchema-instance" />
</mediosdetransporte>
</empresa>
A mí me parece una solución bastante elegante.
Adjunto el código de ejemplo.
Salu2