Intégration
Il est parfois nécessaire d’intégrer un composant Silverlight dans une application WPF, étant donné que certains contrôles Silverlight n’existent pas en WPF. La méthode la plus courante consiste à placer un WebBrowser dans l’application WPF, et d’utiliser la méthode Navigate en indiquant l’URL d’une page contenant le composant Silverlight à intégrer. Ainsi, dans le XAML côté WPF :
<WebBrowser Name="wb" />
Dans le constructeur correspondant côté .xaml.cs :
public MainWindow()
{
InitializeComponent();
wb.Navigate("http://aaa.bbb.com/toto.aspx");
}
Le résultat est visuellement excellent et remarquablement fluide. En plus de l’unification des développements (Xaml + C#), on dispose donc d’un moyen très simple et efficace pour tirer profit de développements réalisés en Silverlight a priori incompatibles avec WPF sans recompilation du code source.
Une fois l’intégration réalisée, le problème qui se pose immédiatement est celui de la communication entre le composant Silverlight et le reste de l’application WPF. Certains articles expliquent comment réaliser cette communication en passant par le document HTML, seul point de liaison disponible depuis l’application Silverlight et le WebBrowser WPF. Néanmoins, le moins que l’on puisse dire est que cette méthode n’est pas maintenable et qu’elle réduit l’évolutivité de l’architecture. Un véritable bricolage.
Une méthode de communication élégante
Pour faire communiquer l’application WPF avec le composant Silverlight, le mieux est encore d’utiliser la technologie prévue à cet effet : WCF. Plus précisément, il s’agit de concevoir un service WCF hébergé sur le serveur Web de l’application hébergeant le composant Silverlight. L’application WPF doit pouvoir envoyer des informations au composant Silverlight (et inversement), en passant systématiquement par le service WCF. Par conséquent, l’application WPF et l’application Silverlight doivent pouvoir :
- Envoyer des messages au service WCF
- Recevoir des messages provenant du service WCF.
Le service WCF agit comme un relai et doit pouvoir :
- Recevoir des messages de l’application WPF et les relayer à l’application Silverlight
- Recevoir des messages de l’application Silverlight et les relayer à l’application WPF.
Le service WCF a donc besoin de pouvoir recevoir ET envoyer des messages : un CallbackContract va être utilisé. D’autre part, les communications bidirectionnelles à permettre correspondent à un binding pollingDuplexHttpBinding avec Silverlight et à un binding wsDualHttpBinding avec WPF. Pour pouvoir appliquer ces 2 binding au service WCF, il faut les endpoints correspondants :
<services>
<service name="WpfSilverlightCommunication.CommunicationService">
<endpoint
address="silverlight"
binding="pollingDuplexHttpBinding"
bindingConfiguration="multipleMessagesPerPollPollingDuplexHttpBinding"
contract="WpfSilverlightCommunication.ICommunicationService">
</endpoint>
<endpoint
address="wpf"
binding="wsDualHttpBinding"
contract="WpfSilverlightCommunication.ICommunicationService">
</endpoint>
<endpoint
address="mex"
binding="mexHttpBinding"
contract="IMetadataExchange"/>
</service>
</services>
Le reste de la configuration WCF (system.serviceModel) peut être configuré de façon classique :
<extensions>
<bindingExtensions>
<add name="pollingDuplexHttpBinding"
type="System.ServiceModel.Configuration.PollingDuplexHttpBindingCollectionElement,System.ServiceModel.PollingDuplex, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</bindingExtensions>
</extensions>
<bindings>
<wsDualHttpBinding>
<binding sendTimeout="00:00:05" />
</wsDualHttpBinding>
<pollingDuplexHttpBinding>
<binding name="multipleMessagesPerPollPollingDuplexHttpBinding"
duplexMode="SingleMessagePerPoll" sendTimeout="00:00:05"/>
</pollingDuplexHttpBinding>
</bindings>
Ainsi, l’application Silverlight pourra accéder au service WCF par l’adresse <adresseService>.svc/silverlight et l’application WPF par l’adresse <adresseService>.svc/wpf. Le service WCF fonctionne de la façon suivante :
- Le mode d’instance est InstanceContextMode.Single, parce que l’application WPF et l’application Silverlight ne se connectent pas avec le même identifiant de session. Par conséquent, avec la configuration par défaut (PerSession), faire communiquer les 2 instances du service serait plus compliqué.
- Le service contient une méthode Ping qui enregistre en premier lieu les canaux de communication vers l’application WPF et vers l’application Silverlight.
- Chaque couple WPF/Silverlight est identifié par un Guid que partagent les 2 applications.
Le code du contrat de service WCF est le suivant :
[ServiceContract(CallbackContract=typeof(ICommunicationServiceHandler))]
public interface ICommunicationService
{
[OperationContract]
bool Ping(string id);
[OperationContract(IsOneWay=true)]
void Order(string appId, List<string> values);
}
[ServiceContract]
public interface ICommunicationServiceHandler
{
[OperationContract(IsOneWay = true)]
void Receive(List<string> values);
}
Et le service :
[ServiceBehavior(InstanceContextMode=InstanceContextMode.Single, ConcurrencyMode=ConcurrencyMode.Multiple)]
public class CommunicationService : ICommunicationService
{
Dictionary<string, ICommunicationServiceHandler> silverlightAppChannels = new Dictionary<string, ICommunicationServiceHandler>();
Dictionary<string, ICommunicationServiceHandler> wpfAppChannels = new Dictionary<string, ICommunicationServiceHandler>();
public void Order(string appId, List<string> values)
{
string endpointExtension = OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri.Segments.Last();
if (silverlightAppChannels[appId] != null && wpfAppChannels[appId] != null)
{
switch (endpointExtension)
{
case "silverlight":
wpfAppChannels[appId].Receive(parcelleIds);
break;
case "wpf":
silverlightAppChannels[appId].Receive(parcelleIds);
break;
default:
break;
}
}
Ping(appId);
}
public bool Ping(string id)
{
string endpointExtension = OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri.Segments.Last();
switch (endpointExtension)
{
case "silverlight":
silverlightAppChannels[id] = OperationContext.Current.GetCallbackChannel<ICommunicationServiceHandler>();
break;
case "wpf":
wpfAppChannels[id] = OperationContext.Current.GetCallbackChannel<ICommunicationServiceHandler>();
break;
default:
break;
}
return true;
}
}
La méthode principale, Order, effectue le relai en appelant la méthode Receive de l’autre canal de communication précédemment enregistré par la méthode Ping.
Pour faire en sorte que l’application WPF et l’application Silverlight partagent le même Guid, l’application WPF peut le créer et le transmettre en paramètre d’URL :
private static Guid appId = Guid.NewGuid();
public MainWindow()
{
InitializeComponent();
wb.Navigate("<adressePage>?guid=" + appId.ToString());
}
Pour le reste, l’application WPF agit de façon tout à fait classique comme client de l’endpoint avec le binding wsDualHttpBinding : après ajout de la référence de service, la fenêtre WPF peut implémenter le contrat de callback WCF et contenir une instance du proxy. Il faut simplement penser à appeler une fois la méthode Ping en passant le Guid au chargement de la fenêtre.
De même, l’application Silverlight agit comme un client tout à fait classique de l’endpoint ayant le binding PollingDuplexHttpBinding. Pour récupérer le Guid initialisé par l’application WPF, l’application Silverlight peut procéder de la manière suivante, par exemple pour l’appel initial à Ping :
proxy.PingAsync(HtmlPage.Document.QueryString["guid"]);Conclusion
Une méthode originale et élégante a été conçue pour faire communiquer une application WPF avec une application Silverlight qui serait rendue dans un WebBrowser. On pourrait facilement étendre cette méthode selon plusieurs axes :
- Réalisation d’autres types de relais, de type hub (broadcast) ou routeur par exemple.
- Communication entre une application Winforms et un composant Silverlight avec le composant WebBrowser de WinForms.
- Synchronisation des 2 applications qui pourraient s’échanger périodiquement leurs données.
- Introduction d’une queue de messages et maintien des messages en attente dans le service WCF, en cas de perte de connexion.