WCF : serialiser un objet non serialisable lorsque l'on n'a pas accès au type
Lorsqu'on utilise un service WCF, il se peut que l'on ne possède pas le contrôle des différents types que l'on transfère. Dans ce cas il est possible d'avoir des problèmes pour sérialiser, en effet, ne pouvant pas modifier le type, on ne peut pas rajouter des attributs utiles à la sérialisation, rajouter un constructeur vide, ...
Ces problèmes surviennent quelques soit le binding utilisé, dans la suite de mon exemple j'utilise le netTcpBinding.
J'ai récemment été confronté à ce problème, après pas mal de recherche, j'ai trouvé une solution qui me convenait, celle-ci ne nécessite pas de réécrire une version simplifié de l'objet à transiter.
Pour cela j'ai créé une classe possédant une propriété du type de l'objet à transférer. Dans mon exemple, l'objet qui pose problème lors de la sérialisation est de type Query.
public class QueryEncapsulator{
public QueryEncapsulator()
{ }
public QueryEncapsulator(Query q)
{
this.Query = q;
}
public Query Query { get; set; }
}
Bien sur, cela ne suffit pas pour résoudre le problème, l'étape suivante est d'implémenter l'interface IXmlSerializable. Cette interface va nous permettre de personnaliser la sérialisation de cet objet, pour ne pas avoir de problème nous allons utiliser une sérialisation binaire via le BinaryFormatter puis écrire le binaire dans le XMLWriter en base64.
[Serializable]
public class QueryEncapsulator : IXmlSerializable
{
public QueryEncapsulator()
{ }
public QueryEncapsulator(Query q)
{
this.Query = q;
}
public Query Query { get; set; }
public System.Xml.Schema.XmlSchema GetSchema()
{
return null;
}
public void ReadXml(System.Xml.XmlReader reader)
{
BinaryFormatter formatter = new BinaryFormatter();
Byte[] b = new Byte[1024];
using (MemoryStream ms = new MemoryStream())
{
if (!reader.Read())
throw new Exception("boom");
int i;
while ((i = reader.ReadContentAsBase64(b, 0, b.Length)) > 0)
{
ms.Write(b, 0, i);
}
ms.Position = 0;
Query obj = (Query)formatter.Deserialize(ms);
this.Query = obj;
}
}
public void WriteXml(System.Xml.XmlWriter writer)
{
using (MemoryStream ms = new MemoryStream())
{
BinaryFormatter formatter = new BinaryFormatter();
formatter.Serialize(ms, this.Query);
Byte[] b = ms.ToArray();
writer.WriteBase64(b, 0, b.Length);
}
}
}
A partir de là, nous pouvons transférer notre objet QueryEncapsulator à la place de notre objet Query.
Mais pourquoi cette astuce fonctionne ? En effet, pourquoi une sérialisation binaire via le BinaryFormatter fonctionne alors que lorsqu'on utilise le NetTcpBinding le message est transféré sous forme binaire.
[disclaimer:information non confirmé] D'après ce que j'ai compris, quelque soit le binding utilisé la sérialisation WCF se fait via le DataContractSerializer. Ce DataContractSerializer utilise en interne les mécanismes sous-jacent de la sérialisation XML afin de créer le graph de la sérialisation. Cela implique que l'on peut utiliser l'interface IXmlSerializable afin de personaliser la sérialisation.
Niveau performance, le surcout de cette astuce est négligeable par rapport à la machinerie WCF, dans mon cas, le surcout ne prend pas 1ms alors que le service met entre 5 et 30ms à répondre.