Accueil > .NET, Mobile > Bonnes pratiques pour une application Windows Phone et/ou Windows Store

Bonnes pratiques pour une application Windows Phone et/ou Windows Store

Bonjour à tous, pour ce tutoriel je vous propose de :

  • Comprendre et appliquer le pattern MVVM (Model View View-Model)
  • Organiser son code pour minimiser les dépendances grâce aux Inversion de Contrôles (IoC) et Injections de Dépendances (DI)
  • Maximiser le partage de code entre les différentes plateformes grâce aux PCL (Portable Class Library)
  • Utiliser ces concepts pour :
    • Développer une application Windows Phone
    • Développer une application Windows Store
    • Développer une application cross plateforme Windows Phone et Windows Store
    • Développer d’autres types de projet (WPF, Silverlight…)

Ce tutoriel suppose que vous ayez déjà quelques bases en développement Windows Phone / Windows 8,  les détails spécifiques aux plateformes ne sont pas expliquées mais sont implémentées dans le code source disponible ici

Un petit peu de théorie

L’architecture d’une application est un point clef qui doit être pensé avant le début du développement. Une bonne architecture doit être le bon compromis entre efficacité, adaptabilité et séparation des métiers.

L’architecture MVVM

L’architecture MVVM (Modèle Vue Vue-Modèle) est une architecture très rependue dans les applications Microsoft et est une évolution du bien connu MVC.

  • Modèle : toutes les données, c’est dans cette partie que vous stockerez vos objets métiers
  • Vue : toute la partie graphique, page, contrôles utilisateurs… seul la logique graphique doit être contenue.
  • Vue-Modèle : toute la logique, il lie les données du modèle à l’affichage de la vue grâce aux liaisons (data binding) et récupère les actions utilisateurs grâce aux commandes.

Afin d’expliquer et d’appliquer cette architecture je vais baser ce tutoriel sur un exemple simple utilisant les données de l’Open Data Parisien et plus précisément les fêtes foraines parisiennes.

  • Modèle : mes classes métier tel que « JeuForain.cs », aucune logique
  • Vue : toutes mes classes graphiques tels que mes pages « MainPage.xaml » et mes contrôles utilisateurs « JeuForainUserControl.xaml », que du visuel
  • Vue-Modèle : toute ma logique applicative avec mon « MainViewModel.cs » et mon « ViewModelLocator.cs » (nous y reviendrons dans la partie technique).

 

Les  inversions de contrôles et injections de dépendances

Vous remarquerez que pour l’instant nous n’avons pas encore parlé de la récupération des données, du stockage de celles-ci et toute autre fonctionnalité précise (partage, naviguer entre les pages, envoyer un mail, stocker un paramètre…).  Nous allons utiliser les inversions de contrôles (IoC) et injections de dépendances (DI) pour toutes ces fonctionnalités.

Les IoC permettent un découplage fort entre les services (les fonctionnalités) et la logique de l’application. Chaque service va suivre un contrat (une interface) et la logique de l’application (le Vue Modèle) n’utilisera que les contrats pour accéder aux services.

Ainsi une modification des services n’entraînera aucune modification de la logique (le contrat ne change pas) et l’on pourra même facilement inter-changer des services suivant le même contrat sans aucune incidence sur le reste de l’application.

Une DI est un type d’IoC ou l’on garde une référence vers notre service.

  • Contrat « IJeuxForainsAPIService.cs » permet de récupérer les données des jeux forains, le service « JeuxForainsAPIOpenDataService.cs » suit ce contrat et récupère les données depuis l’open data. On peut imaginer un autre service suivant ce contrat mais se connectant à une API privée.
  • Contrat « IStorageService.cs » permet de stocker et de récupérer les données sur le disque dur, il est suivi par deux classes « StorageService.cs », une pour Windows 8 et une pour Windows Phone, ainsi la logique applicative n’a pas à se soucier des spécificités des OS.
  • Vous pourrez ajouter d’autres services tel le partage, l’envoie de mail, la navigation entre les pages, l’utilisation d’une autre API … en utilisant cette logique.

Les Bibliothèques de Classes Portables

