L'objectif de cet article en deux parties est de montrer la manière de passer d'une application utilisant le Remoting à une application utilisant Windows Communication Foundation (ex indigo). Dans une première partie je décrirai donc l'architecture de l’application développée et les raisons qui ont déterminé ses choix d'implémentation. Dans la seconde partie de l'article, je décrirai la nouvelle application à la sauce WCF.
Enjoy !
Les différents projets sont réalisés en Visual C#.NET, Framework 2.0.50727 et WinFX 3.0 Beta 2.
Une des problématiques principale d'une application utilisant des composants distants via le Remoting est que le client a besoin de la définition des méthodes appelables sur un service distant (les langages .NET sont à typage fort). On pourrait finalement penser que ce n'est guère différent des applications COM+ d'antan que l'on déployait via le proxy généré par l'application COM+ (et qui déployait aussi la DLL ActiveX).
Pourtant ce n'est pas le cas. En effet, dans le monde .NET la manière d'instancier les composants distants est beaucoup plus riche qu'il n'y parait. En effet, on peut à loisir utiliser le mot clé « New », « Activator.CreateInstance() », « Activator.GetObject() ». La dernière manière de faire a pour avantage de ne déployer que le contrat du composant que l'on souhaite accéder. Comme vous vous en doutez, un contrat sera représenté par une Interface. C'est pourquoi l'application exemple ne référencera que les interfaces et pas directement les composants métiers.
Voici donc les quelques petites contraintes ajoutées dans cette appliquette :
- Les différentes couches ne doivent échanger aucun objet technique, seulement des collections génériques typées
- On ne déploie du côté client que les interfaces des composants accessibles en remoting
- On doit facilement pouvoir switcher d'un appel CAO (Client Activated Object) à un appel SAO (Server Activated Object ou WellKnown)
- On souhaite un paramétrage via un fichier de configuration
L'objectif fonctionnel est de récupérer la classique liste des employés de la base « Northwind » installée en standard avec SQL Server 2000.
Pour réaliser cet objectif et en tenant compte des contraintes précédentes, la solution .NET 2.0 est composée des projets suivants :
- Data : couche simplissime chargée de l'accès aux données et de la constitution des collections génériques
- Entities : permet de définir la structure des données échangées (collections et types élémentaires)
- BusinessContracts : interfaces décrivant les contrats à implémenter par la couche métier
- Business : la couche métier proprement dite implémentant les contrats décrits dans la couche BusinessContracts
- Client : modeste application Windows Forms permettant d'afficher un bête jeux de données dans une grille récupérer via un composant accessible en Remoting
- Service : application Web chargée de hoster les instances clientes
IIS hostera les composants et nous utiliserons un formatter binaire. Voici le schéma de référence des différents projets :
- BusinessContracts
- Business
- Entities
- BusinessContracts
- Data
- Client
- Entities
- BusinessContracts
- Service
- Business
- BusinessContracts
- Data
- Client
- Entities
- BusinessContracts
Voici le schéma de principe de l'application :
Le plus souple lorsque l'on souhaite travailler en Remoting est de passer par des fichiers de configuration. Deux fichiers de configuration seront nécessaires : l'un du côté du client, l'autre du côté du serveur qui va hoster les instances utilisateur. Examinons chacun d'entre-eux.
Voici pour la partie cliente :
<system.runtime.remoting>
<application>
<channels>
<channel ref="http" useDefaultCredentials="true" port="0">
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
</channel>
</channels>
<client>
<!-- SAO Factory for CAO objects -->
<wellknown
type="BusinessContracts.IFactory, BusinessContracts" url="http://localhost/Service/BusinessContracts.IFactory.rem" />
</client>
<!-- (1) SAO types configuration -->
<client>
<wellknown
type="BusinessContracts.IEmployee, BusinessContracts" url="http://localhost/Service/BusinessContracts.IEmployee.rem" />
</client>
<!-- (2) CAO types configuration
<client url="http://tazdevil/Service" >
<activated type="BusinessContracts.IEmployee, BusinessContracts" />
</client>
-->
</application>
</system.runtime.remoting>
Vous noterez plusieurs points :
1. le paramétrage du formatter à « Binary » dans la partie « clientProviders »
2. Pour la partie SAO : deux interfaces sont configurées. L'une pour l'accès au composant permettant de récupérer la liste des employés, l'autre permettant d'accéder à la fabrique de classe de type CAO. Je reviendrai sur ce point par la suite.
3. Si l'on regarde le paramétrage suivant : <wellknown type="BusinessContracts.IEmployee, BusinessContracts" url="http://localhost/Service/BusinessContracts.IEmployee.rem" />, vous noterez qu'a chaque fois que l'on veut récupérer une instance d'une classe implémentant l'interface BusinessContracts.IEmployee contenue dans l'assembly BusinessContracts.dll, il faut aller sur l'url "http://localhost/Service/" et plus précisément sur l'uri suivante BusinessContracts.IEmployee.rem correspondant au composant
4. Pour la partie CAO (commentée), l'objet implémentant l'interface BusinessContracts.IEmployee contenue dans l'assembly BusinessContracts.dll, à instancier le sera via l'url http://tazdevil/Service
Par conséquent dans ce petit exemple, pour passer du mode SAO au mode CAO, il suffit de décommenter la partie "(2)" et commenter la partie "(1)"
Voici donc pour la partie serveur :
<system.runtime.remoting>
<application>
<service>
<wellknown mode="SingleCall" objectUri="BusinessContracts.IFactory.rem" type="Business.FactoryCAO, Business"/>
<wellknown mode="SingleCall" objectUri="BusinessContracts.IEmployee.rem" type="Business.EmployeeService, Business"/>
<activated type="Business.EmployeeService, Business"/>
</service>
<channels>
<channel ref="http"/>
</channels>
</application>
</system.runtime.remoting>
Vous noterez les points suivants :
1. la partie serveur est paramétrée de manière à gérer les demandes SAO sur l'URI BusinessContracts.IEmployee.rem et d'instancier Business.FactoryCAO de l'assembly Business.dll
2. la partie serveur est paramétrée de manière à gérer les demandes SAO sur l'URI BusinessContracts.IFactory.rem et d'instancier Business.EmployeeService de l'assembly Business.dll
3. En vous noterez que le type « Business.EmployeeService » peut-être aussi accessible en CAO via le tag « activated »
Du fait du switch possible entre SAO et CAO, la création des instances n'est pas uniforme et doit être centralisée. En effet, comment gérer le switch SAO/CAO alors que « Activator.GetObject » ne fonctionne que pour les composants SAO. L'idée derrière tout ça est la mise en place d'une fabrique de classe chargée de tous ces aspects en fonction du paramétrage du fichier de configuration. De plus, nous verrons que l'utilisation des méthodes génériques peut s'avérer utile. Enfin dernier point l'utilisation de cette fabrique doit être la plus simple possible pour le développeur :
BusinessContracts.IEmployee service = Factory.GetInstance().GetObject<BusinessContracts.IEmployee>();
Pour arriver à cela, voici les éléments intéressants mis en oeuvre dans cette fabrique de classe appelée « Factory » dans le projet :
public T GetObject<T>()
{
if ( ! _types.ContainsKey(typeof(T)) )
throw new RemotingException("Unknown type!");
TypeEntry entr = _types[typeof(T)];
// Get SAO configured types
WellKnownClientTypeEntry wct = entr as WellKnownClientTypeEntry;
// Get CAO configured types
ActivatedClientTypeEntry act = entr as ActivatedClientTypeEntry;
// Check firstly for SAO types
if (wct != null)
return (T)Activator.GetObject(wct.ObjectType, wct.ObjectUrl);
else
{
// Get the SAO factory for CAO types
WellKnownClientTypeEntry entrCAOFactory =
(WellKnownClientTypeEntry)_types[typeof(BusinessContracts.IFactory)];
BusinessContracts.IFactory fact =
(BusinessContracts.IFactory)Activator.GetObject(entrCAOFactory.ObjectType, entrCAOFactory.ObjectUrl);
return fact.GetObject<T>();
}
}
Quelques éléments important sur cette fabrique de classe :
· Il s'agit d'un singleton dont l'instance est créée sur le constructeur statique
· Elle permet aussi de charger un cache (dictionnaire) des paramètres de configuration du fichier de configuration (membre _types). Ce cache contiendra indifféremment le paramétrage SAO (WellKnownClientTypeEntry) comme le paramétrage CAO (ActivatedClientTypeEntry). Par paramétrage, j'entends notamment le type et l'url.
· La méthode Getobject est générique et est chargée de la création de l'instance. Elle commence par vérifier si l'on est en mode SAO. Si c'est le cas, « Activator.GetObject() » suffit à créer l'instance distante et à générer le proxy. Sinon, on passe une fabrique de classe SAO pour les instances CAO
Comme vous pouvez le constater dans le code précédent, une fabrique d'instance CAO est réalisée à partir d'une classe SAO. Et c'est là que le bas blesse dans la solution mise en oeuvre. Le fait de ne posséder que les interfaces est très contraignant pour le mode CAO. L'implémentation de cette classe représente le talon d'Achille de ma solution puisque la fabrique d'instance CAO chaîne en dur les demandes de création d'instance des clients (classe « FactoryCAO » du projet Business).
Maintenant, il est vrai que conceptuellement parlant, peu de projets ont besoin de basculer leur couche métier de SAO vers CAO et inversement. Enfin, du point de vue de la montée en charge d'un serveur d'application, le mode SAO est préférable. Mais c'est une autre histoire !
Toute bonne architecture qui se respecte doit assurer la non adhérence des différentes couches avec des objets techniques. Pour assurer cela, la couche métier de ce petit exemple référence une assembly appelée entities. Cette dernière permet de regrouper les collections qui seront échangées.

