Dot.Blog

C#, XAML, Xamarin, UWP/Android/iOS

Silverlight et la sérialisation

La sérialisation est un besoin fréquent que le framework .NET sait parfaitement gérer. La sérialisation binaire s'obtient d'ailleurs le plus naturellement du monde en ajoutant un simple attribut [Serializable] à la définition de la classe. Mais voilà, essayez de décorer une classe avec SerializableAttribute sous Silverlight... Surprise ! Un message d'erreur vous prévient que l'espace de nom ne peut être trouvé.

Pas de sérialisation ?

Non. Pas de sérialisation binaire en tout cas sous Silverlight jusqu'à maintenant (v 3.0 au moment de ce billet). La classe SerializableAttribute n'est tout simplement pas définie dans System.

Avant d'aller crier au scandale sur les blogs ou forums officiels essayons de réfléchir...

A quoi sert la sérialisation ?

Sérialiser, cela sert à prendre un "cliché" d'une instance dans le but d'être capable de recréer une copie parfaite de cette dernière au bout d'un certain temps.

Un certain temps... cela peut être une milliseconde ou bien 1 jour ou 1 an. Dans le premier cas le stockage du "cliché" est en mémoire généralement, mais dans le second, il s'agit le plus souvent d'un stockage persistant tel qu'un fichier disque ou une base de données.

Pas de gestion de disque local en RIA

La nature même de Silverlight et le style bien particulier d'applications qu'il permet d'écrire fait qu'il n'existe aucune gestion de disque local. En réalité il n'existe rien qui permette d'accéder à la machine hôte, en lecture comme en écriture, question de sécurité.

On notera quelques exceptions à cette règle : l'OpenFileDialog qui permet d'ouvrir uniquement certains fichiers sur les disques de l'hôte et l'IsolatedStorage, espace de stockage local protégé pouvant être utilisé par une application Silverlight. Toutefois OpenFileDialog ne règle pas le problème de persistance et si l'IsolatedStorage peut être exploité en ce sens il s'agit d'un espace restreint, inlocalisable par un utilisateur "normal" donc in-sauvegardable sélectivement. Autant dire que l'IsolatedStorage peut rendre des services ponctuels (cache de données principalement) mais que ce n'est certainement pas l'endroit où stocker des données sensibles ou à durée de vie un peu longue.

Bref, en dehors de ces exceptions qui ne règlent pas tout à fait le besoin de persistance des instances, une application de type RIA ne peut sérieusement gérer des données que distantes...

En tout logique...

Reprenons : la sérialisation sert à persister des instances pour un stockage à plus ou moins longue échéance ou une communication avec un serveur distant. La nature d'une application RIA supprime la liberté d'un stockage local fiable et facilement maitrisable.

En toute logique sérialiser des instances sous Silverlight n'a donc aucun intérêt, sauf pour communiquer avec un serveur distant.

Comme le framework .NET pour Silverlight est une version light du framework complet il a bien fallu faire des coupes sombres... La sérialisation n'a pas échappé à cette rigueur. La principale raison d'être de la sérialisation sous Silverlight étant la communication (quelle que soit la technologie cela se fait sur une base XML le plus souvent) et cela réclamant une sérialisation XML plutôt que binaire, SerializableAttribute et sa sérialisation binaire ont ainsi été "zappés" !

Une solution

La situation est grave mais pas désespérée. S'il reste difficile le plus souvent d'utiliser directement sous Silverlight des classes conçues pour le framework complet (et pas seulement à cause de la sérialisation binaire), il est tout à fait possible de sérialiser des instances sous Silverlight à condition d'utiliser les mécanismes qui servent aux communications avec les serveurs distants.

Le DataContract

Silverlight utilise une partie du mécanisme WCF du DataContract (mais l'attribut DataMember n'existe pas). On trouve même une classe le DataContractSerializer qui fournit le nécessaire pour sérialiser et désérialiser des instances même si celles-ci ne sont décorées d'aucun attribut particulier.

Au final la sérialisation sous Silverlight est plus simple que la sérialisation binaire par SerializableAttribute sous le framework complet !

Un exemple de code

Einar Ingebrigtsen, un MVP nordiste travaillant en scandinavie, à eu la bonne idée de proposer deux méthodes utilitaires qui montrent comment utiliser le DataContractSerializer. Plutôt que d'imiter et réinventer la roue, regardons son code :

   1:  public string Serialize<T>(T data)
   2:          {
   3:              using (var memoryStream = new MemoryStream())
   4:              {
   5:                  var serializer = new DataContractSerializer(typeof(T));
   6:                  serializer.WriteObject(memoryStream, data);
   7:   
   8:                  memoryStream.Seek(0, SeekOrigin.Begin);
   9:   
  10:                  var reader = new StreamReader(memoryStream);
  11:                  string content = reader.ReadToEnd();
  12:                  return content;
  13:              }
  14:          }
  15:   
  16:          public T Deserialize<T>(string xml)
  17:          {
  18:              using (var stream = new MemoryStream(Encoding.Unicode.GetBytes(xml)))
  19:              {
  20:                  var serializer = new DataContractSerializer(typeof(T));
  21:                  T theObject = (T)serializer.ReadObject(stream);
  22:                  return theObject;
  23:              }
  24:          }

C'est simple et efficace. Attention, pour utiliser ce code il faudra ajouter une référence à System.Runtime.Serialization.dll, puis un using System.Runtime.Serialization.

L'utilisation de ces méthodes tombe dès lors sous le sens, mais un petit exemple est toujours plus parlant :

   1:  { ....
   2:  // nouvelle instance 
   3:  var t = new Test { Field1 = "test de serialisation", Field2 = 7 };
   4:  t.List.Add(new test2 { Field1 = "item 1" });
   5:  t.List.Add(new test2 { Field1 = "item 2" });
   6:  t.List.Add(new test2 { Field1 = "item 3" });
   7:   
   8:  // sérialisation et affichage
   9:  var s = Serialize<Test>(t);
  10:  var sp = new StackPanel { Orientation = Orientation.Vertical };
  11:  LayoutRoot.Children.Add(sp);
  12:  sp.Children.Add(new TextBlock { Text = s });
  13:   
  14:  // désérialisation et affichage
  15:  Var t2 = Deserialize<Test>(s);
  16:  var result = t2 == null ? "null" : "instantiated";
  17:  sp.Children.Add(new TextBlock { Text = result });
  18:  }
  19:   
  20:  // la classe de test
  21:  public class Test
  22:  {
  23:    public string Field1 { get; set; }
  24:    public int Field2 { get; set; }
  25:    private ObservableCollection<test2> list = 
  26:                  new ObservableCollection<test2>();
  27:    public ObservableCollection<test2> List { get { return list; } }
  28:  }
  29:   
  30:  // la classe des éléments de la liste de la classe Test
  31:  public class test2
  32:  {
  33:    public string Field1 { get; set; }
  34:  }

Conclusion

Une fois que l'on a compris pourquoi la sérialisation binaire est absente de Silverlight, et une fois qu'on a trouvé une solution de rechange, on se sent beaucoup mieux :-)

Il reste maintenant tout à fait possible de stocker en local le résultat de la sérialisation dans un fichier texte (en utilisant l'IsolatedStorage sur lequel je reviendrai) ou bien de le transmettre à service Web ou autre...

Stay Tuned !

blog comments powered by Disqus