Afin de réutiliser un maximum de code entre les plateformes  (Windows Phone et Windows 8 dans mon cas) il nous faut des projets utilisables par les deux plateformes : les bibliothèques de classe portable (PCL).

Les PCL sont un type de projet spécifique sous Visual studio, hélas les Frameworks disponibles en PCL sont limités et l’on ne pourra pas partager l’ensemble du code mais uniquement certaines parties.

  • Le modèle est partageable, il ne s’agit que de classes basiques et l’on n’a donc aucun souci à les partager. Pour dé-sérialiser les données nous utilisons Newtonsoft.Json qui fournit une librairie portable.
  • Les contrats sont de simples interfaces et sont donc partageables.
  • Les services par contre diffèrent selon les cas :
    • « JeuxForainsAPIOpenDataService.cs » utilise System.Net.Http qui est portable et peut donc être partagé dans une PCL.
    • « StorageService.cs » eux utilisent des Frameworks propres aux plateformes et doivent être implémentés dans des bibliothèques séparées Windows Phone et Windows Store.
  • J’ai pour habitude de centraliser la logique de récupération des données dans un data manager contenu dans une PCL, celui-ci fait appel aux contrats et n’est donc pas sensible aux plateformes. Une grande partie du code est ainsi mutualisé.
  • Les vues  et contrôles utilisateurs sont propres à chaque plateforme.
  • Je conseille d’implémenter les Vues Modèles dans les applications et non dans des PCL, cela évite quelques écueils sur des spécificités. La logique applicative peut différer selon les plateformes.

Passons à la pratique

Maintenant que nous avons vu l’architecture nous allons pouvoir passer à l’implémentation de celle-ci.

J’utilise Visual Studio 2013 et des projets Windows Phone 8 et Windows 8.1 mais la majeur partie du code est réutilisable sous d’autre plateforme.

Si vous ne visez qu’une plateforme utilisez simplement des bibliothèques propres à votre plateforme à la place de PCL. Les PCL sont très pratiques pour écrire du code cross plateforme mais induisent de grandes limitations.

J’utilise MvvMLight,  Newtonsoft.Json et System.Net.http qui sont récupérées à l’aide de Nuget.

Le code source complet est disponible ici. Je vous conseille de télécharger le projet et de naviguer entre celui ci et l’article, le code n’étant pas détaillé dans l’article.

 

Architecture générale de l’application :

  • Dans Visual Studio créez une nouvelle application visant votre plateforme.
  • Ajoutez un dossier PCL, un dossier Windows 8 et un dossier Windows Phone 8.
  • Dans PCL ajoutez un projet de type PCL « Models » et un projet PCL « Services ».
  • Dans Windows 8 ajoutez votre application Windows Store (blank app) ainsi qu’une bibliothèque de classe Windows Store (class library).
  • Dans Windows Phone 8 ajoutez votre application Windows Phone (Windows Phone app) ainsi qu’une bibliothèque de classe Windows Phone (class library).

Services, IoC et DI

IJeuxForainsAPIService

Les données sont issues de l’open data parisien et plus précisément des flux de cette adresse : http://parisdata.opendatasoft.com/api/records/1.0/search?dataset=maneges_et_jeux_2012&facet=arrt&facet=categorie_de_jeux_forains

Modèle :

Il nous faut tout d’abord créer nos classes stockant les données reçues dans le projet model :

  • « JeuForain.cs » contient les données des flux que nous utiliserons dans l’application. Cette classe suit la même organisation que les flux JSON de l’API.
  • « APIJeuxForainsRecord.cs » nous sert a dé-sérialiser les données et suit également l’organisation des flux de l’API.

Service :

Nous allons ensuite créer notre contrat et notre service permettant de récupérer ces données dans le projet « Services » :

  • Créez un dossier « Web », puis dans celui-ci une interface « IJeuxForainsAPIService.cs », celle-ci défini la méthode « GetJeuxForainsAsync ».
  • Créez le service « JeuxForainsAPIOpenDataService.cs » implémentant le contrat en utilisant System.Net.Http et Json.Net (importés à l’aide de NuGet), ces deux bibliothèques étant portables nous pouvons implémenter notre contrat dans une PCL.

