Faire communiquer un AppService #UWP avec une application #WPF : c’est possible !
Dans un précédent article je vous parlais d’AppService, une excellent moyen de faire communiquer deux applications UWP les unes avec les autres. Voyons maintenant comment faire communiquer une application WPF avec un AppService !
Définition de l’AppService
Il suffit de suivre la procédure décrite dans mon précédent article.
Vous pouvez le faire avec la première ou la dernière version du SDK UWP sans problème.
Dans le cadre de cet exemple, j’ai défini une calculatrice simple en guise de service.
Pour rappel, les échanges sont faits au travers de ValueSet qui sont des dictionnaires empêchant les types non-sérialisables d’être insérés.
private async Task ManageRequestAsync(AppServiceRequestReceivedEventArgs args) { var result = new ValueSet(); try { var part1 = int.Parse(args.Request.Message["part1"].ToString()); var part2 = int.Parse(args.Request.Message["part2"].ToString()); var operand = (string)args.Request.Message["operand"]; switch (operand) { case "add": result["result"] = part1 + part2; result["success"] = true; break; case "substract": result["result"] = part1 - part2; result["success"] = true; break; default: result["result"] = "unknow"; result["success"] = false; break; } } catch (Exception e) { result["result"] = e.Message; result["success"] = false; } await args.Request.SendResponseAsync(result); _deferal.Complete(); }
Mise en place de l’application WPF – à la main
Il est possible depuis longtemps de faire appel à une partie des APIs WinRT depuis une application bureau sous contrainte d’être au minimum sur la plateforme des APIs ciblées (Windows 8, 8.1 ou 10).
Cette première condition est respectée en indiquant la plateforme ciblée par l’application en éditant à la main le fichier csproj. Il faut, comme indiqué sur cette page MSDN, ajouter cette ligne (dans notre cas on cible la première version du SDK Windows 10, le 10.0.10240.0) :
<PropertyGroup> <TargetPlatformVersion>10.0.10240.0</TargetPlatformVersion> </PropertyGroup>
Ensuite, il va être possible de faire référence aux APIs WinRT depuis la fenêtre d’ajout de références mais il ne faut pas choisir cette voie car elle ne fonctionne pas.
Nous allons au contraire aller chercher à la main les fichiers WinMD de descriptions des APIs du SDK. Dans notre cas, nous ajoutons les deux suivantes présentes dans le dossier c:\Program Files (x86)\Windows Kits\10\References\ :
- Windows.Foundation.UniversalApiContract\1.0.0.0\Windows.Foundation.UniversalApiContract.winmd
- Windows.Foundation.FoundationContract\1.0.0.0\Windows.Foundation.FoundationContract.winmd
Une fois cela en place, on peut faire référence aux différentes APIs dans notre code WPF mais le compilateur va alors être colérique au sujet des opérations asynchrones :
Error CS4036 'IAsyncOperation<AppServiceConnectionStatus>' does not contain a definition for 'GetAwaiter' and no extension method 'GetAwaiter' accepting a first argument of type 'IAsyncOperation<AppServiceConnectionStatus>' could be found (are you missing a using directive for 'System'?)
Après quelques minutes de négociations musclées (et de recherche sur l’internet mondial), on découvre qu’il faut ajouter une référence supplémentaire à la DLL System.Runtime.WindowsRuntime.dll présente dans le dossier c:\Program Files (x86)\Reference Assemblies\Microsoft\Framework\.NETCore\v4.5\
Nous avons alors ces références pour le projet WPF :
Tout compile et nous avons maintenant ce code dans l’application WPF :
var targetPackageId = "bb22f70c-a443-4e1a-8dbb-b6825209c8f9_w7mxzscebfmq4"; var serviceName = "com.infinitesquare.appservice"; var service = new AppServiceConnection(); service.PackageFamilyName = targetPackageId; service.AppServiceName = serviceName; var status = await service.OpenAsync(); if (status != AppServiceConnectionStatus.Success) { ResultListView.Items.Add("Connexion impossible " + status); return; } ResultListView.Items.Add("Connecté"); var message = new ValueSet(); message["operand"] = "add"; message["part1"] = "21"; message["part2"] = "21"; var result = await service.SendMessageAsync(message); ResultListView.Items.Add("Envoyé : 21 + 21"); ResultListView.Items.Add("Status résultat : " + result.Status); ResultListView.Items.Add("Résultat : " + result.Message["result"]);
Il ne reste alors plus qu’à faire F5 pour lancer le debug depuis Visual Studio pour constater que… le service semble toujours innccessible :-(
Et c’est alors que dans un acte de désespoir, le développeur essaye (après avoir redémarrer son PC – dans le doute, reboot) de lancer l’exécutable lui-même à la main par un double-clic dessus : et miracle de Noël, cela fonctionne !
Il est donc bien possible d’utiliser AppService depuis une application WPF même si à priori le debuggage ne va pas être simple !
ERRATUM
En fait le lancement depuis Visual Studio fonctionne mais il ne faut pas le lancer en tant qu’administrateur. Merci Christophe pour le coup de main.
Cela s’applique aussi à l’application WPF : lancée en tant qu’administrateur, elle ne pourra pas avoir accès à l’AppService. Je pense qu’il faut tout simplement que l’application soit lancée sous le compte de l’utilisateur courant.
Pour aller plus vite et de la lecture
Pour aller plus vite, il est aussi possible d’utiliser le package Nuget UWPDesktop de Lucian afin de ne pas avoir à référencer les DLL UWP à la main.
Pour ceux souhaitant aller un peu plus loin, voici des lectures intéressantes :
- How to call WinRT APIs in Windows 8 from C# Desktop Applications - Scott Hanselman
- UWP for Desktop - Lucian Wischik
Commentaires