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.

Image0.jpg

Effectuons un clic-droit sur MonApplication.web et ajoutons un nouvel item.

Image1.jpg

Allons dans Data et sélectionnons ADO.NET Entity Data Model puis nommons-le Northwind.edmx.

Image2.jpg

Une nouvelle fenêtre apparait nous demandant de choisir quel type de modèle nous désirons. Sélectionnons Generate from database.

Image3.jpg

Dans la fenêtre d'option, dans Data Source, sélectionnons Change et choisissons Microsoft SQL Server Database File.

Image4.jpg
Image5.jpg

Cliquons sur le bouton Browse de Database File Name et sélectionnons la base NORTHWND.MDF.

Image6.jpg
Image7.jpg

Nous pouvons cliquer sur le bouton Test Connection pour nous assurer que tout se passe bien.

Image8.jpg

Cliquons sur OK, et une nouvelle fenêtre apparait. Cliquons alors sur Next.

Image9.jpg

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.

Image10.jpg

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.

Image12.jpg
Image11.jpg

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.

Image13.jpg

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.

Image14.jpg

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.

Image15.jpg
Image16.jpg

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.

Image17.jpg

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 :

Image18.jpg

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.

Image19.jpg

Modifions alors le xaml ainsi ajouté comme suit :

Image20.jpg

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.

Image21.jpg

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.

Image22.jpg

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.

Image23.jpg

Puis, éditons le code nouvellement inséré. Ajoutons en premier lieu l'espace de nom de notre application.

Image24.jpg

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.

Image25.jpg

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.

Image27.jpg

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.

Image28.jpg

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 :

Image29.jpg

Puis, nous ajoutons le code suivant :

Image30.jpg


Et voilà le résultat :

Image31.jpg

Maintenant, ajoutons un filtrage. Tout d'abord, ajoutons un Label et un TextBox.

Image32.jpg

Enfin, ajoutons ces filtres à notre DomainDataSource :

Image33.jpg

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.

Image34.jpg

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 :

Image35.jpg

Puis la méthode dans la classe SuperEmployeeDomainService qui retourne les origines :

Image36.jpg

Dans notre vue Home.xaml, remplaçons le TextBox par un AutoCompleteBox comme ceci :

Image37.jpg

Puis ajoutons le code behind pour charger tout ça :

Image38.jpg


Lançons le tout avec F5 et nous devrions obtenir cela :

Image39.jpg

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).

Home.xaml
Sélectionnez
<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 :

Image40.jpg

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.

Image41.jpg

Maintenant, nous devons brancher le bouton « Envoyer ». Pour ce faire, dans votre fichier Home.xaml.cs, ajoutons le délégué suivant :

Image42.jpg

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).

Image43.jpg

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 :

Image44.jpg

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.

Image45.jpg

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.

Image46.jpg

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 :

Image47.jpg

Puis, lions-le en code behind (Home.xaml.cs):

Tout d'abord, ajoutons cette clause Using :

Image48.jpg

Puis écrivons notre évènement comme suit :

Image49.jpg

Et voilà le résultat :

Image50.jpg

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)

AddNewWindow.xaml
Sélectionnez
<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.

Image52.jpg

Ajoutons cette déclaration  de classe :

Image53.jpg

Initialisons le constructeur.

Image54.jpg

Et gérons le bouton OK :

Image55.jpg

Voilà le résultat :

Image56.jpg


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 :

Image57.jpg

Et nous allons créer ce délégué :

Image58.jpg

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