Vous pouvez maintenant récupérer les données de votre API à l’aide d’une simple ligne de code :

var jeux = await _jeuxForainsApiService.GetJeuxForainsAsync();

IStorageService

Maintenant il nous faut stocker ces données :

  • Dans le projet « Services » créez un dossier « Storage », puis dans celui-ci une interface « IStorageService.cs » définissant les méthodes  «StoreJeuxForains» et «RetrieveJeuxForains», celles-ci permettrons de stocker et de récupérer nos liste de jeux forains.
  • Dans les projets « Services» de Windows 8 et Windows Phone 8 créez un dossier «Storage» puis implémentez votre contrat IStorageServices dans les classes «StorageService.cs». Chacune va utiliser les spécificités de l’OS pour stocker les données en mémoire tout en suivant le même contrat.

Vous pouvez maintenant stocker et récupérer vos listes de jeux forains à l’aide de 2 lignes de codes :

await _storageService.StoreJeuxForains(listToStore);

var jeux = await _storageService.RetrieveJeuxForains();

Data Manager

Afin de limiter au maximum le View-Model  à la logique applicative toute la logique de gestion des données est contenue dans la classe « DataManager.cs ». Celle-ci en plus de contenir certaines dépendances implémente aussi un système de lock pour gérer l’accès par les différents threads aux données (utile lorsque différentes vues tentent d’accéder aux même données). En utilisant les contrats le DataManager est indépendant des spécificités des OS et donc portable.

  • Dans « Services » PCL créez un dossier « Manager », dans celui-ci ajoutez une classe « DataManager.cs ». Créez une propriété privée pour chaque service puis une fonction Initialize qui permet d’injecter nos dépendances dans notre datamanager. Vous pouvez ajouter un système de lock afin de gérer l’accès multithreadé à votre dataManager. Créez une liste contenant toutes les données reçues et enfin des méthodes contenant la logique de gestion des données de l’application.

Vous pouvez maintenant utiliser votre DataManager pour récupérer vos données à l’aide d’une ligne de code :

DataManager.Instance.LoadOnlineJeuxForains();

Et accéder aux données reçues facilement :

var jeux = DataManager.Instance.AllLoadedJeuxForrains;

Cet exemple est plutôt simple et ne fait appelle qu’à 2 services, vous pouvez en suivant cette démarche injecter vos propres services (navigation entre les pages, mise à jour des tuiles, calcul, taggage, partage…). Plus votre application aura de fonctionnalité plus les injections de dépendances faciliteront son développement.

MVVM

Nous n’avons plus qu’à implémenter notre MVVM pour utiliser l’injection de dépendances précédemment créée.

Notre modèle est déjà créé et nos services sont déjà prêts, nous n’avons donc plus qu’à modifier nos projets d’applications Windows 8 et Windows Phone 8 afin d’implémenter ce pattern :

  • A l’aide de NuGet ajouter « MVVM Light librairies only » à chacun de vos projets applicatifs. J’utilise MVVM Light par habitude et car je trouve celle ci très efficace et pratique. Celle ci est un peu lourde pour un petit projet et rien ne vous empêche d’implémenter vous même votre propre librairie MVVM. 
  • Dans vos deux projets applicatifs créez un dossier ViewModel et ajoutez-y 2 classes :

MainViewModel

« MainViewModel.cs » et le vue modèle couplé à votre page principale.

  • Créez une classe MainViewModel.cs.
  •  Pour créer une donnée couplable il faut simplement créer une propriété publique. Pour une collection il faut créer une propriété publique de type ObservableCollection<>. Lorsque vos données sont mises à jour il suffit de notifier la vue à l’aide d’un « RaisePropertyChanged(() => VotreNomDePropriété); ».
  • Pour coupler une commande il vous faut créer une propriété publique de type RelayCommand pointant vers votre méthode.
  • Vous aurez besoin d’un constructeur prenant en paramètre les services que vous voulez injecter dans votre vue modèle. Le test sur !IsInDesignModeStatic permet d’éviter les erreurs de compilation avec le designer et Blend.

Vous pouvez maintenant implémenter votre logique applicative.

