Dot.Blog

C#, XAML, WinUI, WPF, Android, MAUI, IoT, IA, ChatGPT, Prompt Engineering

De Silverlight/WPF à WinRT : .NET pour Metro Style (partie 3)

[new:30/08/2012]Je vous ai déjà présenté différents éléments de WinRT, notamment ses différences avec les frameworks Silverlight et WPF. Soyons plus précis. Avec C#/Xaml il est possible de créer des applications Metro Style, ces applications fonctionnent sous WinRT, mais utilisent avant tout une version spéciale de .NET, un peu comme Silverlight. Et il y a beaucoup à dire sur les différences entre SL et .NET pour Metro Style.

.NET pour Metro Style

Avant tout, utiliser C# avec Xaml pour créer des applications Windows 8 c’est utiliser une version spéciale et allégée de .NET, exactement comme le runtime Silverlight est lui aussi une version allégée du framework .NET complet. Seul WPF permet en réalité d’utiliser le framework complet (si on reste dans les applications Xaml).

Dans les comparaisons que j’ai déjà publiées, on remarque que le framework .NET allégé de Silverlight est presque totalement contenu dans celui disponible sous WinRT.

Mais de nombreuses et parfois subtiles différences existent.

Essayons de faire le tour des principales.

.NET APIs pour Metro Style

C’est le nom complet de ce framework .NET spécialement conçu pour concevoir des applications Metro Style en C# ou en VB.

Allégé ?

Oui pour au moins une bonne raison : tout ce qui ne permet pas directement de concevoir des applications Metro Style a été supprimé.

La règle est simple, prendre les décisions classe par classe a forcément du être moins simple. C’est pourquoi certains de ces choix nous apparaitrons évidents et logiques et d’autres plus difficiles à comprendre...

.NET APIs + WinRT

Lorsqu’on travaille en C#/Xaml on utilise ainsi les APIs .NET pour Metro Style, APIs simplifiées et écrémées de tout ce qui ne sert pas à faire du Metro Style. Mais tout cela repose malgré tout sur Windows Runtime (WinRT), et les types de WinRT sont aussi utilisables, en plus de ceux du framework .NET spécifique Metro Style...

Cela est très important à comprendre. Les APIs .NET pour Metro Style tournent au-dessus de WinRT qui lui-même se présente d’une façon semblable à .NET même s’il a été conçu différemment et toutes les APIs WinRT sont utilisables depuis C# comme on le faisait avec les APIs .NET. Mais cela est juste une impression, WinRT n’est pas un .NET modifié, c’est autre chose, conçu autrement, mais se présentant sous une forme semblable.

Ainsi, le développeur utilisant C#/Xaml pour développer sous Windows 8 Metro Style (le bureau classique à part donc) dispose en réalité d’un ensemble d’APIs séparées en deux groupes principaux : d’une part le framework .NET spécifique Metro Style, et d’autre part toutes les APIs WinRT. C’est donc un ensemble très vaste offrant l’accès à toutes les APIs utiles et nécessaires qui s’offrent au développeur C#/Xaml.

Ce n’est pas rien !

Un .NET allégé

Comme expliqué plus haut ce framework spécial pour Metro Style a été conçu avec un objectif clair : se concentrer sur tout ce qui est utile pour développer des applications Metro Style et rien que cela. Il en découle que certains types sont logiquement absent de ce framework:

  • Les types et les membres qui ne peuvent pas servir à créer des applications Metro Style comme la Console ou les types ASP.NET.
  • Les types obsolètes conservés uniquement pour la compatibilité avec les versions antérieures de .NET.
  • Les types qui doublent des types existant dans les APIs WinRT.
  • Les types et les membres qui encapsulent les fonctions de l’OS comme System.Diagnostic.EventLog ou les compteurs de performance.
  • Les membres considérés comme introduisant une certaine confusion (comme les méthodes “Closes” sur les types gérant des entrées/sorties).

Comme je le disais un tel découpage à la hache, même fait avec grande intelligence, possède sa part d’ombre. ll sera certainement aisé de comprendre certains choix, et plus difficile d’en accepter d’autres. Mais tout comme le framework limité de Silverlight, il faudra faire avec.

