C# Serializando una interfaz con XmlSerialization. « Devthisblog

Devthisblog Desarrollo .net, Diseño Web, Seguridad… Y todo lo que se me vaya ocurriendo…

septiembre 1, 2014

C# Serializando una interfaz con XmlSerialization.

Filed under: .net,API,Opinion,Tips — Etiquetas: , , , , — jnavero @ 8:31 pm

Aquí comienzo un post un tanto curioso, ya que haciendo pruebas me encontré con la situación de querer serializar una interfaz en Xml.  La solución se trata de un ejemplo de una implementación en MonoDeveloper usando IoC con Spring .NET

La solución que uso en el post como ejemplo se puede descargar desde GIT aquí.

Esta solución contiene 3 proyectos:

  • Entities.Contracts: Donde está la interfaz IDiaryEntry
  • Entities: Donde está la clase DiaryEntry que implementa la interfaz,
  • IoCSpringExampleForm: Este proyecto carga un GUI con un Calendar. Además de implementar el AppContext que utiliza Spring.NET

En el proyecto IoCSpringExampleForm existe la clase IOEntries donde se encuentran funciones de serialización. Si deseamos serializar con BinayFormatter, se serializa correctamente, aquí dejo el código concreto del que hablo:

 C | 
 
 copy code |
?

01
02
        public static void SaveEntriesToDisk(List<IDiaryEntry> entries)
03
        {
04
            BinaryFormatter bf = new BinaryFormatter();
05
            MemoryStream ms = new MemoryStream();
06
            bf.Serialize(ms, entries);
07
            ms.Position = 0;
08
            var bytes = ms.ToArray();
09
            File.WriteAllBytes(FileName, bytes);                                            
10
            ms.Close();
11
        }
12
 
13
        //Deserializa y carga datos.
14
        public static List<IDiaryEntry> LoadEntriesFromDisk()
15
        {
16
            if (File.Exists(FileName))
17
            {
18
                BinaryFormatter bf = new BinaryFormatter();
19
                Stream fileStream = new MemoryStream();
20
                fileStream = File.OpenRead(FileName);
21
                var obj = bf.Deserialize(fileStream);
22
                fileStream.Close();
23
                return (List<IDiaryEntry>)obj;
24
            }
25
           return null;
26
        }
27

Pero, resulta que lo que deseamos es una serialización con XmlSerializer, así que usamos un código similar a esto:

 C | 
 
 copy code |
?

01
02
     public static void SaveEntriesToDisk(List<IDiaryEntry> entries)
03
        {
04
            var memStream = new MemoryStream();
05
            using (var textWriter = new XmlTextWriter(memStream, Encoding.Unicode))
06
            {
07
                var serializer = new XmlSerializer(typeof(List<IDiaryEntry>));
08
                serializer.Serialize(textWriter, item);
09
                memStream = textWriter.BaseStream as MemoryStream;
10
            }
11
            if (memStream != null)
12
                return Encoding.Unicode.GetString(memStream.ToArray());
13
            else
14
                return null;
15
        } 
16

Obtenemos el siguiente resultado:

System.InvalidOperationException: There was an error reflecting type ‘System.Collections.Generic.List`1[[Entities.Contracts.IDiaryEntry, Entities.Contracts, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null]]’. —> System.InvalidOperationException: There was an error reflecting type ‘Entities.Contracts.IDiaryEntry’. —> System.InvalidOperationException: Entities.Contracts.IDiaryEntry cannot be serialized because it is an interface
at System.Xml.Serialization.ReflectionHelper…

Es decir, no se puede serializar una interfaz.
Buscando por google encontré varias referencias como:
How to Serialize Interfaces in .NET
y esta de XmlSerializer serialize generic List of interface

En stackoverflow hay varios ejemplos uno de ellos usa el BinaryFormater y otro ejemplo valido implementa la interfaz IXmlSerializable.
Esta interfaz implementa las funciones:
public void ReadXml(XmlReader reader) y public void WriteXml(XmlWriter writer) ambos con un foreach y sinceramente, aunque puede ser lo más correcto, no me gustan las formas.

Hablando con un compañero del trabajo (Fabio), propuso una solución bastante válida. Esta solución trata de castear a Object la interfaz pero presenta el problema de la deserialización que no se hace correctamente.  Esto lo hemos solucionado, con un poco de linq que nos obtiene los tipos y dando como resultado una posible solución sin tener que implementar la interfaz IXmlSerializable y creo que se hace y se puede comprender de forma fácil y rápida:

 C | 
 
 copy code |
?

01
02
namespace IoCSpringExampleForm
03
{
04
    //Realiza las operaciones de entrada o salida del diario (en un archivo)
05
    public static class  IOEntries
06
    {
07
        const string FileNameEntries = "DataDiary.dat";
08
 
09
        //Serializa y guarda datos
10
        public static  void SaveEntriesToDisk(List<IDiaryEntry> entries)
11
        {
12
            try
13
            {
14
                var OtherTypes = entries.Select(x => x.GetType()).Distinct().ToArray();
15
 
16
                //Save Entries to disk
17
                List<Object> ObjectEntries = new List<object>(entries);
18
                XmlSerializer serializer =  new XmlSerializer(ObjectEntries.GetType(), OtherTypes);
19
                var fileStream = new FileStream(FileNameEntries,  FileMode.Create);
20
                serializer.Serialize(fileStream, ObjectEntries);
21
                fileStream.Close();
22
            }
23
            catch (Exception e)
24
            {
25
                String msj = e.Message;
26
            }
27
        }
28
 
29
        //Deserializa y carga datos.
30
        public static  List<IDiaryEntry> LoadEntriesFromDisk()
31
        {
32
            if (File.Exists(FileNameEntries))
33
            {
34
                //First: I Need the types of the XML.
35
                var xDoc = XDocument.Parse(File.ReadAllText(FileNameEntries));
36
                var NameOfTypes = xDoc.Descendants(XName.Get("anyType")).Select(x => x.Attributes().First(y => y.Name.LocalName == "type").Value).Distinct().ToList();
37
                List<Type> TypesFromString = new List<Type>();
38
 
39
                ////For a normal type (if you not use IoC)
40
                ////NameOfTypes.ForEach(x => TypesFromString.Add(Type.GetType(x)));
41
                ////Like I Use Spring, I Can use it and reload the types...
42
                NameOfTypes.ForEach(x => TypesFromString.Add(AppContext.Instance.GetObject(x).GetType()));
43
                var DeserializeData = new XmlSerializer(typeof(List<Object>), TypesFromString.ToArray());
44
                var DataFileStream = new FileStream(FileNameEntries,  FileMode.Open);
45
                var ListOfEntries = (List<Object>)DeserializeData.Deserialize(DataFileStream);
46
                var result = new  List<IDiaryEntry>(ListOfEntries.OfType<IDiaryEntry>());
47
                return result;
48
            }
49
           return null;
50
        }
51
    }
52
}
53

Espero que se vea todo con claridad, desconozco si esto es más o menos correcto que la interfaz, así que si hay comentarios al respecto, los espero.

Saludos y hasta la próxima.

No hay comentarios »

No comments yet.

RSS feed for comments on this post. TrackBack URL

Leave a comment

Powered by WordPress