Vous remarquerez sur le diagramme la présence de la classe générique « EntityCollection<T> ». Elle permettra de représenter nos collections fortement typées puisqu'elle hérite de « IList<T>. On pourrait à juste titre penser que cette dernière est inutile. Pourtant elle s'avèrera utile pour deux raisons :
· Il peut s'avérer très utile que ces collections implémentent l'interface IBindingList lors du databinding puisque c'est grâce à son événement « ListChanged » que les grilles pourront se rafraichir notamment.
· Lorsque vous choisissez le formatter SOAP pour la sérialisation d'une liste générique, cela ne fonctionne pas. En effet, il est dit dans la documentation à ce sujet : « The .NET Remoting technology supports both binary and SOAP serialization - that is, Remoting messages may be represented in a Microsoft-specific binary format or as SOAP XML. However, generic types (which are a new feature in .NET Framework 2.0) may only be used with binary serialization. The usage of generics with SOAP serialization is not supported, either through .NET Remoting or by using the SoapFormatter class directly ». En voilà une nouvelle, ça sent la classe dépréciée ça ! Il en va de même avec les types nullables. En effet, Microsoft indique que ce formatter n'a pas évolué avec.
En bref, si vous pensiez utiliser le SOAP FOrmatter pour accroitre l'interopérabilité de vos composants, préférez l'utilisation de Web Service "Basic Profile" en lieu et place du remoting HTTP + SoapFormatter qui reste une solution très propriétaire.
Il reste maintenant à réaliser la migration de ce petit exemple sur Windows Communication Foundation. Cette partie fera l'objet de l'article suivant : « Migration d'un projet Remoting Framework 2.0 vers Windows Communication Foundation 2/2 : la migration ».