Toutefois, si le développeur Silverlight se trouvait devant un mur face à certaines omissions (comme les méthodes de coercition de données sur les propriétés de dépendance), le développeur Metro Style aura souvent la possibilité de se “rabattre” sur les types de l’API WinRT qui est pléthorique (plusieurs milliers de types) alors que sous Silverlight un tel échappatoire n’existe pas...

Toute l’astuce de l’apprentissage consistera donc à bien connaître les limites du framework .NET pour Metro Style tout autant que les APIs “natives” de Windows 8 (WinRT).

Des substituions à connaître

Si j’ai déjà évoqué les changements de namespaces dans de précédents billets, j’évoque ici des substitutions plus subtiles, du type de celles qui réclament justement de bien connaître les APIs WinRT.

Par exemple le développeur sera très étonné de ne plus trouver dans le framework .NET les APIs concernant l’Isolated storage.

Comment s’en passer ? Pourquoi ont-ils supprimé ces types essentiels ?

On prend du recul, et on relit les règles... sont omis notamment “les types qui doublent des types existant dans les APIs WinRT”...

Bien entendu WinRT fourni un service totalement équivalent à tous les langages de la plateforme. Il aurait été idiot de laisser des choses spécifiques dans le .NET pour Metro Style alors même que WinRT fournit les mêmes services de façon globale à toute la plateforme;

C’est ainsi que System.IO.IsolatedStorage.IsolatedStorageSettings est par exemple omis de .NET pour Metro Style car on peut accéder tout aussi facilement à Windows.Storage.ApplicationDataContainer qui offre des services équivalents.

Une simplification de l’accès aux APIs

Lorsqu’on créée une application Metro Style avec C#, la totalité des APIs est automatiquement référencée dans le projet. On peut dès lors utiliser directement n’importe quel type supporté par .NET Metro Style dans actions supplémentaires.

Créer des librairies Metro Style portables

Vous pouvez également créer un projet de classe Portable pour développer une bibliothèque. NET Framework qui pourra être utilisée à partir d'une application Metro Style (peu importe son langage) ou même d’applications WPF en bureau classique ! Le projet doit inclure. NET pour Metro Style comme l'un des plates-formes cibles (si on souhaite un partage avec cette plateforme bien entendu). La bibliothèque de classes portable est particulièrement utile lorsque vous voulez développer des classes qui peuvent être utilisés à partir d'applications pour différents types de plates-formes, comme une application Windows Phone, une application de bureau, et une application Metro Style.

Ces librairies “portables” sont une nouveauté et peuvent s’avérer essentielles pour qui désire développer un logiciel tournant sous WinRT et WPF ou Windows Phone 7.x et WPF, etc...

La conversion du code existant

Partir de zéro sur une nouvelle plateforme est certainement le meilleur moyen de “faire propre”, mais puisqu’ici nous sommes sur des plateformes “cousines” étudiées pour être compatibles entre elles (jusqu’à un certain point), il est tout à fait envisageable de vouloir porter un code existant, WPF ou Silverlight, vers WinRT.

Je parle de “cousinage” car comme un cousin germain, s’il existe une “proximité génétique” indéniable, les différences sont suffisamment importantes pour être visibles... Ainsi lors d’un portage vers WinRT il faudra veiller à de nombreuses différences qui se cachent derrière la masse des similitudes, car il y a des changements dans :

  • La gestion de l’interface utilisateur;
  • La gestion des Entrées/Sorties;
  • La gestion du réseau;
  • La manière dont le multitâche fonctionne;
  • La gestion des mécanismes de réflexion;
  • La gestion de la sécurité;
  • La gestion des ressources;
  • La façon dont WCF fonctionne et réagit;
  • A l’intérieur même de certains types .NET

Les changement dans la gestion de l’interface utilisateur

La conversion d’un code d’interface depuis Silverlight n’est pas si difficile du point de vue UI car les bases restent identiques. J’ai pu, dans mes expériences, transférer des UserControls avec leurs styles, des templates, etc, sans rencontrés d’énormes problèmes et même avec parfois une facilité qui m’a étonnée.

