Travail pratique #6- Application CRUD avec WCF RIA Services Mettre en œuvre une application 420-335-MOO Techniques dee l informatique - Informatique de gestion 420.AAA Le Framework Simple MVVM Toolkit est entièrement compatible avec WFC RIA Services pour exécuter des opérations CRUDD (Create, Retrieve, Update, Delete). Vous pouvez utiliser des expressionss Linq sur les entités ou créer des Domain service pourr interagir avec la couche d accès aux données (DAL ou Data Access Layer) en utilisant différents ORM (object-relational mapper) comme Entity Framework ou NHibernate. RIA Services permet de développer rapidement des solutions d affaire complètes sans avoir à écrire vous-même de nombreusess lignes de code comme les mises à jour en lot, la validation, l authentification et les autorisations d accès. Le Framework Simple MVVM Toolkit permet de sauver beaucoup de temps pour mettre en route une application d affaire sous WCF RIA Services. De plus, ce Framework adhère au patron de conception MVVM. 1) Aperçu de l applicatiol on CRUD à développer L application à développer permet dans ce travail pratique d ajouter (C), de consulter (R), de mettre à jour (U) et de supprimer (D) des produits. L option Gestion des produits (en haut à droite) amène à une série de boutons et une grille de données. La liste déroulante de gauche affiche des catégories de produit (en anglais) en ordre croissant.
Page 2 Lorsqu une catégorie est sélectionnée et que le bouton Charger est appuyé, la grille affiche les produits (ordre croissant dee nom produit) reliés à la catégorie. En cliquant sur le bouton Nouveau, une fenêtre modale (seule cette fenêtre est accessible) propose d ajouter un nouveau produit
Page 3 L utilisateur peut entrer les informations et ensuite cliquer sur OK. Le nouveau produit apparaît dans la grille. Le numéro du produit, actuellement 0 (zéro) car aucune requête est envoyée au serveur Web, est généré par la base de données (EstIdentité à Vrai dans la table Products).
Page 4 Un produit peut être édité pour modifier ses informations (ici Non disponible est maintenant coché) : Un produit peut être supprimé (l intégrité référentielle n est pas appliquée dans la base de données Northwind, tous les produits peuvent être supprimés).
Page 5 Le produit n est plus là (localement ). Pour envoyer au serveur web toutes les modifications en un seul coup (beauté de la chose), cliquer sur Sauvegarder (principe du guichet automatique). Un message confirme que les changements ont été versés sur le serveur. Les changements sont maintenant persistants. Le bouton Annuler annule toutes les modifications qui viennent d être apportées (évidemment si l utilisateur n a pas encoree sauvegardé).
Page 6 Créer un nouveauu projet nommé NorthwindSimpleMvvmRIA comme ceci : 1) Base de données Northwind (Microsoft SQL Server 2008 R2) 1. Restaurer la base de données Microsoft SQL Serve Northwind.bak (se trouve sur Public). Tout est prêt, rien à changer. Si non, toujours sur Public, se servir de Nwind.mdb (format Microsoft Access) et l importer dans une nouvelle base de données nommée Northwind dans Microsoftt SQL Server 2008 R2. Ajouter les clés primaires dans les trois (3) tables : Categories, Products et Suppliers. Ne pas oublier de mette IsIdentity à Yes dans la table Products. Autrement, ajouter un nouveau produit sera impossible. S assurerr d obtenir ce diagramme :
Page 7 2) Mise en place du service Web côté serveur (application Web) 1. Dans le répertoire Models, créer un item ADO.NET Entity Data Model (ici on utilise Entity Framework, c est-à-dire unn object-relational mapper) nommé Northwind.edm mx. 2. Ajouter les trois (3) tables suivantes et cocher Pluralize : Faire les liens suivants dans le modèle et conserver seulement les champs indiqués dans Category et Supplier. Conserver tous les champs dans Product.
Page 8 S assurer (et remarquer en même temps) que Entity Set Name et Namee sont bien ceux-ci.
Page 9 3. Dans le répertoire Services, ajouter le Domain Service nommé NorthwindDomainService et indiquer ces aspects : Le fichier NorthwindDomainService.cs contient du code créé automatiquement, il se peut qu un jour vous souhaitez le recréer. Si vous ajoutez votre propre code dans ce fichier, il sera supprimé! Que faire? Le placer dans une classe avec le même nom. Voilà un bel exemple d utilisation d une classe partielle. 4. Créer le fichier MonNorthwindDomainService.cs qui est une classe partielle de la classe retrouvée dans NorthwindDomainService.cs. 5. Déplacer toutes les méthodes de type IQueryable de NorthwindDomainService.cs vers MonNorthwindDomainService.cs. 6. Créer la méthode pour GetProduitsParCodeCategorie et faire le code Linq pour obtenir la liste des produits selon un code de catégorie. Trier les données par nom de produit. Inclure les catégories et les fournisseurs (Include) et compléter ce qui manque dans le fichier MonNorthwindDomainService.metadata.cs. 7. Développer les méthodes suivantes pour alimenter les zones de liste déroulantes : GetCategories pour la liste des catégories en ordre croissant de nom, GetSuppliers pour la liste des fournisseurs en ordre croissant de nom de société. 3) Mise en place des éléments côté client (application Silverlight) Le répertoire Service contient les classes qui permettent de communiquer avec les services Web ou les mocks (données bidon pour tester l application sans toucher à la base de données qui pourrait être en production). Les agents service (ServiceAgent) sont des classes pour agir sur les données (simuler des données ou les vraies données dans la base de données). Mettre en œuvre une application 420 335 MO
Page 100 L interface est un contrat quii impose aux 2 classes d implémenter (coder) ses méthodes Contient des données fictives stockées dans des propriétés Contient le code pour communiquer avec la base de données via WCF RIA Services Remarquer dans chaque classe ceci : [ServiceAgentExport(typeof(IProduitsServiceAgent), AgentType = AgentType.Mock)] ] [ServiceAgentExport(typeof(IProduitsServiceAgent), AgentType = AgentType.Real)] C est pour identifier la classe qui simule les données (AgentType.Mock)) et la classe qui se connecte vraiment à la base de données (AgentType. Real). Au moment du développement,, vous pourrez utiliser soit l une ou l autre des classes pour obtenir les mocks ou la vraie base de données par injection.. Votre application, une fois en production, la classe Mocks ne sert plus. Elle pourra être utile à nouveau pour faire évoluer l application (demande de modifications ou amélioration) ou corriger des bugs. 1. Dans le répertoire Services, créer la classe interface nommée IProduitsServiceAgent.cs. À propos de IProduitsServiceAgent.cs. Utiliser IItemListServiceAgent comme exemple, ajouter les méthodes pour obtenir, ajouter ou supprimer des lignes ou bien les sauvegarder ou rejeter les modifications. Parce que la demande de services est asynchrone (un délai), il faut incluree un retour de laa réponse du serveur (callback Action) comme paramètre pour récupérer les résultats ou les exceptions (erreurs). Ajouter, supprimer et rejeter les modifications ne demandent pas de paramètre callbackk 2. Faites les signaturess des méthodes suivantes : ObtenirProduits, AjouterProduit, SupprimerProduit, SauvegarderProduit et RejeterModifications. 3. Dans le répertoire Services, créer la classe MockProduitsServiceAgent.cs et son contenu pour simuler les données. Ne pas implémenter SauvegarderP Produit et RejeterModifications. Modifier les messagess en conséquence.
Page 111 Données Mock (fictives) à utiliser (implanter dans le constructeur dee MockProduitsServiceAgent.cs). Rendez-vous sur le blog pour copier ces lignes l (semble impossible ici ) // Init mock data // Mock pour les catégories mockcategories = new List<Category> { new Category { CategoryID = 1, CategoryNamee = "Boissons (mock)" }, new Category { CategoryID = 2, CategoryNamee = "Condiments ( mock)" }, new Category { CategoryID = 3, CategoryNamee = "Desserts (mock)" } }; // Mock pour les fournisseurs mockfournisseurs = new List<Supplier> { new Supplier { SupplierID = 1, CompanyName= "Exotic Liquids ( mock)", ContactName="Charlotte Cooper (mock)" }, new Supplier { SupplierID = 2, CompanyName = "New Orleans Cajun Delights (mock)", ContactName="Shelley Burke (mock)" }, new Supplier { SupplierID = 3, CompanyName = "Gai pâturage ( mock)", ContactName="Eliane Noz (mock)" } }; // Mock pour les produits mockproduits = new List<Product> { new Product { ProductID = 1, ProductName = "Chai (mock)", CategoryID = 1, SupplierID = 2, Supplier = mockfournisseurs[1], QuantityPerUnit="10 boîtes x 20 sacs (mock)", UnitPrice=5, UnitsInStock=10, UnitsOnOrder=15, ReorderLevel=20, Discontinued=false e}, new Product { ProductID = 2, ProductName = "Chang (mock)", CategoryID = 1, SupplierID = 1, Supplier = mockfournisseurs[0], QuantityPerUnit="24 bouteilles (1 litre) (mock)", UnitPrice=6, UnitsInStock=12, UnitsOnOrder=18, ReorderLevel=22, Discontinued=true }, new Product { ProductID = 3, ProductName = "Northwoods Cranberry Sauce (mock)", CategoryID = 2, SupplierID =1, Supplier = mockfournisseurs[0], QuantityPerUnit="48 pots (6 onces) (mock)", UnitPrice=7, UnitsInStock=14, UnitsOnOrder=20, ReorderLevel=24, Discontinued=false e}, new Product { ProductID = 4, ProductName = "Tarte au sucre (mock)", CategoryIDD = 3, SupplierID = 3, Supplier = mockfournisseurs[2], QuantityPerUnit="48 tartes (mock)", UnitPrice=8, UnitsInStock= =16, UnitsOnOrder=22, ReorderLevel=26, Discontinued=true} }; Le répertoire ViewModels contient les classess qui permettent, entre autres, de faire communiquer la vue (l interface graphique) avec le modèle (les données). 1. Créer la classe ProduitsListeViewModel.cs de la manière suivante : 2. Utiliser la classe ItemListViewModel.cs pour coder la classe ProduitsListeViewModel.cs. Conserver les régions. 3. Utiliser ces messages dans les méthodes indiquées.
Page 12 Contexte Dans la région Methods SauvegarderModifications Dans la région Completion Callbacks CategoriesChargees FournisseursCharges ProduitsCharges InformationsSauvegardees Message à afficher Des modifications ont été apportées. Souhaitez vous les sauvegarder? Impossible d'obtenir les catégories Impossible d'obtenir les fournisseurs Impossible d'obtenir les produits Les informations sont sauvegardées avec succès. Impossible de sauvegarder les informations. Compiler le projet Silverlight et corriger les erreurs au besoin. Le répertoire Locators contient la classe InjectedViewModelLocator.cs qui permet de faire basculer votre application avec des données bidon ou la vraie base de données. 1. Ouvrez et utiliser mvvminjectedlocator pour ajouter la propriété ProduitsListeViewModel et corriger le code (voir la méthode ItemListViewModel pour vous guider). 2. Déplacer les 3 dernières en commentaire à la fin de la classe et placer-les dans à la fin du constructeur. Décommenter les 2 lignes et les corriger. Lignes à la fin de la classe. À déplacer dans le constructeur, décommenter et corriger. // TODO: Move to the ctor to verify service agent creation //Debug.Assert(this.MyViewModel!= null, // string.format("no IMyServiceAgent has ServiceAgentExport with AgentType = {0}", agenttype)); Changer le type d agent pour AgentType.Real afin d utiliser la base de données. Indique d utiliser la base de données plutôt que les mocks. // TODO: Change agent type to Real private AgentType agenttype = AgentType.Mock; Pour (oui changer le commentaire aussi!) : // Change agent type to Real private AgentType agenttype = AgentType.Real; Mettre en œuvre une application 420 335 MO
Page 133 4) Créer des tests unitaires Maintenant que la classe ProductListViewModel.cs est en place, nous pouvons la ester. Les données testées sont celles des Mocks. Dans le projet des tests unitaires, créer la classe de tests ProduitsListeViewModelTests.cs : Compléter les TODO afin de tester les méthodes ChargerCategories, ChargerFournisseurs, ChargerProduits et e ListeCategoriesNbreElements selon ces exigences : Méthode à tester ChargerCategories ChargerFournisseurs ChargerProduits ChargerCategories Nom du test ChargerCategoriesTest ChargerFournisseursTest ChargerProduitsTest ListeCategoriesNbreElements Test à faire Doit être non null. Doit être non null. Doit être non null. La liste doit contenir exactement 3 éléments Conduire les quatre (4) tests afin de s assurerr qu ils passent tous. Vous devriez voir ceci :
Page 144 L intérêt de ces tests programmés c est qu ils peuvent être exécutés en tout temps comme par exemple après des modifications au code pour s assurer que rien n est brisé. 4) Créer les vues : grille des produits et détails d un produit 1. Dans la page MainPage.xaml modifier ce qui suit pour faire pointer le lien vers la page ProduitsListeView.xaml et e changer le nom à afficher pour Gestion des produits. Indique d utiliser la base de données plutôt que les mocks. <! Set CommandParamter to view name (without.xaml) > <HyperlinkButton x:name=" itemslink" Style="{StaticResource LinkStyle}" Command= ="{Binding NavigateCommand}" CommandParameter="ItemListView" TargetName="ContentFrame" Content="items" "/> 2. Créer une page Silverlight nommée ProduitsListeView.xaml. Utiliser le xaml suivant. Les images sont disponibles sous Public. Au besoin, ajouter les xmlns manquants et le codee xaml nécessaire. ProduitsListeView.xaml <navigation:page.resources> <! Style pour les informations des clients dans la zone de liste déroulante des clients > <Style x:key="styletextebouton" TargetType= ="TextBlock"> <Setter Property="FontSize" Value="12" /> <Setter Property="HorizontalAlignment" Value="Left" /> <Setter Property="VerticalAlignment" Value="Center" /> <Setter Property="Foreground" Value="Black" /> </Style> </navigation:page.resources> <StackPanel x:name="layoutroot"> <StackPanel Orientation="Horizontal"> <! ComboBox. Ajouter les l propriétés manquantes > <ComboBox Height="40" Name="categoriesComboBox" Width="200"/> <TextBlock Width="7"/> <! Spacer > <! loadbutton. Ajouter l'événement manquant > <Button Name="loadButton" Width="150" IsEnabled="{Binding CanLoad}">
Page 15 <! Ajouter l'événement Click manquant > <ContentControl> <StackPanel Orientation="Horizontal" > <! Mettre l'image ici telecharger.png" Width="35" Height="35" > <TextBlock Width="7"/> <TextBlock Text="Charger" Style="{StaticResource StyleTexteBouton}" /> <TextBlock Width="5"/> </StackPanel> </ContentControl> </Button> <TextBlock Width="7"/> <! Spacer > <Button Name="addButton" Width="150" IsEnabled="{Binding CanAdd}"> <ContentControl> <StackPanel Orientation="Horizontal" > <! Mettre l'image ici ajouter.png" Width="35" Height="35" > <TextBlock Width="7"/> <TextBlock Text="Nouveau" Style="{StaticResource StyleTexteBouton}" /> <TextBlock Width="5"/> </StackPanel> </ContentControl> </Button> <TextBlock Width="7"/> <! Spacer > <Button Name="editButton" Width="150" IsEnabled="{Binding CanEdit}" > <ContentControl> <StackPanel Orientation="Horizontal" > <! Mettre l'image ici editer.png" Width="35" Height="35" > <TextBlock Width="7"/> <TextBlock Text="Éditer" Style="{StaticResource StyleTexteBouton}" /> <TextBlock Width="5"/> </StackPanel> </ContentControl> </Button> <TextBlock Width="7"/> <! Spacer > <Button Name="removeButton" Width="150" IsEnabled="{Binding CanRemove}" > <ContentControl> <StackPanel Orientation="Horizontal" > <! Mettre l'image ici supprimer.png" Width="35" Height="35" > <TextBlock Width="7"/> <TextBlock Text="Supprimer" Style="{StaticResource StyleTexteBouton}" /> <TextBlock Width="5"/> </StackPanel> </ContentControl> </Button> <TextBlock Width="7"/> <! Spacer > <! Set IsEnabled to True > <! savechangesbutton. Ajouter l'événement manquant > <Button Name="saveChangesButton" Width="150" IsEnabled="True" > <! Ajouter l'événement Click manquant > <ContentControl> <StackPanel Orientation="Horizontal" > <! Mettre l'image ici sauvegarder.png" Width="35" Height="35" > <TextBlock Width="7"/> <TextBlock Text="Sauvegarder" Style="{StaticResource StyleTexteBouton}" /> <TextBlock Width="5"/> </StackPanel> </ContentControl> </Button> <TextBlock Width="7"/> <! Spacer > <! Set IsEnabled to True > <! rejectchangesbutton. Ajouter l'événement manquant > <Button Name="rejectChangesButton" IsEnabled="True" Width="150"> <! Ajouter l'événement Click manquant > Mettre en œuvre une application 420 335 MO
Page 166 <ContentControl> <StackPanel Orientation="Horizontal" > <! Mettre l'image ici annuler.png" Width="35" Height="35" > <TextBlock Width= ="7"/> <TextBlock Text="Annuler" Style="{StaticResource StyleTexteBouton}" /> <TextBlock Width= ="5"/> </StackPanel> </ContentControl> </Button> </StackPanel> <toolkit:separator Height=" "10" /> <sdk:datagrid AutoGenerateColumns="False" Name="productsDataGrid" IsReadOnly="True" Width="1250" HorizontalAlignment="Left"> <sdk:datagrid.columns> <sdk: DataGridTextColumn Binding="{Binding Path=ProductID}" CanUserReorder="True" CanUserResize="True" CanUserSort="True" Width="Auto" Header="No produit" /> <! Ajouter les DataGridTextColumn manquants > </sdk:datagrid.columns> </sdk:datagrid> <Grid> <! Bind IsBusy to IsBusy > <toolkit:busyindicator Name="isBusyIndicator" Height="80" Width="200" IsBusy="{Binding IsBusy}" Margin="152,39,148, 39" /> </Grid> </StackPanel> Il faut créer une vue pour afficher les détails d un produit ou en créer un nouveau. Comme pour chacune des vues, il faut d abord faire une classe ViewModel. 3. Dans le répertoire ViewModels, créer une classe nommée SimpleMvvmViewModelDetail : ProduitDetailViewModel.cs dont l item est de typee 4. Supprimerr ces lignes : Supprimer ces références au ServiceAgent (inutile) dans la région Initialization and Cleanup // TODO: Add a member for IXxxServiceAgent private ProduitsServiceAgent serviceagent;
Page 17 ET // TODO: Ctor to set base.model to DetailType public ProduitDetailViewModel(/* DetailType */ model) { base.model = model; } 5. Ajouter le code dans la région Properties (pour alimenter les zones de listes déroulantes dans la fenêtre détail). 6. Créer une page Silverlight Child Window nommée ProduitDetailView.xaml pour ajouter un nouveau produit ou de modifier un produit sélectionné dans la grille de données. Utiliser ce qui suit pour faire le contenu de la page : ProduitDetailView.xaml <! Label and TextBox styles > <sdk:childwindow.resources> <Style TargetType="sdk:Label"> <Style.Setters> <Setter Property="Height" Value="28"/> <Setter Property="VerticalAlignment" Value="Center"/> <Setter Property="Margin" Value="10,0,0,0"/> </Style.Setters> </Style> <Style TargetType="TextBox"> <Style.Setters> <Setter Property="HorizontalAlignment" Value="Left"/> <Setter Property="Margin" Value="5"/> </Style.Setters> </Style> </sdk:childwindow.resources> <Grid x:name="layoutroot" Margin="2"> <Grid.RowDefinitions> <RowDefinition /> <RowDefinition Height="Auto" /> </Grid.RowDefinitions> <Grid> <Grid.ColumnDefinitions> <ColumnDefinition Width="136*" /> <ColumnDefinition Width="242*" /> </Grid.ColumnDefinitions> <Grid.RowDefinitions> </Grid.RowDefinitions> <sdk:label Content="No produit (non modifiable) :" Grid.Row="0" HorizontalAlignment="Right"/> <sdk:label Content="Nom produit :" Grid.Row="1" HorizontalAlignment="Right"/> <sdk:label Content="Fournisseur :" Grid.Row="2" HorizontalAlignment="Right"/> <sdk:label Content="Catégorie :" Grid.Row="3" HorizontalAlignment="Right"/> <sdk:label Content="Quantité par unité :" Grid.Row="4" HorizontalAlignment="Right"/> Mettre en œuvre une application 420 335 MO
Page 18 <sdk:label Content="Prix unitaire :" Grid.Row="5" HorizontalAlignment="Right"/> <sdk:label Content="Unités en stock :" Grid.Row="6" HorizontalAlignment="Right"/> <sdk:label Content="Unites commandées :" Grid.Row="7" HorizontalAlignment="Right"/> <sdk:label Content="Niveau de réapprovisonnement :" Grid.Row="8" HorizontalAlignment="Right"/> <sdk:label Content="Non disponible:" Grid.Row="9" HorizontalAlignment="Right"/> <! Lier les contrôles aux propriétés du modèle > <TextBox Name="productId" Text="{Binding Path=Model.ProductID, Mode=TwoWay}" Grid.Column="1" Grid.Row="0" Height="28" Width="150" IsReadOnly="True" Background="LightGray" /> <TextBox Name="productName" Text="{Binding Path=Model.ProductName, Mode=TwoWay}" Grid.Column="1" Grid.Row="1" Height="28" Width="300"/> <! ajouter les TextBox manquants. Le premier TextBox est Grid.Column="1" Grid.Row="4" > <! Ajouter le CheckBox manquant Grid.Column="1" Grid.Row="9" > <! Ajouter les attributs manquants dans les combobox > <ComboBox Name="ComboBoxFournisseurs" Grid.Column="1" Grid.Row="2" Margin="5,5,5,5" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="300"/> <ComboBox Name="ComboBoxCategories" Grid.Column="1" Grid.Row="3" Margin="5,5,5,5" Height="25" HorizontalAlignment="Left" VerticalAlignment="Top" Width="300"/> </Grid> <Button x:name="cancelbutton" Content="Cancel" Click="CancelButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,0,0" Grid.Row="1" /> <Button x:name="okbutton" Content="OK" Click="OKButton_Click" Width="75" Height="23" HorizontalAlignment="Right" Margin="0,12,79,0" Grid.Row="1" /> </Grid> 7. Donner le titre de Gestion d un produit à la fenêtre 8. Modifier le code nécessaire dans le fichier ProduitDetailView.xaml.cs. 5) Correction Point à vérifier Fait 1 La liste des tâches à faire (TODO) dans Visual Studio est presque vide 2 MonNorthwindDomainService.cs contient toutes les méthodes de type IQueryable. La classe NorthwindDomainService.cs ne contient aucune méthode de type IQueryable. 3 Vérifier Assert du test ListeCategoriesNbreElements 4 Les tests sont tous un succès sur les méthodes ChargerCategories, ChargerFournisseurs, ChargerProduits et ListeCategoriesNbreElements 5 La liste des catégories est triée en ordre croissant 6 Les produits sont filtrés selon la catégorie 7 Les produits sont triés par défaut par nom en ordre croissant Fenêtre enfant 8 Le titre de la fenêtre enfant est Gestion d un produit 9 L ajout d un produit se fait correctement 10 La modification d un produit se fait correctement 11 La suppression d un produit se fait correctement 12 La sauvegarde sur le serveur Web se fait correctement 13 L annulation se fait correctement Mettre en œuvre une application 420 335 MO