I. Introduction : Préparation à l'accès aux données▲
Cette introduction ne fait pas partie du document original. Elle est juste là à but dictatique afin de préparer l'applicationpour l'accès aux données.
Tout d'abord, récupérons la base NORTHWND.MDF disponible ici.
Plaçons-là dans le répertoire MonApplication.web/AppData.
Effectuons un clic-droit sur MonApplication.web et ajoutons un nouvel item.
Allons dans Data et sélectionnons ADO.NET Entity Data Model puis nommons-le Northwind.edmx.
Une nouvelle fenêtre apparait nous demandant de choisir quel type de modèle nous désirons. Sélectionnons Generate from database.
Dans la fenêtre d'option, dans Data Source, sélectionnons Change et choisissons Microsoft SQL Server Database File.
Cliquons sur le bouton Browse de Database File Name et sélectionnons la base NORTHWND.MDF.
Nous pouvons cliquer sur le bouton Test Connection pour nous assurer que tout se passe bien.
Cliquons sur OK, et une nouvelle fenêtre apparait. Cliquons alors sur Next.
Sur la fenêtre suivante, nous allons sélectionner la table qui nous intéresse, en l'occurrence, ici, il faut cocher SuperEmployees (dbo). Validons notre choix en cliquant sur Finish.
A ce stade, il est alors nécessaire de compiler à nouveau la solution afin de tout prendre en compte. Pour cela, faisons un clic droit sur notre solution et sélectionnons Build Solution.
Voilà, nous avons créé notre couche d'accès aux données.
II. Partie 2 : L'accès aux données▲
Maintenant, une fois que tout est en place, nous allons pouvoir parler de l'accès aux données. Quasiment toutes les applications professionnelles ont besoin d'un accès aux données. Nous allons d'abord démarrer avec le projet web. Pour cet exemple, j'ai utilisé un modèle de donnée Entity Framework, mais RIA Services fonctionne très bien avec tout type de données issues d'objets ou de fichier XML, de services web au Linq to Sql.
Maintenant, la question qui se pose est : comment allons-nous accéder à ces données depuis notre client Silverlight ? Traditionnellement, la plupart des applications d'entreprises démarrent avec un modèle d'application 2-tiers. Ceci est source de nombreux problèmes en termes de flexibilité et d'évolutivité. De plus, cela ne fonctionne tout simplement pas avec l'architecture client Silverlight / web.
Du coup, les développeurs s'orientent plutôt dans le monde du n-tiers. Il est très facile, avec .NET RIA Services de créer des services n-tiers qui soient flexibles et évolutifs, bâtis sur WCF et ADO.NET Data Services.
Ces services .NET RIA modélisent la logique de notre application UI-tiers et encapsulent l'accès à nos données diverses tel que les données POCO (Plain Old CLR Object), les services Cloud comme Azure, S3, etc. via REST et autre. L'un des points intéréssant de cette technologie est que l'on peut migrer d'une base de donnée SQL Server à un service Azure distant sans rien n'avoir à changer quoique ce soit dans la logique de notre application.
Voyons maintenant à quel point il est aisé de créer ces services RIA.
Effectuons un clic droit sur le projet serveur (MonApplication.web) et sélectionnons ajouter une nouvelle classe Domain Service. Nommons la nouvelle classe SuperEmployeeDomainService.cs.
Dans l'assistant, sélectionnons notre source de données (ici NORTHWNDEntities). Notez que nous pourrions choisir une classe Linq2Sql, une classe POCO, etc. Veillons également à cocher les cases Enable Client Access, SuperEmployees, Enable editing et Generate associated classes for metada.
Dans la classe nouvellement créée, nous avons une ébauche de toutes les méthodes pour accéder à nos données. Nous devrons bien évidemment les modifier pour notre application. Pour les prochaines étapes, nous allons utiliser la méthode GetSuperEmployees(), donc, nous allons devoir la modifier comme suit :
Maintenant, nous allons basculer coté client. Tout d'abord, compilons à nouveau la solution afin d'y accéder coté client directement. Ces projets sont liés.
Effectuons un glisser-déposer du Datagrid de la boite à outil dans notre vue Home.xaml, juste après les TextBlock et avant la fermeture du StackPanel.
Modifions alors le xaml ainsi ajouté comme suit :
Maintenant, dans le code behind (c-a-d, dans le fichier Home.xaml.cs), ajoutons la clause using MonApplication.Web. Notez qu'il est intéressant que MonApplication.Web soit définie sur le serveur. Nous pouvons maintenant accéder au proxy client du serveur DomainService localement.
A la ligne 1, nous avons créé notre SuperEmployeeDomainContext. Il s'agit du SuperEmployeeDomainService coté client. Notez la convention de d'appellation ici.
A la ligne 2, nous lions les données à la datagrid que nous avons créé précédemment. Enfin, à la ligne 3, nous chargeons les données grâce à la méthode GetSuperEmployees() que nous avons défini sur le serveur. Notez que tout cela se fait de façon asynchrone et que nous n'avons pas a nous préoccuper de la complexité de l'asynchronisation.
Et voilà le résultat ! Nous avons toutes nos entrées, mais avec un vrai site web, ne voulons-nous pas pouvoir paginer et faire en sorte que le serveur trie et filtre les résultats ? Voyons comment procéder.
Tout d'abord, effaçons totalement les lignes de code que nous avons ajouté en code behind (c-a-d, les trois lignes dans notre fichier Home.xaml.cs). Puis, dans notre vue Home.xaml, ajoutons un DomainDataSource en faisant un glisser-déposer de la boite à outil.
Puis, éditons le code nouvellement inséré. Ajoutons en premier lieu l'espace de nom de notre application.
En 1, nous avons jouté l'espace de nom.
En 2, nous appelons la méthode GetSuperEmployeesQuery du DomianContext spécifié en 4.
En 3, nous réglons la taille du chargement à 20. Ceci signifie que nous allons télécharger les données par groupe de 20.
Maintenant, nous allons lier tout cela au Datagrid et afficher un petit indicateur de chargement.
Pour ce faire, nous allons tout d'abord ajouter un espace de nom afin d'accéder à l'indicateur de progression.
Puis nous allons modifier notre vue comme suit :
lorsque l'on fait un glisser-déposer d'un contrôle sur la vue, l'espace de nom est automatiquement ajouté. Ajoutons donc le DataPager de cette manière.
A la ligne 27, nous voyons le Datagrid qui est lié à la propriété DDS.Data. Puis, nous voyons un DataPager à la ligne 31, qui est lié à la même source de données. Ceci nous fournit l'interface de pagination. Notez qu'à la ligne 31 nous choisissons d'afficher dix enregistrements à la fois. Finalement, nous englobons l'ensemble dans un contrôle Activity afin d'afficher la progression.
Ce qui est pratique avec les contrôles Activity, Datagrid et DataPager, c'est que l'on peut les utiliser avec n'importe quelle source de données comme les services WCF, les services REST, etc.
Appuyons sur F5 et voyons le résultat.
Notez que nous chargeons 20 enregistrements à la fois, mais que nous n'en n'affichons que 10. Ainsi, si nous avançons d'une seule page, cela sera géré uniquement par le client, mais si nous avançons d'avantage, nous faisons un appel serveur et nous téléchargeons 20 enregistrements de plus. Notez également que le tri fonctionne aussi. Et où se trouve le code qui gère le tri ? Avons-nous écrit le code coté client ou serveur ? Pas du tout, ceci n'est que la magie de Linq, tout cela se fait tout seul et le résultat tombe.
Nous pouvons déjà ajouter une fonction de groupement. Pou cela, nous allons tout d'abord ajouter un espace de nom comme suit :
Puis, nous ajoutons le code suivant :
Et voilà le résultat :
Maintenant, ajoutons un filtrage. Tout d'abord, ajoutons un Label et un TextBox.
Enfin, ajoutons ces filtres à notre DomainDataSource :
Lorsque nous appuyons sur F5, nous obtenons une boite de filtre et lorsque l'on tape quelque chose dedans, nous faisons un filtrage des résultats coté serveur.
Maintenant, supposons que nous voulons créer un champ de saisie automatique plutôt qu'une simple boite de texte. La première chose que nous devons faire est d'obtenir toutes les origines possibles. Notez que nous devons les obtenir à partir du serveur (le client risque en effet de ne pas toutes les avoir dans la mesure où nous chargeons les données par groupe de 20, que nous effectuons une pagination, etc.). Pour ce faire, nous allons ajouter une méthode dans notre DomainService.
Dans le fichier SuperEmployeeDomainService.cs, ajoutons tout d'abord cette classe :
Puis la méthode dans la classe SuperEmployeeDomainService qui retourne les origines :
Dans notre vue Home.xaml, remplaçons le TextBox par un AutoCompleteBox comme ceci :
Puis ajoutons le code behind pour charger tout ça :
Lançons le tout avec F5 et nous devrions obtenir cela :
II-A. Valider la mise à jour des données▲
Maintenant, nous avons certes une application qui nous permet d'afficher les données de manière sophistiquée, mais les applications professionnelles nécessitent également d'autoriser la mise à jour de données. Voyons comment procéder. Tout d'abord, remplaçons le xaml ci-dessous, sous le DomainDataService. Ceci nous fournira une belle vue Master-detail.
(Vous pouvez télécharger le fichier Home.xaml).
<navigation:Page xmlns:controls
=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:riaControls
=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
x:Class
=
"MonApplication.Home"
xmlns:dataControls
=
"clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data.DataForm.Toolkit"
xmlns:input
=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Input"
xmlns:data
=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Data"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d
=
"http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc
=
"http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:navigation
=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:navControls
=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls.Navigation"
xmlns:activity
=
"clr-namespace:System.Windows.Controls;assembly=ActivityControl"
xmlns:datagroup
=
"clr-namespace:System.Windows.Data;assembly=System.Windows.Ria.Controls"
xmlns:App
=
"clr-namespace:MonApplication.Web"
mc:Ignorable
=
"d"
d:DesignWidth
=
"640"
d:DesignHeight
=
"480"
Title
=
"Home"
NavigationCacheMode
=
"Enabled"
Style
=
"{StaticResource PageStyle}"
>
<Grid x:Name
=
"LayoutRoot"
>
<ScrollViewer x:Name
=
"PageScrollViewer"
Style
=
"{StaticResource PageScrollViewerStyle}"
>
<StackPanel x:Name
=
"ContentStackPanel"
Style
=
"{StaticResource ContentStackPanelStyle}"
>
<riaControls:DomainDataSource x:Name
=
"dds"
AutoLoad
=
"True"
QueryName
=
"GetSuperEmployeesQuery"
LoadSize
=
"20"
>
<riaControls:DomainDataSource.DomainContext>
<App:SuperEmployeeDomainContext/>
</riaControls:DomainDataSource.DomainContext>
<riaControls:DomainDataSource.GroupDescriptors>
<datagroup:GroupDescriptor PropertyPath
=
"Publishers"
/>
</riaControls:DomainDataSource.GroupDescriptors>
<riaControls:DomainDataSource.FilterDescriptors>
<datagroup:FilterDescriptorCollection>
<datagroup:FilterDescriptor PropertyPath
=
"Origin"
Operator
=
"StartsWith"
>
<datagroup:ControlParameter PropertyName
=
"Text"
RefreshEventName
=
"TextChanged"
ControlName
=
"originFilterBox"
>
</datagroup:ControlParameter>
</datagroup:FilterDescriptor>
</datagroup:FilterDescriptorCollection>
</riaControls:DomainDataSource.FilterDescriptors>
</riaControls:DomainDataSource>
<StackPanel Orientation
=
"Horizontal"
Margin
=
"0,5,10,0"
>
<activity:Activity IsActive
=
"{Binding IsBusy, ElementName=dds}"
>
<StackPanel>
<StackPanel Orientation
=
"Horizontal"
Margin
=
"0,0,0,10"
>
<TextBlock Text
=
"Origine: "
></TextBlock>
<input:AutoCompleteBox x:Name
=
"originFilterBox"
Width
=
"75"
Height
=
"30"
ValueMemberBinding
=
"{Binding Name}"
ItemTemplate
=
"{StaticResource OriginsDataTemplate}"
>
</input:AutoCompleteBox>
</StackPanel>
<data:DataGrid x:Name
=
"dataGrid1"
Height
=
"380"
Width
=
"380"
IsReadOnly
=
"True"
AutoGenerateColumns
=
"False"
HorizontalAlignment
=
"Left"
HorizontalScrollBarVisibility
=
"Disabled"
ItemsSource
=
"{Binding Data, ElementName=dds}"
>
<data:DataGrid.Columns>
<data:DataGridTextColumn Header
=
"Nom"
Binding
=
"{Binding Name}"
/>
<data:DataGridTextColumn Header
=
"N° Employee"
Binding
=
"{Binding EmployeeID}"
/>
<data:DataGridTextColumn Header
=
"Origine"
Binding
=
"{Binding Origin}"
/>
</data:DataGrid.Columns>
</data:DataGrid>
<data:DataPager PageSize
=
"10"
Width
=
"380"
HorizontalAlignment
=
"Left"
Source
=
"{Binding Data, ElementName=dds}"
Margin
=
"0,0,2,0"
>
</data:DataPager>
<StackPanel Orientation
=
"Horizontal"
Margin
=
"0,5,0,0"
>
<Button x:Name
=
"SubmitButton"
Content
=
"Envoyer"
Width
=
"105"
Height
=
"28"
Click
=
"SubmitButton_Click"
></Button>
<Button x:Name
=
"AddNewButton"
Content
=
"Ajouter..."
Width
=
"105"
Height
=
"28"
Margin
=
"5,0,0,0"
HorizontalAlignment
=
"Left"
Click
=
"AddNewButton_Click"
></Button>
</StackPanel>
</StackPanel>
</activity:Activity>
<StackPanel Margin
=
"35,95,0,0"
HorizontalAlignment
=
"Left"
VerticalAlignment
=
"Top"
Height
=
"498"
>
<dataControls:DataForm x:Name
=
"DataForm1"
Height
=
"393"
Width
=
"331"
VerticalAlignment
=
"Top"
Header
=
"Détails des employés"
CurrentItem
=
"{Binding SelectedItem,ElementName=dataGrid1}"
HorizontalAlignment
=
"Left"
>
<dataControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataControls:DataField>
<TextBox Text
=
"{Binding Name, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding EmployeeID, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Origin, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Sites, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Gender, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Publishers, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<controls:DatePicker Text
=
"{Binding LastEdit, Mode=OneWay}"
></controls:DatePicker>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Issues, Mode=TwoWay}"
/>
</dataControls:DataField>
</StackPanel>
</DataTemplate>
</dataControls:DataForm.EditTemplate>
</dataControls:DataForm>
</StackPanel>
</StackPanel>
</StackPanel>
</ScrollViewer>
</Grid>
</navigation:Page>
Maintenant, lançons l'application avec F5 et nous obtenons alors ceci :
Si l'on tente de modifier un enregistrement dans la vue détail, nous voyons un astérisque nous signalant que cette entrée a été modifiée et quelle devra être renvoyée au serveur. Si vous éditez une ou plusieurs entrées et que vous annulez ces modifications, l'astérisque disparaitra.
Maintenant, nous devons brancher le bouton « Envoyer ». Pour ce faire, dans votre fichier Home.xaml.cs, ajoutons le délégué suivant :
Tout d'abord, nous devons valider l'élément qui est en cours d'édition, puis nous devons juste envoyer les changements. Ces changements sont regroupés par lot et renvoyés au serveur. Puis, notre méthode Update est appelée. Notez que l'astérisque disparait alors.
Voilà qui est fait, mais qu'en est-il de la validation des données ? De base, nous avons une validation de niveau type (si c'est un entier, une chaine de caractère, etc.). Par exemple, si nous remplissons le champ EmployeeID avec une chaine de caractère, nous obtenons une erreur (NB : pour que cela fonctionne, vous devez lancer votre projet sans débogage, en appuyant sur CTRL-F5).
Maintenant, voyons comment aller un peu plus loin. Pour ce faire, nous allons éditer SuperEmployeeDomainService.metadata.cs coté serveur. Il est important de faire cela coté serveur, ainsi le système effectue toutes les vérifications dans un premier temps pour une expérience utilisateur agréable puis une seconde fois sur le serveur pour vérifier l'intégrité des données. Lorsque notre méthode Update est appelée sur notre DomainService, nous pouvons être sûr que la validation a été faite.
Voici quelles validations nous pouvons faire :
Maintenant, il faut reconstruire la solution pour que ces modifications soient prises en compte. Lançons l'application avec CTRL-F5 et nous voyons que nos règles de validation fonctionnent.
Notons que nous pouvons naviguer entre les erreurs et que le focus se fait sur l'erreur sélectionnée.
Voilà pour ce qui est de la validation des données. Mais, qu'en est-il sur l'ajout de données ?
Pour ce faire, nous allons utiliser la nouvelle fenêtre enfant (ChildWindow) de Silverlight 3.
Effectuons un clic-droit sur View, Add. , New Item. Sélectionnons Silverlight Child Window et nommons-la AddNewWindow.
Lions cette fenêtre à un bouton afin de permettre son affichage. Tout d'abord, ajoutons un bouton au formulaire principal (Home.xaml) pour afficher cette nouvelle fenêtre :
Puis, lions-le en code behind (Home.xaml.cs):
Tout d'abord, ajoutons cette clause Using :
Puis écrivons notre évènement comme suit :
Et voilà le résultat :
Nous voyons que cette nouvelle fenêtre est déjà dotée d'un bouton OK et d'un bouton Cancel qui sont déjà fonctionnels (bien qu'ils ne fassent pas grand-chose). Nous n'avons qu'à ajouter un DataForm. Pour cela, nous n'avons qu'a reprendre le même que nous avons défini pour la mise à jour des données dans notre vue master/detail. (Vous pouvez téléchargez ce fichier ici : AddNewWindow.xaml)
<controls:ChildWindow x:Class
=
"MonApplication.Views.AddNewWindow"
xmlns
=
"http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x
=
"http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls
=
"clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
xmlns:dataControls
=
"clr-namespace:System.Windows.Controls;
assembly=System.Windows.Controls.Data.DataForm.Toolkit"
Title
=
"AddNewWindow"
>
<Grid x:Name
=
"LayoutRoot"
Margin
=
"2"
>
<Grid.RowDefinitions>
<RowDefinition />
<RowDefinition Height
=
"Auto"
/>
</Grid.RowDefinitions>
<dataControls:DataForm x:Name
=
"newEmployeeForm"
Height
=
"393"
Width
=
"331"
VerticalAlignment
=
"Top"
CommandButtonsVisibility
=
"None"
Header
=
"Add New Super Employee"
HorizontalAlignment
=
"Left"
>
<dataControls:DataForm.EditTemplate>
<DataTemplate>
<StackPanel>
<dataControls:DataField>
<TextBox Text
=
"{Binding Name, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding EmployeeID, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Origin, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Sites, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Gender, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Publishers, Mode=TwoWay}"
/>
</dataControls:DataField>
<dataControls:DataField>
<controls:DatePicker Text
=
"{Binding LastEdit, Mode=OneWay}"
></controls:DatePicker>
</dataControls:DataField>
<dataControls:DataField>
<TextBox Text
=
"{Binding Issues, Mode=TwoWay}"
/>
</dataControls:DataField>
</StackPanel>
</DataTemplate>
</dataControls:DataForm.EditTemplate>
</dataControls:DataForm>
<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>
</controls:ChildWindow>
Maintenant, en code behind, nous devons procéder à ces différentes étapes. Tout d'abord, n'omettons pas d'ajouter notre clause Using.
Ajoutons cette déclaration de classe :
Initialisons le constructeur.
Et gérons le bouton OK :
Voilà le résultat :
Bien, maintenant, nous allons répercuter ce changement localement (Home.xaml.cs):
Tout d'abord, nous allons abonner l'évènement AddNewButton_Click à un évènement qui détectera la fermeture de la fenêtre AddNewWindow :
Et nous allons créer ce délégué :
III. Conclusion▲
Et voilà ! Notre application est totalement fonctionnelle. Notez que pour que les changements soient répercutés sur la base de donnée distante, une fois la fenêtre AddNewWindow fermée, nous devons cliquer sur le bouton envoyer.
IV. Liens▲
Article original sur le blog de Brad Abrams
VS2008SP1 (qui comprend Sql Express 2008)
La base de donnée pour cet aricle: northwnd.mdf
Le fichier Home.xaml: Home.xaml
Le fichier AddNewWindow.xaml: AddNewWindow.xaml