La différence première est le stockage des types habituels qui passe de System.Windows à l’espace de noms Windows.UI.Xaml.

Les types sont similaires mais il y a parfois des différences dans les membres. Ce genre de “surprise” se découvre dans l’action et il serait fastidieux d’en dresser la liste complète...

Le premier point à se rappeler est donc de changer toutes les références à System.Windows.* par Windows.UI.Xaml.*. Ainsi la classe Border se trouve désormais dans Windows.UI.Xaml.Controls au lieu de System.Windows.Controls.

Pas trop compliqué surtout si, comme la plupart du temps, les espaces de noms sont indiqués une fois pour toute en entête de code par le biais d’une instruction “using”. Seul cet endroit du code devra donc être modifié.

Les changements dans la gestion des Entrées / Sorties

Ici les changements sont radicaux car tout dans WinRT est asynchrone. C# 5 introduit des nouveaux mots clé “async” et “await” dont je reparlerai bien évidemment et qui simplifient grandement la prise en charge de l’asynchronisme. Il n’en reste pas moins vrai que toutes les opérations d’E/S sont devenus asynchrones et réclameront d’être “revisitées” parfois en profondeur.

Par exemple la lecture des flux via les méthodes System.IO.Stream.BeginRead/EndRead est remplacée par une unique méthode System.IO.Stream.ReadAsync, totalement asynchrone.

Dans le même esprit les écritures sur un flux (BeginWrite/EndWrite) sont remplacés par un unique WriteAsync.

La méthode “Close()” a souvent fait couler beaucoup d’encre. Un fichier, par exemple, une fois sa méthode Close() appelée, est-il “disposé” ou non ? Peut-on directement appeler Dispose() ? Etc. L’enfer est pavé de bonnes intentions... C’est en voulant simplifier les choses du point de vue sémantique que Close() a été ajouté (en pur équivalent à Dispose()). Mais c’était une fausse bonne idée. Sous WinRT les méthodes Close() n’existent plus, reste toujours Dispose(). On peut appeler Dispose() directement, dans un bloc try/finally, ou mieux dans un bloc “using”.

Par exemple la lecture d’un flux sous WinRT, en prenant en compte l’asynchronisme, s’écrira de la façon suivante :

using (StreamReader sr = 
  new StreamReader(await passedFile.OpenStreamForReadAsync()))
{
    while ((nextLine = await sr.ReadLineAsync()) != null)
    {
        contents.Append(nextLine);
    }
}

On voit clairement l’utilisation d’un bloc ‘using’ tel qu’il était de toute façon conseillé d’en utiliser partout où l’on travaillait sur des objets offrant une méthode Dispose() ayant un rôle réel (libérer des ressources non managées occupées par l’instance).

On remarque l’utilisation de ‘await’ qui allège considérablement l’asynchronisme.

Autre problème récurent, la lecture d’un fichier texte. Lorsqu’il n’est pas trop long on utilise souvent System.IO.File.ReadAllText, sous WinRT on utilisera Windows.Storage.PathIO.ReadTextAsync.

L’exemple suivant lit deux fichiers texte, l’un se trouvant dans le package du logiciel, l’autre se trouvant dans le répertoire local de l’application :

public static async void ReadFileSamples()
{
  // Read a file from package
  StorageFolder packageFolder = 
      ApplicationModel.Package.Current.InstalledLocation;
  StorageFile packagedFile = 
      await packageFolder.GetFileAsync("FileInPackage");
 
  // Read a file from AppData
  StorageFolder localFolder = ApplicationData.Current.LocalFolder;
  StorageFile localFile = 
    await localFolder.GetFileAsync("FileInAppData");
}

Nous noterez que ces exemples, comme le squelette de cet article, sont puisés du site de la documentation temporaire de WinRT.

On remarque une fois encore l’utilisation de ‘await’ et des méthodes dont le nom se termine par ‘async’. Absolument toutes les I/O sont asynchrones sous WinRT.

