[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

Published 4/11/2009 18:15 por Luis Ruiz Pavón
Archivado en: ,,,
Comparte este post:
http://geeks.ms/blogs/lruiz/archive/2009/11/04/xmlserializer-el-atributo-xmlinclude-aplicando-conceptos-de-poo-en-la-serializaci-243-n-de-las-clases.aspx

Comentarios

# re: [XmlSerializer] El atributo XmlInclude: Aplicando conceptos de POO en la serialización de las clases

Buenas :)

Antes que nada, si me permites, una puntualización:

No necesitas [Serializable] si vas a serializar tu clase mediante Xml. El atributo [Serializable] aplica a la serialización binaria. ;-)

Ahora, sobre el tema del post... a mi el uso de [XmlInclude] no me ha convencido nunca (desde un punto de vista un poco metafísico). La razón es simple: traslada decisiones que deberían estar en las clases derivadas, a la clase base.

Imagina que yo cojo tu librería de vehículos y decido crear una nueva clase derivada de MedioTransporte, llamada Hovercraft.

En este momento cualquier código que esté en TU librería y que serializase objetos o colecciones MedioTransporte no podría ser usado con mi clase, puesto que el serializador se quejaría... ya que TU no metiste un [XmlInclude] en TU clase, para dar soporte a MI clase que obviamente ni sabías que existía.

Hay otros métodos, cierto, para evitar esto, pero requieren trabajo extra para tí y para mí, lo que muy elegante no es... Al menos en mi opinión.

Resumiendo, que XmlInclude es una buena solución sólo cuando tienes a mano el código fuente de la clase base, lo que no ocurre siempre (ok reconozco que muchas veces sí), e incluso si ocurriese, a mi no me termina de convencer el hecho de que debas modificar la clase base sólo por el hecho de añadir una clase derivada!

Saludos!

Friday, November 06, 2009 9:30 AM por Eduard Tomàs i Avellana

# re: [XmlSerializer] El atributo XmlInclude: Aplicando conceptos de POO en la serialización de las clases

señore,s cuál es la solución correcta, cuáles son esos métodos aunque requieran trabajo ??

sobre todo pensando en evolución d elas clases, que cambiarán con el tiempo... y que haya mínimo impacto para quien utilice las clases... que puede ser empresa externa etc..

gracias salu2

Wednesday, November 11, 2009 11:57 AM por preguntoncojonero