Bienvenue à Blogs CodeS-SourceS Identification | Inscription | Aide

Actualités

  • Blog de Cyril DURAND, passionné de JavaScript, Ajax, ASP.net et tout ce qui touche au developpement Web Client-Side.

    View Cyril Durand's profile on LinkedIn

    hit counters

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.

Posted: mardi 17 juin 2008 21:19 par cyril
Ce post vous a plu ? Ajoutez le dans vos favoris pour ne pas perdre de temps à le retrouver le jour où vous en aurez besoin :

Commentaires

VincentG a dit :

A vérifier, mais il me semble que si la classe à sérialiser n'a pas l'attribut [DataContract] alors ce n'est pas pas le DataContractSerializer .Net 3 qui va être utilisé mais le XmlSerializer .Net 2.

Dans ce cas il est donc logique que ton code soit pris en compte, ta classe étant [Serializable] et implémentant IXmlSerializable.

Il devrait d'ailleurs fonctionner avec des WS Soap asp.Net 1.1

NB : Plutot qu'un QueryEncapsulator, j'aurais bien vu un Encapsulator

<Query> ;)

# juin 18, 2008 09:23

cyril a dit :

hum,

Je vais essayer de vérifier ce que tu dis avec le [DataContract]. D'après ce que tu dis, si notre classe ne possède pas l'attribut DataContract, c'est du XML qui est ensuite converti en binaire ?

Pourtant il me semble que dans le stack trace, je vois DataContractSerializer.

Sinon pour le Encapsulator

<T> j'y ai aussi pensé en écrivant ce post :-)

# juin 18, 2008 11:03

RaptorXP a dit :

Attention, ce n'est pas parceque tu utilise un NetTcpBinding que tout sera serialize en binaire. Par exemple lorsque tu envoie un DataSet par WCF (c'est mal, je sais), c'est serialisé en XML (je crois) meme avec NetTcp

# juin 18, 2008 19:40

cyril a dit :

C'est ce que je suis en train de vérifier, ton propos est cohérent avec celui de Vincent, en fait le DataContractSerializer utiliserait une serialization binaire pour tous les objets décorés avec [DataContract] pour les autres il y aurait une sérialisation XML puis ensuite le binding prend le main, donc on retourne de nouveau sur du XML.

Bref ca semble un peu compliqué et bien usine à gaz. Si quelqu'un à des explications : je prend.

# juin 18, 2008 19:51

VincentG a dit :

J'ai vérifié, et contrairement a ce que je pensais, par défaut WCF utilise le DataContractSerializer.

Et ce indifféremment sur des DataContract, [Serializable] et IXmlSerializable...

cf. http://msdn.microsoft.com/en-us/library/ms733901.aspx

Sinon pour l'aspect binaire / xml je crois (a verifier bis ;) que la réponse est "encodage binaire xml", les deux donc, le xml serait optimisé les balises etant encodées et le contenu en clair sauf pour les champs binaires qui ne sont pas transformés en base 64.

NB qui m'incite a croire ca :

pulic sealed class DataContractSerializer : XmlObjectSerializer

J'ai pas trouvé de doc claire sur le sujet :(

Une piste : http://msdn.microsoft.com/en-us/library/ms735115.aspx

(la flemme de reflector le framework .Net ;)

Bonne nuit!

# juin 18, 2008 22:57

usingsystemnet a dit :

"Et ce indifféremment sur des DataContract, [Serializable] et IXmlSerializable..." --&gt; oui c'est effectivement le cas depuis le sp1 du 3.5 si je me trompe pas ce qui n'était pas le cas avant ou il fallait décorer ses entités avec l'attribut DataContract.

concernant la serialisation xml pour les point de communication orienté connexion c'est effectivement du xml encodé en binaire et pour le http de la serialisaison xml simple (sachant qu'on peut très bien faire du binary over http en faisant du custom binding). Pour les champs binaire, bien penser à utiliser le mtom comme format de message.

# juin 19, 2008 12:00

cyril a dit :

Le SP1 de .net 3.5 fait quelque chose à ce niveau là, mais je ne sais pas exactement quoi ni comment, je n'ai pas le SP1 de .net 3.5 sur ma machine, il faut que je me renseigne sur ces différentes nouveautés.

# juin 19, 2008 16:06

VincentG a dit :

Négatif, le sp1 du 3.5 n'y change rien c'est comme ca dès le 3 : Cf. http://msdn.microsoft.com/en-us/library/ms733901(VS.85).aspx

# juin 19, 2008 18:47

usingsystemnet a dit :

Visual Studio 2008 and .NET Framework 3.5 Service Pack 1 Beta Change

...

Expanding reach of DataContract Serializer by relaxing the need of having [DataContract]/ [DataMember] on types and by supporting an interoperable mechanism for dealing with object references.

...

--&gt;http://msdn.microsoft.com/en-us/vstudio/products/cc533447.aspx

# juin 19, 2008 20:18

shieldd a dit :

Bonjour,

j'aimerai une précision car il y a quelque chose qui me parait flou dans tout ça.

L'attribut [Serializable] sur ton encapsulator est seulement utile pour la serialisation avec BinarryFormatter et SoapFormatter,

quant à ton interface IXmlSerializable, il me semblait qu'elle etait seulement utile au XmlSerializer (et DataContractSerializer si je ne me trompe pas).

Je ne comprend pas pourquoi à la fois l'interface IXmlSerializer et l'attribut Serializable

serait utile à un serializer.

os : Utiliser le BinaryFormatter sur un objet (Query) implique quand même la contrainte que Query soit marqué Serializable, et que tout ses attributs le doit aussi, chose que l'on ne controle pas forcement.

La solution a ce type de probleme peut paraitre laborieuse, mais la web service factory préconise de faire un classe "Translator" qui sert a mapé entre tes objets du domaine non serializable, et les contrats de données.

# juin 20, 2008 18:23
Les commentaires anonymes sont désactivés

Les 10 derniers blogs postés

- [WPF] Nouvel article sur c2i.fr par Richard Clark le il y a 1 heure et 21 minutes

- F# nouvelle CTP 1.9.6.2 (update) par Pierrick's Blog le il y a 5 heures et 26 minutes

- La suite ...Proposition de collaboration rédactionnelle entre les communautés de développeurs et Microsoft France par LucasR le 09-05-2008, 17:45

- [Fun] Votre simulateur de vol avec Microsoft ESP par Julien Chable le 09-05-2008, 12:02

- [Best Practices] Customisation du My Site : Comment le modifier en amont et en aval par The Mit's Blog le 09-05-2008, 10:47

- Patrick Tisseghem s'en est allé ... par The Mit's Blog le 09-05-2008, 10:04

- MS AutoCollage par alex# le 09-05-2008, 09:18

- Un grand SharePointeur nous a quitte : Patrick Tisseghem manquera à la communauté ! par RedoBlog - The .NET Gentleman !!! le 09-05-2008, 08:52

- [WPF] Comment charger dynamiquement un fichier XAML qui définit des eventhandler ? par Thomas Lebrun le 09-04-2008, 10:56

- Article sur le filtrage des modèles de site SharePoint par The Grib's Lair [Sébastien PICAMELOT - MVP SharePoint] le 09-04-2008, 00:11