Si vous avez d’autres pages vous pourrez créer d’autres ViewModel couplés à chacune de celles-ci.

Nos ViewModel peuvent maintenant gérer toute la logique de nos applications, il nous faut maintenant les exposer à nos vues et les lier à celles-ci.

ViewModelLocator

« ViewModelLocator.cs » permet de déclarer vos ViewModel, d’y injecter vos dépendances et d’exposer vos ViewModel à vos vues.

  • Créez une classe ViewModelLocator.cs.
  • Déclarez un constructeur statique. Celui-ci enregistre vos injections de dépendances ainsi que vos ViewModels à l’aide de SimpleIoC, il initialise aussi votre dataManager.
  • Déclarez votre propriété publique Main, celle-ci expose votre MainViewModel à vos vues. 
  • Si besoin déclarez vos autres ViewModels et nettoyez vos ressources.

Nous y sommes presque, pour permettre à vos vues d’accéder à votre locator, et donc à vos ViewModels il faut le déclarer dans votre fichier de ressources principal : « app.xaml »

  • Dans le fichier app.xaml :
    • Déclarez le namespace viewmodel selon la syntaxe de votre plateforme.
    • Exposez votre locator dans les ressources de votre application.

Il se peut qu’à ce point vous ayez des erreurs, nettoyez votre solution (manuellement si besoin), quittez et recompilez pour les régler.

Les vues

Nous n’avons plus besoin que de lier nos vues à notre ViewModel grâce au Locator.

  • Dans vos solutions créez un dossier « Views » et mettez y votre « MainPage.xaml » (pensez bien à renommer vos namespace), toutes vos vues devront être dans ce dossier.
  • Dans ce dossier créez un dossier «  UserControl »qui contiendra tous vos contrôles utilisateurs :
    • Créez un Contrôle Utilisateur « JeuForainUC.xaml » « JeuForainUC.xaml.cs » afin d’afficher les détails d’un élément.
  • Dans votre main page :
    • Définissez le contexte de votre page comme étant le MainViewModel, grâce à votre DataContext vous avez maintenant accès à l’ensemble des propriétés publiques de votre ViewModel.
    • Définissez un bouton et liez sa commande à votre commande contenu dans votre MainViewModel :
    • Définissez une listbox (listview sous windows 8), liez sa source à votre collection de MainViewModel et définissez son item template comme étant votre contrôle utilisateur (n’oubliez pas de définir son namespace) :
    • Par défaut le binding est dans un seul sens, de vote ViewModel vers votre Vue. Si vous voulez que votre vue mette à jour automatiquement votre ViewModel, comme par exemple pour une textbox remplissable par l’utilisateur pensez à ajouter « ,Mode=TwoWay » dans votre binding.

Conclusion

Tout est en place, vous avez :

  • Créé votre modèle
  • Créé vos contrat de services
  • Implémenté vos contrats dans des services portables ou propres à vos plateformes
  • Créé vos View Model
  • Injecté vos services dans vos View Model et exposé ceux-ci grâce au locator
  • Créé vos vues et lié celles-ci aux View Model

Un click sur le bouton enverra une commande à votre vue model, il chargera les données dans le model grâce aux injections de dépendances contenue dans votre datamanager, puis notifiera la vue d’une mise à jours des données, magique !

L’architecture de ce projet peut paraître complexe, mais votre code est organisé et les différentes parties sont clairement délimitées. Au fur et à mesure de l’enrichissement de votre projet il sera toujours simple d’ajouter des pages et des fonctionnalités sans complexifier votre projet et en évitant les effets de bord.

J’espère que ce premier tutoriel était clair et complet, n’hésitez pas à me demander des précisions ou à me faire des remarque pour que je le mette à jour !

Enfin Windows Phone 8.1 apportera des grandes améliorations sur la portabilité entre Windows Phone et Windows Store, en attendant patience !

Thierry

PS quelques sources et liens utiles:

PS Bis En bonus dans le code un système permettant d’afficher des données de test avec le designer et sous Blend grâce au databinding est implémenté 😉

  1. Pas encore de commentaire
  1. Pas encore de trackbacks


− 3 = deux