L’isolated Storage

Puisque nous parlons des E/S et de portage depuis Silverlight, il est important de noter la différence dans la gestion de l’Isolated Storage.

(Ce problème ne se pose pas pour un portage depuis WPF puisque ce dernier n’a pas d’équivalent à l’Isolated Storage, mais en revanche si on veut écrire un code portable SL / WinRT / WPF il faudrait forcément faire attention à “simuler” ce fonctionnement pour WPF).

Sous .NET pour Silverlight on utilise la classe System.IO.IsolatedStorageFile, sous WinRT on utilisera la propriété LocalFolder de Windows.Storage.ApplicationData.

De même System.IO.IsolatedStorage.IsolatedStorageSettings sera remplacé par la propriété LocalSettings de Windows.Storage.ApplicationData.

On noter au passage que les espaces de noms de WinRT suivent une logique différente de .NET mais qu’on gagne un peu, en tout cas à mon avis, en intelligibilité. Tout ce qui concerne les E/S se trouve dans Windows.Storage par exemple. C’est clair. Les données de l’application sont dans Windows.Storage.ApplicationData, c’est ultra clair. Forcément, avec le recul, Microsoft a pu retravailler certains aspects comme le nommage des espaces de noms et nous gagnons en cohérence au passage.

Les changements dans la gestion du réseau

Là aussi les changements sont radicaux et demanderont de revisiter le code. D’abord en raison de la nature asynchrone des nouvelles APIs et parce que ces dernières ont une philosophie un peu différente.

AInsi, les échanges en lecture et écriture via Http utilisent sous Silverlight la classe System.Net.WebClient alors que sous WinRT il s’agira de System.Net.Http.HttpClient. Un nom plus parlant quand aux techniques utilisées.

WinRT propose d’ailleurs une autre API destinée aux uploads et downloads de grandes quantités de données : Windows.Networking.BackgroundTransfer. Ici aussi les noms clarifient l’intention. On comprend immédiatement qu’il s’agira de transférer des données dans un thread d’arrière-plan. Ce qui, bien entendu, est mieux adapté lorsqu’il s’agit de communiquer des données de taille importante.

Autre changement, tous les types qui se trouvent dans System.Net.Sockets se retrouvent dans Windows.Networking.Sockets.

Parmi les différences qui peuvent avoir un impact non négligeable on remarquera aussi que les URI relatives ne peuvent pas être passées au classes de WinRT qui réclame des adresses absolues. Cela peut être délicat pour certains codes.

Un dernier aspect concerne la gestion des exceptions. UriFormatException doit être changée dans les ‘catch’ par FormatException qui est la classe parente de UriFormatException. Ne cibler que cette dernière semble laisser des “trous”. En utilisant la classe parente on est certain d’attraper toutes les exceptions que Silverlight gère (plus celles propres à WinRT certainement dans ce cas).

Les changements dans la gestion du multitâche

Certaines classes de gestion du multitâche du framework .NET connaissent des changements dans leurs membres, ce qui peut s’avérer délicat à gérer. Certaines ont totalement disparu... mais heureusement, c’est qu’ici aussi on retrouve un équivalent géré par WinRT qu’il est possible d’utiliser. Mais cela modifie le code ce qui a un poids non négligeable dans le cas d’un portage.

Toutefois il faut relativiser certains changements qui concernent des propriétés peu utilisées comme MemoryBarrier de la classe Thread ou ManagedTheadId.

Plus déroutant peut être l’absence de Thread.CurrentCulture ou CurrentUICulture qu’on retrouve dans CultureInfo de l’espace de noms System.Globalization. Mais je trouve qu’ici aussi on gagne en cohérence. Que des aspects liés à la localisation soient “cachés” dans une classe telle que Thread relève plus de l’astuce que de la bonne écriture d’un framework... Alors que retrouver ces informations dans System.Globalization est d’une logique imparable. Mais chacun appréciera !

La disparition de System.Threading.Timer ne doit pas vous affoler non plus. On retrouve un équivalent en la classe ThreadPoolTimer de Windows.System.Threading. De même que la classe System.Threading.ThreadPool se trouve déplacée dans Windows.System.Threading.

