
Microsoft a annoncé le mois dernier .NET 7 Preview 6, la version du framework pour la création des applications. Elle améliore les convertisseurs de type, personnalise des contrats JSON. « .NET 7 s'appuie sur la base établie par .NET 6, qui comprend un ensemble unifié de bibliothèques de base, de runtime et de SDK, une expérience de développement simplifiée et une productivité accrue des développeurs. Les principaux domaines d'intérêt de .NET 7 comprennent une meilleure prise en en charge des scénarios cloud native, des outils facilitant la mise à niveau des projets existants et la simplification de l'expérience du développeur en facilitant le travail avec les conteneurs », a écrit Jeremy Likness de l'équipe .NET à propos de cette version. En gros, .NET 7 vise à faciliter le développement d'applications cloud natives et la conteneurisation.
La boîte à outils de la communauté .NET est une collection d'aides et d'API qui fonctionnent pour tous les développeurs .NET et qui sont indépendants de toute plate-forme d'interface utilisateur spécifique. La boîte à outils est maintenue et publiée par Microsoft, et fait partie de la Fondation .NET. Elle est également utilisée par plusieurs projets internes et applications en boîte, comme le Microsoft Store. À partir de la nouvelle version 8.0.0, le projet se trouve désormais dans le dépôt CommunityToolkit/dotnet sur GitHub, qui comprend toutes les bibliothèques faisant partie de la boîte à outils.
Toutes les API disponibles ne dépendent d'aucun runtime ou framework spécifique et peuvent donc être utilisées par tous les développeurs .NET. Ces bibliothèques sont multi-cibles, de .NET Standard 2.0 à .NET 6, ce qui leur permet de prendre en charge le plus grand nombre de plateformes possible et d'être optimisées pour obtenir les meilleures performances lorsqu'elles sont utilisées sur des moteurs d'exécution plus récents.
Comme pour chaque version du Community Toolkit, toutes les modifications ont été influencées par les commentaires reçus à la fois par les équipes de Microsoft qui utilisent le Toolkit et par les autres développeurs de la communauté. Les bibliothèques du .NET Community Toolkit sont les suivantes :
- CommunityToolkit.Common ;
- CommunityToolkit.Mvvm (alias "Microsoft MVVM Toolkit") ;
- CommunityToolkit.Diagnostics ;
- CommunityToolkit.HighPerformance.
Vous vous demandez peut-être pourquoi la première version du .NET Community Toolkit est la version 8.0.0. Bonne question ! La raison en est que toutes les bibliothèques du .NET Community Toolkit faisaient à l'origine partie du Windows Community Toolkit, qui est une collection d'aides, d'extensions et de contrôles personnalisés qui simplifient et démontrent les tâches courantes des développeurs créant des applications UWP et .NET pour Windows 10 et Windows 11.
Au fil du temps, le nombre d'API ciblant uniquement .NET et ne comportant aucune dépendance spécifique à Windows a augmenté, et Microsoft a décidé de les séparer dans un projet distinct afin qu'elles puissent évoluer indépendamment et être plus faciles à trouver pour les développeurs .NET qui ne développent pas sous Windows. C'est ainsi qu'est né le .NET Community Toolkit. Cela a également permis de mieux organiser la documentation, qui comporte désormais des sections distinctes pour chaque boîte à outils spécifique à une plateforme.
Étant donné que la dernière version du Windows Community Toolkit avant de se ramifier était 7.1.x, Microsoft a décidé de suivre la sémantique de ce numéro de version pour rendre la transition plus facile à comprendre pour les utilisateurs, et c'est pourquoi la première version du .NET Community Toolkit est 8.0.0. À l'avenir, il sera versionné séparément du Windows Community Toolkit, car chaque projet aura sa propre feuille de route et son propre calendrier de publication.
Ceci étant éclairci, plongeons maintenant dans toutes les nouvelles fonctionnalités de cette nouvelle version majeure des bibliothèques du .NET Community Toolkit !
MVVM Toolkit
Comme annoncé précédemment dans la version 7.0, l'un des principaux composants du .NET Community Toolkit est le MVVM Toolkit : une bibliothèque MVVM moderne, rapide, agnostique en termes de plateforme et modulaire. Il s'agit de la même bibliothèque MVVM que celle utilisée par le Microsoft Store, l'application Photos, etc.
MVVM Toolkit s'inspire de MvvmLight, et est également son remplaçant officiel maintenant que la bibliothèque a été dépréciée. Microsoft a également collaboré avec Laurent Bugnion lors du développement de la boîte à outils MVVM, et il a approuvé la boîte à outils MVVM comme la voie à suivre pour les utilisateurs actuels de MvvmLight. Laurent Bugnion travaille en tant que Senior Cloud Developer Advocate pour Microsoft après avoir travaillé pendant près de 10 ans pour IdentityMine et Valorem.
La boîte à outils MVVM repose sur quelques principes clés :
- Plateforme agnostique : cela signifie qu'il ne dépend d'aucun framework d'interface utilisateur spécifique. Il peut être utilisé pour partager du code entre UWP, WinUI 3, MAUI, WPF, Avalonia, Uno, et bien plus ;
- Agnostique en matière de runtime : la bibliothèque est multicible et prend en charge jusqu'à .NET Standard 2.0, ce qui signifie que vous pouvez bénéficier d'améliorations des performances lors de l'exécution sur des runtimes modernes (par exemple .NET 6), tout en continuant à l'utiliser même sur .NET Framework ;
- Simplicité de prise en main et d'utilisation : il n'y a pas d'exigences strictes concernant la structure de l'application ou les modèles de codage à utiliser. Vous pouvez utiliser la bibliothèque en fonction de votre propre architecture et de votre style ;
- À la carte : tous les composants sont indépendants et peuvent également être utilisés séparément. Il n'y a pas d'approche "tout compris" à laquelle vous êtes contraint : si vous ne souhaitez utiliser qu'un seul type de la bibliothèque, vous pouvez le faire sans problème, puis utiliser progressivement d'autres fonctionnalités selon vos besoins ;
- Implémentation de référence : toutes les API disponibles sont conçues pour être légères et performantes, en fournissant des "implémentations de référence" pour les interfaces qui sont incluses dans la bibliothèque de classes de base .NET, mais ne disposent pas de types concrets pour les utiliser directement. Par exemple, vous pourrez trouver une « mise en œuvre de référence » pour des interfaces telles que INotifyPropertyChanged ou ICommand.
La plus grande nouveauté de la version 8.0.0 de la boîte à outils MVVM sont les nouveaux générateurs de sources MVVM, qui sont destinés à réduire considérablement le code passe-partout nécessaire pour configurer une application utilisant MVVM. Par rapport aux générateurs de prévisualisation que Microsoft a livré dans la version 7.1.0, ils ont également été complètement réécrits pour être des générateurs incrémentiels, ce qui signifie qu'ils fonctionneront beaucoup plus rapidement qu'avant et qu'ils aideront à maintenir l'EDI rapide et réactif, même lorsque vous travaillez sur des projets à grande échelle.
La création de commandes peut être assez répétitive, avec la nécessité de configurer une propriété pour chaque méthode que nous voulons exposer de manière abstraite aux différents composants de l'interface utilisateur de nos applications qui sont censés les invoquer (comme les boutons). C'est là que le nouvel attribut [RelayCommand] entre en jeu : il permettra à la boîte à outils MVVM de générer automatiquement des commandes (en utilisant les types RelayCommand inclus dans la bibliothèque) avec la bonne signature, en fonction de la méthode annotée.
À titre de comparaison, voici comment on procède habituellement pour mettre en place une commande :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 | private IRelayCommand<User> greetUserCommand; public IRelayCommand<User> GreetUserCommand => greetUserCommand ??= new RelayCommand<User>(GreetUser); private void GreetUser(User user) { Console.WriteLine($"Hello {user.Name}!"); } |
Cela peut maintenant être simplifié à ceci :
Code : | Sélectionner tout |
1 2 3 4 5 | [RelayCommand] private void GreetUser(User user) { Console.WriteLine($"Hello {user.Name}!"); } |
Le générateur de sources se chargera de créer la bonne propriété GreetUserCommand en fonction de la méthode annotée. En outre, une méthode CanExecute peut également être spécifiée, et il est également possible de contrôler le niveau de concurrence pour les commandes asynchrones. Il existe également d'autres options permettant d'affiner le comportement des commandes générées.
Propriétés observables
L'écriture de propriétés observables peut être extrêmement verbeuse, surtout lorsqu'il faut également ajouter une logique supplémentaire pour gérer les propriétés dépendantes qui sont notifiées. Tout cela peut être grandement simplifié en utilisant les nouveaux attributs du MVVM Toolkit et en laissant le générateur de sources créer des propriétés observables en coulisse.
Les nouveaux attributs sont [ObservableProperty], [NotifyPropertyChangedFor] et [NotifyCanExecuteChangedFor], [NotifyDataErrorInfo] et [NotifyPropertyChangedRecipients]. Passons rapidement en revue ce que tous ces nouveaux attributs peuvent faire.
Considérons un scénario dans lequel il existe deux propriétés observables, une propriété dépendante et la commande définie ci-dessus, et dans lequel la propriété dépendante et la commande doivent être notifiées lorsque l'une des deux propriétés observables change. Autrement dit, lorsque FirstName ou LastName changent, FullName est également notifié, ainsi que la commande GreetUserCommand.
C'est ainsi que cela aurait été fait dans le passé :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | private string? firstName; public string? FirstName { get => firstName; set { if (SetProperty(ref firstName, value)) { OnPropertyChanged(nameof(FullName)); GreetUserCommand.NotifyCanExecuteChanged(); } } } private string? lastName; public string? LastName { get => lastName; set { if (SetProperty(ref lastName, value)) { OnPropertyChanged(nameof(FullName)); GreetUserCommand.NotifyCanExecuteChanged(); } } } public string? FullName => $"{FirstName} {LastName}"; |
Tout cela peut maintenant être réécrit comme suit :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 | [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] [NotifyCanExecuteChangedFor(nameof(GreetUserCommand))] private string? firstName; [ObservableProperty] [NotifyPropertyChangedFor(nameof(FullName))] [NotifyCanExecuteChangedFor(nameof(GreetUserCommand))] private string? lastName; public string? FullName => $"{FirstName} {LastName}"; |
La boîte à outils MVVM se chargera de la génération du code pour ces propriétés, y compris l'insertion de toute la logique permettant de déclencher les événements de changement de propriété spécifiés ou d'exécuter les événements de changement.
Mais attendez, il y a plus ! Lorsque vous utilisez [ObservableProperty] pour générer des propriétés observables, la boîte à outils MVVM génère également deux méthodes partielles sans implémentation : On<PROPERTY_NAME>Changing et On<PROPERTY_NAME>Changed. Ces méthodes peuvent être utilisées pour injecter une logique supplémentaire lorsqu'une propriété est modifiée, sans avoir à recourir à une propriété manuelle. Notons qu'étant donné que ces deux méthodes sont partielles, à retour nul et sans définition, le compilateur C# les supprimera complètement si elles ne sont pas implémentées, ce qui signifie que lorsqu'elles ne sont pas utilisées, elles disparaissent tout simplement et n'ajoutent aucune surcharge à l'application.
Voici un exemple de la façon dont ils peuvent être utilisés :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 | [ObservableProperty] private string name; partial void OnNameChanging(string name) { Console.WriteLine($"The name is about to change to {name}!"); } partial void OnNameChanged(string name) { Console.WriteLine($"The name just changed to {name}!"); } |
Bien entendu, vous êtes également libre de n'implémenter qu'une seule de ces deux méthodes, ou aucune. À partir de l'extrait ci-dessus, le générateur de sources produira un code analogue à celui-ci :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public string Name { get => name; set { if (!EqualityComparer<string>.Default.Equals(name, value)) { OnNameChanging(value); OnPropertyChanging(); name = value; OnNameChanged(); OnPropertyChanged(); } } } partial void OnNameChanging(string name); partial void OnNameChanged(string name); |
L'attribut [ObservableProperty] prend également en charge la validation : si l'un des champs représentant une propriété possède un ou plusieurs attributs héritant de ValidationAttribute, ceux-ci seront automatiquement copiés dans les propriétés générées. Cette approche est donc entièrement prise en charge lorsque vous utilisez ObservableValidator pour créer des formulaires validables. Si vous souhaitez que la propriété soit validée chaque fois que sa valeur est définie, vous pouvez également ajouter [NotifyDataErrorInfo] pour que le code de validation soit également généré dans le paramètre de la propriété.
D'autres fonctionnalités sont disponibles pour [ObservableProperty] et, comme pour les commandes.
Prise en charge de l'annulation pour les commandes
Une nouvelle propriété a été ajoutée à l'attribut [RelayCommand], qui peut être utilisée pour demander au générateur de source de générer une commande d'annulation en plus de la commande originale. Cette commande d'annulation peut être utilisée pour annuler l'exécution d'une commande asynchrone.
Cela montre également comment [RelayCommand] peut s'adapter automatiquement aux méthodes asynchrones et aux méthodes qui acceptent également des paramètres, et créer des implémentations de commandes asynchrones en coulisse. Cela permet également de mettre en place des fonctionnalités supplémentaires, comme des liaisons faciles à configurer pour afficher des indicateurs de progression, etc.
Voici un exemple de la façon dont ils peuvent être utilisés :
Code : | Sélectionner tout |
1 2 3 4 5 | [RelayCommand(IncludeCancelCommand = true)] private async Task DoWorkAsync(CancellationToken token) { // Do some long running work with cancellation support } |
À partir de ce petit extrait, le générateur produira le code suivant :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 | private AsyncRelayCommand? doWorkCommand; public IAsyncRelayCommand DoWorkCommand => doWorkCommand ??= new AsyncRelayCommand(DoWorkAsync); ICommand? doWorkCancelCommand; public ICommand DoWorkCancelCommand => doWorkCancelCommand ??= IAsyncRelayCommandExtensions.CreateCancelCommand(UpdateSomethingCommand); |
Ce code généré, combiné à la logique de l'API IAsyncRelayCommandExtensions.CreateCancelCommand, permet de n'avoir besoin que d'une seule ligne de code pour qu'une commande soit générée, notifiant l'interface utilisateur dès que le travail a commencé ou est en cours d'exécution, avec un contrôle automatique de la concurrence (la commande est désactivée par défaut lorsqu'elle est déjà en cours d'exécution). La commande d'annulation séparée sera notifiée chaque fois que la commande primaire commencera ou finira de s'exécuter, et lorsqu'elle sera exécutée, elle signalera l'annulation au jeton passé à la méthode enveloppée par la commande primaire. Tout ceci est complètement abstrait et facilement accessible avec un seul attribut.
Prise en charge des changements de diffusion pour les propriétés générées
Microsoft a également ajouté un nouvel attribut [NotifyPropertyChangedRecipients] qui peut être utilisé sur une propriété observable générée à partir d'un type qui hérite de ObservableRecipient (ou qui est annoté avec [ObservableRecipient]). Son utilisation génèrera un appel à la méthode Broadcast, afin d'envoyer un message à tous les autres composants abonnés au sujet du changement de propriété qui vient de se produire. Cela peut être utile dans les scénarios où un changement de propriété d'un modèle de vue doit également être notifié à d'autres composants de l'application (Supposons qu'il y ait une propriété booléenne IsLoggedIn qui se met à jour lorsqu'un utilisateur se connecte ; cela peut notifier et déclencher le rafraîchissement de certains autres composants de l'application avec le message diffusé).
Il peut être utilisé comme suit :
Code : | Sélectionner tout |
1 2 3 | [ObservableProperty] [NotifyPropertyChangedRecipients] private string name; |
Et cela produira un code analogue à celui-ci :
Code : | Sélectionner tout |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | public string Name { get => name; set { if (!EqualityComparer<string>.Default.Equals(name, value)) { OnNameChanging(value); OnPropertyChanging(); string oldValue = name; name = value; Broadcast(oldValue, value, nameof(Name)); OnNameChanged(); OnPropertyChanged(); } } } |
Il s'agit d'une autre fonctionnalité permettant d'augmenter les propriétés générées et de garantir qu'elles peuvent être utilisées dans presque tous les scénarios, sans être obligé de se rabattre sur des propriétés manuelles.
Composition du ViewModel
Le C# ne dispose pas de l'héritage multiple, ce qui peut parfois poser problème. Que faire si un modèle de vue doit hériter d'un type spécifique, mais que vous souhaitez également injecter la prise en charge de INotifyPropertyChanged ou qu'il hérite également de ObservableRecipient pour avoir accès à ses API ?
Le MVVM Toolkit permet désormais de contourner ce problème en introduisant des attributs pour la génération de code qui permettent d'injecter la logique de ces types dans des classes arbitraires. Il s'agit de [INotifyPropertyChanged], [ObservableObject] et [ObservableRecipient]. En les ajoutant à une classe, le générateur de sources MVVM Toolkit inclura toute la logique de ce type dans cette classe, comme si cette classe avait également hérité de ce type. Par exemple :
Code : | Sélectionner tout |
1 2 3 4 | [INotifyPropertyChanged] partial class MyObservableViewModel : DatabaseItem { } |
Il est toujours recommandé d'hériter des types de base tels que ObservableObject chaque fois que cela est nécessaire, car cela peut également aider à réduire la taille binaire, mais avoir la possibilité d'injecter du code de cette façon lorsque cela est nécessaire peut aider à contourner les limitations de C# dans les cas où le changement du type de base d'un viewmodel est tout simplement impossible, comme dans l'exemple ci-dessus.
Amélioration des API de messagerie
Une autre fonctionnalité couramment utilisée dans le MVVM Toolkit est l'interface IMessenger, qui est un contrat pour les types qui peuvent être utilisés pour échanger des messages entre différents objets. Cela peut être utile pour découpler différents modules d'une application sans avoir à conserver des références fortes aux types référencés. Il est également possible d'envoyer des messages à des canaux spécifiques, identifiés de manière unique par un jeton, et d'avoir différents messagers dans...
La fin de cet article est réservée aux abonnés. Soutenez le Club Developpez.com en prenant un abonnement pour que nous puissions continuer à vous proposer des publications.