Un de mes amis m’a conseiller de jeter un oeil à la sérialisation Proto Buffer. Elle est rapide et légère. Idéale donc pour la consommer par webservice. Il existe plusieurs implémentation pour c#, je me suis intéressé à protobuf-net. Pour une comparaison des performances par rapport à du BinaryFormatter du Soap ou du Json, vous pouvez vous rendre sur cette page.
Utilisation avec WCF
Dans mes premiers tests, je voulais remplacer le DataContractSerializer de mes ServiceContract par le serializer protobuf-net, malheureusement le support de WCF de silverlight est assez léger et il n’est actuellement pas possible de le faire. Dans ses exemples protobuf-net contient un serveur et client RPC. J’ai donc configuré WCF avec une adaptation de ce serveur RPC et remplacé le client généré par le wizard de visual studio par le client protobuf-net.
Côté serveur
Pour avoir un serveur RPC compatible avec l’exemple fourni par protobuf-net, il faut rajouter à WCF un OperationSelector routant les urls entrantes vers le bon service contract. J’ai réalisé un EndPointBehavior paramettrant tout ça. La configuration ressemble à ceci:
<system.serviceModel>
<extensions>
<behaviorExtensions>
<add name="protoRPC" type="ProtoBuf.RPC.ProtoRpcEndpointBehaviorSection, protobuf-wcf, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null"/>
</behaviorExtensions>
</extensions>
<behaviors>
<endpointBehaviors>
<behavior name="rpcBehavior">
<protoRPC/>
</behavior>
</endpointBehaviors>
</behaviors>
<services>
<service name="SilverlightApplication4.Web.TestService">
<endpoint address="" binding="webHttpBinding" contract="TestServices.ITestService" behaviorConfiguration="rpcBehavior"/>
</service>
</services>
</system.serviceModel>
La classe implémentant le ServiceContract doit également autoriser toutes les urls:
[ServiceBehavior(AddressFilterMode = AddressFilterMode.Any)]
public class TestService : ITestService
{
#region ITestService Members
public TestServices.Customer DoWork(string a, TestServices.Customer c)
{
return TestServices.Customer.Invent();
}
#endregion
}
J’ai choisi d’externaliser le ServiceContract dans une interface pour pouvoir générer simplement un client dans la partie Silverlight. Voici à quoi il ressemble:
[ServiceContract]
public interface ITestService
{
[OperationContract(Action = "dowork")]
Customer DoWork(string a, Customer c);
}
Côté Silverlight
Pour fonctionner avec Silverlight, il a fallu que je corrige un bug dans les sources de protobuf-net. Les classes temporaires de sérialization générées par protobuf-net sont privées et Silverlight ne permet pas de faire de réflection sur celles-ci. Il faut les mettres en internal.
DataContracts
Dans l’implémentation de mon exemple, j’ai isolé mes datacontracts dans une assembly séparée. J’ai créé la même assembly pour Silverlight en référençant simplement les fichiers par liens, ce qui me permet de n’avoir qu’une seule version de ces fichiers. Pour ceux qui ne saurait pas comment faire et pour éviter de perdre 2 heures à chercher, il suffit lorsque vous ajouter un élément à la solution de cliquer sur la petite flêche à côté du boutton Add et de choisie Add as link.
Génération du client
Nous voici donc avec la même assembly que côté serveur, mais compilée au format Silverlight. Cette assembly contenant notre interface de ServiceContract, nous allons rajouter dans la version Silverlight la génération du client. J’ai fais cette génération par l’utilisation des fichiers t4. Il suffit de copier coller le fichier .tt qui se trouve dans la solution et de le nommer du nom du fichier qui contient l’interface de ServiceContract pour que la génération s’effectue (ici ITestService.tt). La génération utilise le FileCodeModel pour déterminer les Méthodes de l’interface à implémenter dans le client. Le client généré contient des fonctions d’appels asynchrones et les événements completed qui correspondent.
Utilisation
L’utilisation de notre client reste aussi simple que l’utilisation d’un client WCF silverlight standard.
private void Button_Click(object sender, RoutedEventArgs e)
{
ITestServiceClient client = new ITestServiceClient(new Uri("../", UriKind.Relative));
client.DoWorkCompleted += new EventHandler<ITestServiceClient.DoWorkResultEventArgs>(client_DoWorkCompleted);
client.DoWorkAsync("aazer", Customer.Invent());
}
void client_DoWorkCompleted(object sender, ITestServiceClient.DoWorkResultEventArgs e)
{
var r = e.Result;
}
Il y a plusieurs façons d’instancier le client. Tout dépend d’où vos webservices se trouvent.
Le constructeur par défaut va chercher un webservice se nommant du même nom que l’interface de service contract sans le I se trouvant de le même dossier que votre .xap. Sur une surcharge du constructeur, vous pouvez spécifier soit une Url donnant le dossier dans lequel va se trouver le webservice ou alors un url d’appel direct vers le fichier svc.
Les sources