Introduction
La technologie Windows Communication Foundation (WCF) propose deux méthodes en vue de définir au sein de contrats de services les structures de données qui seront amenés à être échangées entre un service et un consommateur. Ces deux méthodes sont les suivantes : DataContract ou MessageContract. Comme leur nom l'indique, chacune suit une orientation spécifique respectivement donnée ou message mais elles ne sont en aucun cas antinomiques.
La première méthode est la plus instinctive pour qui connait depuis sa plus tendre enfance la programmation orientée objets, elle est dans la lignée de tout ce qui a été fait précédemment avec des technologies telles que Remoting ou WebService. La technologie WebService permettait bien une intervention sur les messages échangés, mais celle-ci était le plus souvent utiliser pour insérer des entêtes spécifiques aux messages et ne modifiait pas la moindre structure de données propres aux entités métiers échangés.
La méthode DataContract s'inscrit donc logiquement dans la voie historique de la modélisation des données lors d'une communication.
La méthode MessageContract est quant à elle une nouvelle solution. Mais elle n'est en aucun cas une rupture avec le passé, il ne s'agit pas de s'écarter de la programmation objet. Cette méthode permet avant tout de travailler au niveau du message véhiculé. Elle offre des outils de programmation facilitant grandement ce travail par rapport à ce qui avait été fait par le passé.
La suite de ce document vous présentera les deux méthodes en citant pour chacune ces faiblesses et ces avantages.
Vous avez dit contrat données ? Vous vouliez dire DataContract ?
Comme nous le disions en introduction, il est peu probable que cela ne soit pas cette méthode que vous utiliserez en premier pour publier les entités manipulés par votre premier service WCF puisqu'il n'y aura probablement aucune raison qui vous obligent à intervenir sur les messages qui encapsuleront les entités.
Chacune de vos classes métier doit être décorée par l'attribut DataContract, et chaque membre de celle-ci que l'on veut publier le sera par l'attribut DataMember.
Cette publication passe par une sérialisation des données décorées. Cette sérialisation utilise par défaut le sérialisateur DataContractSerializer qui a pour lui d'être compatible avec le sérialisateur Xml bien connu de la technologie WebService : XmlSerializer.
[DataContract(Namespace = "http://example.org/person")]
public class Person
{
[DataMember(Name = "first", Order = 0)]
public string First;
[DataMember(Name = "last", Order = 1)]
public string Last;
[DataMember(IsRequired = false, Order = 2)]
private string id;
}
Les types supportés par le sérialisateur DataContractSerializer sont les suivants :
- CLR built-in types
- Byte array, DateTime, TimeSpan, GUID, Uri, XmlQualifiedName, XmlElement et XmlNode array
- Les enumerations (Enum)
- Les classes décorées par l'attribut DataContract ou l'attribut CollectionDataContract.
- Les classes qui implémentent l'interface IXmlSerializable
- Les tableaux (Array) et les collections (y compris List<T>, Dictionary<K,V> et Hashtable)
- Les classes décorées par l'attribut Serializable ou implémentant l'interface ISerializable.
Le sérialisateur DataContractSerializer est capable de sérialiser un champ privé. Nous pouvons y voir une conséquence de sa compatibilité avec le sérialisateur XmlSerializer.
Il est à noter que la technologie WCF supporte l'encodage Remote Procedure Call (RPC). Les mots clefs ref et out peuvent donc très bien être utilisés en préfixe des paramètres des méthodes d'un service WCF.
Une lecture attentive de l'exemple de code présenté précédemment fait noter les différents paramètres liés à l'utilisation de l'attribut DataContract ou de l'attribut DataMember :
- Namespace permet de spécifier le namespace lié à l'entité et qui sera utilisé lors de la création du message SOAP.
- Order permet de spécifier l'ordre de sérialisation des membres de votre entité. Cela peut se révéler fort utile lorsqu'une de vos propriétés fait appel à une autre et donc que sa sérialisation dépend de celle précédemment faite de la propriété qu'elle référence.
- IsRequired permet de spécifier que le membre dit de votre entité est obligatoire en vue de réaliser la sérialisation.
Ces différents paramètres permettent bien entendu d'intervenir sur le message SOAP qui sera généré lors d'une communication entre un service WCF et son client. Mais cette intervention se limite à vos données et n'agit pas sur la structure même du message.
Références Cycliques
La technologie WCF ne supporte pas par défaut les références cycliques. La technologie WebService (Asmx) permettait de telles références via une modification du choix d'encodage des communications. Il fallait modifier celui sélectionné par défaut l'encodage Document Literal pour appliquer un encodage de type RPC. Le prix à payer était une perte au niveau des performances.
Bien entendu, il est recommandé de ne pas se trouver face à une telle problématique mais dans une telle situation, il peut être envisagé de spécialiser le comportement du sérialisateur DataContractSerializer :
- Via une implémentation de la classe DataContractSerializerOperationBehavior et le référencement de celle-ci au niveau des comportements personnalisés de votre service WCF.
[DataContract(Namespace = "http://example.org/person")]
[KnownType(typeof(AnimalCollection))]
public class Person
{
[DataMember(Name = "AnimalName", Order = 0)]
public AnimalCollection AnimalCollection
{
get { return _animalCollection; }
set { _animalCollection = value; }
}
}
- Via le développement d'un attribut personnalisé héritant de l'interface IOperationBehavior et son utilisation comme décoration des opérations sérialisant des données ayant des références cycliques.
Pour de plus amples détails quant à cette problématique, le lecteur est invité à lire le post « Preserving Object Reference in WCF » présentant en détail les étapes d'implémentation à suivre.
Devenir de la compatibilité avec la technologie WebService (Asmx)
Le recours aux solutions énoncées si dessus ne vous garantit pas malheureusement une compatibilité avec la technologie Asmx. Si l'utilisation de la fonction AddWebReference à partir de l'environnement de développement Visual Studio ne génèrera aucune erreur, les références cycliques pointeront vers des objets nuls.
Déclarer des types en vue de la sérialisation ou désérialisation d'une entité les référençant
A l'instar du mécanisme XmlInclude offert par le sérialisateur XmlSerializer, la technologie WCF offre également un moyen de qualifier certains types de connus en vue de permettre la sérialisation d'un type les référençant : l'utilisation de l'attribut KnowType ([KnownType(typeToKnow)]).
Si la propriété de l'entité à sérialiser a pour type une interface, il est nécessaire de déclarer un type comme connu implémentant cette interface.
[DataContract(Namespace = "http://example.org/person")]
[KnownType(typeof(AnimalCollection))]
public class Person
{
[DataMember(Name = "AnimalName", Order = 0)]
public AnimalCollection AnimalCollection
{
get { return _animalCollection; }
set { _animalCollection = value; }
}
}
Conclusion
Lors de ce chapitre, il n'a jamais été évoqué la moindre référence à la structure globale des messages qui seront échanges lors d'une communication avec un service WCF. Il est alors facile de comprendre pourquoi cette méthode est et restera la plus populaire, elle fait fi de la tuyauterie de communication utilisée par la technologie WCF et ainsi permet une adoption par le plus grand nombre. Malheureusement, l'hétérogénéité de votre environnement peut vous contraindre à intervenir sur la structure du message et donc vous amenez à utiliser les contrats de message.
Vous avez dit contrat de messages ? Vous vouliez dire MesssageContract ?
Comme son nom le laisse supposer, il ne s'agit plus d'intervenir sur vos entités mais également sur les messages dans laquelle elles viendront prendre place en vue d'établir une communication entre vos services WCF et leurs consommateurs.
Bien entendu, l'emploi de l'attribut MessageContract se combine avec l'attribut DataContract présenté au message précédent. Ces méthodes ne sont pas antinomiques. Chacune ayant un champ d'intervention spécifique.
Ainsi votre contrat de message contiendra le contrat de vos données métiers (DataContract) mais également celui lié à votre message en lui-même (Comment vos données seront-elles formatées dans votre message…).
La technologie Asmx
L'introduction se permettait une comparaison avec la technologie Asmx lorsque nous évoquions les différentes méthodes permettant d'exposer les contrats de vos entités métiers avec la technologie WCF.
Si peu sont intervenus sur la structure globale du message construit par la technologie Asmx, celle-ci ne l'interdisait pas. Ces paramètres sont en réalité fort utile au sein d'environnement hétérogène, lorsque votre service et votre client ne repose pas sur la même plateforme technologique. A titre d'exemple, il vous était possible via l'utilisation de l'énumération SoapParameterStyle de spécifier la façon dont les paramètres étaient mis en forme dans le message.
Il en va de même avec la technologie WCF, L'attribut MessageContract prend en paramètre IsWrapped qui vous permet d'agir sur la mise en forme des paramètres contenus dans votre message.
[MessageContractAttribute(IsWrapped=true)]
L'acronyme SOAP est très peu représenté en amont de ce chapitre, non pas que cette technologie ne soit pas utilisée lors de l'utilisation exclusive des contrats de données, mais il n'y avait aucune raison de l'évoquer. Pour ce qui est des contrats de messages, il est bon de se rappeler qu'un message SOAP est composé de deux parties : tête et corps. Il en ira donc de même pour vos contrats de messages publié à l'aide de la technologie WCF. Chacune partie se voit dédié un attribut : MessageHeader et MessageBodyMember.
Il devient dés lors beaucoup plus aisé de manipuler des contrats de message, et la est une évolution notable apportée par la technologie WCF.
[MessageContractAttribute]
public class EchoPersonMessageRequest
{
[MessageHeader(Namespace="http://example.org/EchoPersonMessageRequest/Header",
ProtectionLevel=ProtectionLevel.EncryptAndSign) ]
public Authorization authorization;
[MessageBodyMember(Namespace="http://example.org/EchoPersonMessageRequest/Message",
ProtectionLevel=ProtectionLevel.EncryptAndSign)]
public Person myPerson;
}
[DataContract(Namespace = "http://example.org/person")]
public class Person
{
#region Fields
private string _first;
private string _last;
[DataMember(IsRequired = false, Order = 2)]
private string id;
#endregion
#region Property
[DataMember(Name = "first", Order = 0)]
public string First
{
get { return _first; }
set { _first = value; }
}
[DataMember(Name = "last", Order = 1)]
public string Last
{
get { return _last; }
set { _last = value; }
}
#endregion
}
[ServiceContract]
public interface IEchoService
{
[OperationContract]
void EchoEmploye(EchoPersonMessageRequest msgRequest);
}