Pour placer un code dans le pool on utilisera un code structuré de cette façon :

Task.Run(() => 
{ 
  // job à faire ici
});

Un code qui place un job dans le pool et qui souhaite attendre sa fin :

await Task.Run(() => 
{ 
  // job à exécuter et à attendre ici
});

On remarque l’étonnante simplicité qu’introduisent ‘async’ et ‘await’.

Pour un code qui créé un job pouvant être long on utilisera la construction suivante :

Task.Factory.StartNew(() => 
{ 
  // long job ici
}, TaskCreationOptions.LongRunning);

 

Les changements dans la gestion de la réflexion

Les changements ne sont pas immenses mais la plupart des membres de System.Type ont été déplacés dans System.Reflection.TypeInfo ce qui malgré tout impact de façon non négligeable un code à porter.

Type.Assembly se retrouve par exemple dans type.GetTypeInfo().Assembly, ce n’est pas compliqué mais encore faut-il retrouver cette équivalence !

De même type.GetMethod(“maMéthode”, BindingFlags.DeclaredOnly) se retrouve par type.GetTypeInfo().GetDeclaredMethod(“MaMéthode”). Dans la même logique type.GetNestedTypes() s’obtient par type.GetTypeInfo().DeclaredNestedTypes.

Si tous ces changements obligent à revoir le code existant, on notera qu’un simple “chercher/remplacer” pourra faire le travail dans la majorité des cas et que dans les autres il s’agit de méthodes ou de membres peu utilisés (en tout cas souvent utilisés dans des parties de code très ciblées ce qui en facilite le changement).

Les changement dans la gestion de la sécurité

Ici presque tout a été supprimé car WinRT fournit bien entendu à l’ensemble de la plateforme et de façon homogène tout ce qui est nécessaire à la gestion de la sécurité, aux authentifications ou à la cryptographie.

Il sera donc nécessaire d’étudier de près les espaces de noms suivants pour retrouver les équivalents :

Les changements dans la gestion des ressources

Dans les applications Metro Style ont créé un fichier unique de ressources ce qui est une philosophie différente des applications desktop généralement. Je reviendrai sur tous ces aspects dans de prochains billets bien entendu, il ne s’agit, pour l’instant, que de débroussailler le chemin...

On notera que les classes de WIndows.ApplicationModel.Resources et Windows.ApplicationModel.Resources.Core sont à utiliser à la place de celles de System.Resources.

Les changements dans la gestion des exceptions

Dans certains cas un type managé peut lever une exception qui n’est pas incluse dans le framework .NET pour Metro Style, ce qui créé des situations assez dangereuses si des ‘catch’ ont été placés sur ces exceptions particulières.

L’astuce, si on peut parler d’astuce, consiste à ‘catcher’ la classe parente de l’exception en question... c’est le conseil de Microsoft, je trouve cela un peu bricolage et dangereux, mais il faudra faire avec...

Un exemple a été évoqué plus haut à propos de UriFormatException. S’il faut utiliser la classe parente FormatException c’est tout simplement que UriFormatException est levée par une partie du code managée mais qu’elle n’existe pas dans le framework .NET épuré pour Metro Style. Situation curieuse, ambigüe et pour parler franchement très “casse gueule”. Méfiez-vous et vérifiez bien vos ‘catch’ !

Les changements dans WCF

En fait les changements se résume à un seul changement, mais de taille : dans une application Metro Style on peut utiliser tous les services de WCF pour obtenir des données mais il est impossible de créer un service WCF pour servir des données... Radical !

C’est ce genre de choses qui expliquent, imposent même, la création du premier OS à deux têtes au monde... Et c’est pourquoi Windows 8 Metro Style est fait pour cohabiter longtemps avec le bureau classique... Metro Style ne permet pas d’écrire de nombreux types d’applications, le bureau classique reste indispensable dans certains cas. Metro Style a été conçu pour des applications grand public puisant leurs données dans le Cloud, absolument pas dans l’optique de créer une plateforme du futur remplaçant l’ancienne... Et c’est pour moi tout le problème de Windows 8 : des idées géniales, de vraies innovations, mais poussées tellement à l’extrême qu’on se retrouve obligé d’avoir un OS bicéphale ce qui est une perte de cohérence énorme, voire une erreur de design impardonnable.

Les changements dans les types .NET

Ils sont assez nombreux malgré tout et éparpillé un peu partout. Ils seront souvent simples à repérer puisque l’avantage d’un langage comme C# sur JavaScript notamment (vous voyez à quel buzz je pense...) permet de s’assurer à la compilation que tout est ok. On imagine quel serait l’enfer de tels changements dans le futur pour ceux qui auraient choisi un langage non fortement typé et non compilé...

Visual Studio saura vous avertir avant même la compilation que System.Xml.XmlConvert.ToDateTime n’est plus accessible. Pas de possibilité de laisser le soft planter chez le client “un jour” quand la fonction sera appelée. En revanche il vous faudra chercher un peu pour savoir qu’il faut utiliser à la place XmlConvert.ToDateTimeOffset car ici le nom change aussi.

Il sera assez rapide de s’apercevoir aussi que System.ICloneable n’existe plus, le code sera rejeté. Ecrire une méthode ad hoc qui retourne le bon type sera la seule solution. Mais cela ne devrait pas trop couter puisqu’il suffira de reprendre le code créé pour supporter ICloneable.

Vous découvrirez d’autres changements... Le site que je vous ai proposé dans un billet précédent et qui s’efforce de lister les équivalences entre Silverlight et WinRT devrait vous aider à trouver la parade (voir le billet sur WinRT Genome Project).

Conclusion

Pareil / pas pareil ... ? That is the question !

Beaucoup de similitudes, beaucoup de petits et parfois grands changements entre Silverlight et WinRT.

Ecrire un code portable entre les deux environnements sera certainement plus facile que de porter un code existant. Quand on sait qu’il y a des différences on prévoit son code en conséquence, quand on reprend un code qui “ne savait pas” qu’il y aurait des changements c’est parfois toute sa structure qui est à revoir.

Mais malgré tout, soyons honnêtes, l’effort de compatibilité entre Silverlight et WinRT est gigantesque.

Tous ceux qui avaient choisi le couple C# / Xaml pour développer seront ravis de voir que 95% de leur savoir-faire reste totalement utile et d’actualité.

Il reste à Windows 8, l’OS à deux têtes, à séduire autant le grand public que les directions informatique.

Personnellement je serai enclin, spontanément, à préférer encore Silverlight pour tout ce qui est de type intra et extranet en entreprise notamment. Mais finalement, Metro Style n’est pas un mauvais choix...si on a décidé de passer à Windows 8 tout le parc informatique de l’entreprise !

Sinon le choix de WPF semble s’imposer pour tout logiciel qui ne peut supporter les limitations de WinRT, le Windows store et la sandbox. J’ai toujours aimé WPF. J’ai adoré Silverlight. Mais j’aime toujours WPF. Je renverrai le lecteur qui connaitrait mal WPF vers cet article de 2008 “10 bonnes raisons de choisir WPF” ou même “9 raisons de plus d’utiliser WPF” de 2009. Ca commence à dater, mais finalement l’histoire n’étant qu’un éternel recommencement, la chute de Silverlight ravive tout l’intérêt de WPF dans bien des cas, même sous Windows 8 !

Pour les applications grand public en revanche, Metro Style est le choix le plus intelligent, si on désire suivre Microsoft dans son grand “shift”... Mais lorsqu’on voit l’indigence des outils de développement pour Java, Html 5, Android ou iOS comparativement à des monstres (gentils) comme Visual Studio et Expression Blend, franchement, à moins d’être masochiste et qu’on soit ou non d’accord avec la totalité de la nouvelle démarche de Microsoft, il faudrait être fou pour ne pas choisir la voie Metro Style et WinRT...

A bientôt pour les suites de cette aventure sur les terres de Windows 8,

Stay Tuned !

blog comments powered by Disqus