Un rapide coup d'œil vous fait noter les différents paramètres pris en compte par l'attribut MessageHeader :
Le paramètre MustUnderstand, hérité de la technologie SOAP permet de spécifier si l'entête devra être obligatoirement analysé (comprise) par le destinataire ou l'entité renseigné par le paramètre Actor.
Le plus intéressant des paramètres est ProtectionLevel. Il permet de spécifier si l'entête du message devra être signée, chiffrée ou les deux. C'est également un paramètre de l'attribut MessageBodyMember. Ainsi les contrats de message permettent à l'inverse des contrats de données de mettre en place des options de sécurité.
Par contre, l'utilisation de l'attribut KnowType est réservée au contrat de données. Si vous devez y recourir, vous utiliserez dés lors un DataContract.
Les tableaux
Il existe deux façons pour déclarer des tableaux au sein d'un contrat de message avec la technologie WCF :
Ne rien faire. Utiliser les attributs MessageHeader et MessageBodyMember sur des propriétés de type Array. Le résultat de la sérialisation sera la création d'un élément ayant plusieurs fils chacun rattaché à un élément du tableau.
Utiliser l'attribut MessageHeaderArray qui hérite de l'attribut MessageHeader. Le résultat de la sérialisation est la création d'un élément pour chaque élément du tableau sérialisé.
Attention, l'utilisation de l'attribut MessageHeaderArray ne fonctionne que pour l'entête du message et les tableaux, il ne fonctionne pas avec les collections.
Recommandations
L'utilisation de contrat de message vous permet de ne pas à avoir à spécifier les paramètres d'une opération d'un service un à un. Au lieu de faire face à cette signature void EchoPerson(string firstName, string LastName ), vous utiliserez celle-ci void EchoPerson(EchoPersonMessageRequest).
Vous pourrez au mieux contrôler toute modification des paramètres pris en compte par votre opération.
De plus lors de la génération de vos proxys par l'utilitaire svcutil, en vue de garantir si vous le souhaitez une compatibilité avec la technologie WebService, créera en interne des contrats de message si vous n'en avez pas fait le choix.
public Customer GetCustomer(int ID, out string ResponseMessage)
{
GetCustomerRequest inValue = new GetCustomerRequest();
inValue.ID = ID;
GetCustomerResponse retVal = ((ICMSService)(this)).GetCustomer(inValue);
ResponseMessage = retVal.ResponseMessage;
return retVal.Customer;
}
Ce qu'il ne fera pas si bien entendu vous avez choisi de créer des contrats de message.
GetCustomerResponse ICMSService.GetCustomer(GetCustomerRequest request)
{
return base.InnerProxy.GetCustomer(request);
}
Performance
L'entête du message et chaque corps de message sont sérialisés indépendamment les uns des autres. Le même espace de nommage est donc répété pour l'entête et chaque partie du corps du message le référençant.
[MessageContract]
public class BankingTransaction
{
[MessageHeader]
public Operation operation;
[MessageBodyMember]
public Account sourceAccount;
[MessageBodyMember]
public Account targetAccount;
[MessageBodyMember]
public int amount;
}
Pour améliorer les performances, surtout pour ce qui est de la taille des messages qui seront échangés, il est bon de fusionner l'ensemble des éléments dans une unique partie du corps du message. Pour se faire, il convient de créer une classe regroupant l'ensemble des parties du corps de message et de la définir comme un contrat de données. Les contrats de message utilisent donc les contrats de données.
[MessageContract]
public class BankingTransaction
{
[MessageHeader]
public Operation operation;
[MessageBodyMember]
public OperationDetails details;
}
[DataContract]
public class OperationDetails
{
[DataMember]
public Account sourceAccount;
[DataMember]
public Account targetAccount;
[DataMember]
public int amount;
}
Conclusion
Que cela soit les contrats de données ou bien les contrats de message, la technologie WCF offre des outils de programmation simplifiés. Les contrats de données vous permettent en quelques attributs de publier rapidement vos données métiers, quant aux contrats de messages ils permettent sans un investissement particulier de manipuler aisément les messages échangés avec vos applications avec un accès aux options de sécurité grandement facilité.
Vous pouvez télécharger à cette adresse cet article au format PDF.
Vous trouverz à cette autre adresse le code source associé à cet article.