Comment développer des applications d’entreprises orientées données avec Silverlight 3 : introduction à .NET RIA Services (3/4)

Article mis à jour : juillet 2009 pour la sortie de Silverlight 3 RTW et .NET RIA Services Juillet 2009

Nous avons construit le squelette de notre application dans le 2ème billet. Nous allons ici faire usage de nouveaux contrôles de Silverlight 3 et ajouter une règle de validation coté serveur pour observer comment elle se réplique coté client par la suite.

Utilisation de nouveaux contrôles Silverlight 3

Reprenons notre application. Pour l’instant, vous allez me dire : « c’est bien gentil tout ça mais afficher tous les enregistrements sans pagination, c’est nul ! ». Bon, alors tentons de régler ce différent.

Le contrôle DataSource

Commencez par retirer les quelques lignes de code que nous avions ajoutées dans le code behind : Home.xaml.cs. On va tenter de faire la même chose en pur XAML.

Ajoutez l’utilisation de ces namespaces à votre contrôle :

xmlns:riaControls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Ria.Controls"
xmlns:riaData="clr-namespace:System.Windows.Data;assembly=System.Windows.Ria.Controls"
xmlns:domain="clr-namespace:GestionClientsRIA.Web"

Le dernier est l’équivalent du « using » fait en C#. Insérez ce bloc de XAML au début du StackPanel :

<riaControls:DomainDataSource x:Name="source"
QueryName="GetCustomers"
AutoLoad="True">
<riaControls:DomainDataSource.DomainContext>
<domain:CustomersContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>

On indique ainsi que l’on souhaite appeler la méthode GetCustomers depuis le domaine CustomersContext. Il nous reste maintenant à indiquer au contrôle grille où se trouve sa source de données :

<data:DataGrid x:Name="GrilleClients" ItemsSource="{Binding Data, ElementName=source}" />

On utilise donc ici le binding entre éléments, autre nouveauté déjà abordée dans ce précédent billet. Si on exécute l’ensemble, on a bien un résultat identique au code C# avec la méthodeLoad(contexte.GetCustomersQuery()).

Trions maintenant les données par ordre croissant selon le nom de l’entreprise à laquelle le client appartient. Pour cela, ajoutez ce morceau de XAML juste avant la balise fermante </riaControls :DomainDataSource> :

<riaControls:DomainDataSource.SortDescriptors>
    <riaData:SortDescriptor PropertyPath="CompanyName" Direction="Ascending" />
</riaControls:DomainDataSource.SortDescriptors>

Désormais les enregistrements sont bien triés par ordre alphabétique croissant selon le nom de l’entreprise. Mais je n’ai toujours pas répondu au besoin de pagination. J’y arrive, j’y arrive…

Le contrôle DataPager

Dans le StackPanel où se trouve le contrôle DataGrid, ajoutez ce XAML en dessous de la grille de données :

<data:DataPager PageSize="10" Source="{Binding Data, ElementName=source}" />

On indique ici d’afficher les enregistrements 10 par 10. Relancez l’ensemble :

RIAImage14

Nous avons donc bien la pagination en marche. Par contre, nous chargeons toujours l’ensemble des enregistrements malgré le fait de les visualiser par lot de 10. Il serait donc peut-être plus intéressant de charger les données au fur et à mesure que l’on avance dans la pagination. Pour cela, rien de plus simple ajoutez l’attribut suivant :

LoadSize="20"

à la source après l’attribut AutoLoad="True" par exemple. Bah c’est tout ! Désormais, toutes les 2 pages, un lot de 20 enregistrements supplémentaires seront chargés en mémoire en asynchrone et en tâche de fond sans que vous n’ayez rien eu à faire.

Le contrôle ActivityControl

L’inconvénient de ces chargements asynchrones est que l’utilisateur ne comprends par forcément ce qu’il se passe lorsqu’il lance une action en cliquant et que cela met quelques millisecondes à s’afficher. Il faut donc le faire patienter et lui indiquer qu’une opération est en cours.

Pour cela, récupérez le fichier attaché à ce billet « ActivityControl.zip » contenant l’assembly « ActivityControl.dll ». Ajoutez ensuite une référence à cette assembly dans votre projet Silverlight. Ce contrôle sera probablement directement intégré dans la version finale de .NET RIA Services.

Ce contrôle est maintenant directement présents dans les références d’un projet de type Business Application. Il ne reste donc plus qu’à ajouter cet espace de nom à votre contrôle :

xmlns:activity="clr-namespace:System.Windows.Controls;assembly=ActivityControl"

Puis englobez le StackPanel contenant la grille et le DataPager entre ces 2 balises XAML :

<activity:Activity IsActive="{Binding IsBusy, ElementName=source}" VerticalAlignment="Top" HorizontalAlignment="Left"></activity:Activity>

Désormais, grâce à cela, lorsque l’on arrive sur la 3ème page le chargement des 20 prochains enregistrements est fait pendant qu’une animation « Loading » se lance :

RIAImage15

Groupons les données

Si l’on souhaite grouper les enregistrements par pays par exemple, c’est à nouveau d’une simplicité déconcertante. En dessous du tri par « CompanyName » de notre DomainDataSource, ajoutez ce XAML :

<riaControls:DomainDataSource.GroupDescriptors>
    <riaData:GroupDescriptor PropertyPath="Country" />
</riaControls:DomainDataSource.GroupDescriptors>

Et voici le résultat :

RIAImage16

On peut même fermer les nœuds pour une meilleure visibilité (comme ici Austria).

Gestion de la sauvegarde

Ajoutons d’abord un bouton pour laisser l’utilisateur sauvegarder ses changements. Pour cela, insérer le bout de XAML dans le StackPanel au dessus de la grille :

<Button x:Name="BoutonSauvegarde" Content="Sauvegarder les changements" Click="BoutonSauvegarde_Click" />

Maintenant, il nous faut lancer l’opération de sauvegarde avec ce code C# :

private void BoutonSauvegarde_Click(object sender, System.Windows.RoutedEventArgs e)
{
    if (source.HasChanges)
    {
        source.SubmitChanges();
    }
    else
    {
        MessageBox.Show("Aucun changement detecté.");
    }
}

Notre objet contenant les enregistrements en mémoire maintient en effet une liste des modifications effectuées (ajout, suppression ou modification). On teste donc le booléen HasChanges pour savoir si quelque chose a été changé par l’utilisateur et si c’est le cas on voit les changements au serveur Web via la commande SubmitChanges() pour que ce dernier s’occupe à son tour de mettre à jour la base de données.Utilisation d’une Child Window

Si aucun changement n’a été détecté et que l’utilisateur clique sur le bouton de sauvegarde, une fenêtre externe à Silverlight de style popup JavaScript est levée :

RIAImage17

Ce n’est pas optimal en termes d’expérience utilisateur. On va utiliser une autre nouveauté de Silverlight 3 pour améliorer tout cela. Ajoutez un nouvel élément à votre projet Silverlight de type « Silverlight Child Window » et nommez le « InfoWindow.xaml » :

RIAImage18

Remplacez le XAML proposé par celui-ci :

<controls:ChildWindow x:Class="GestionClientsRIA.InfoWindow"
          xmlns=https://schemas.microsoft.com/winfx/2006/xaml/presentation
          xmlns:x=https://schemas.microsoft.com/winfx/2006/xaml
          xmlns:controls="clr-namespace:System.Windows.Controls;assembly=System.Windows.Controls"
          Width="200" Height="100"
          Title="Information utilisateur">
    <Grid x:Name="LayoutRoot" Margin="2">
        <Grid.RowDefinitions>
            <RowDefinition />
            <RowDefinition Height="Auto" />
        </Grid.RowDefinitions>
        <TextBlock x:Name="InfoToDisplay" Grid.Row="0" />
        <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>

Revenons maintenant dans notre vue HomePage.xaml.cs et utilisez ce code en remplacement du précédent :

private int nombreEntiteesModifiees = 0;

private void BoutonSauvegarde_Click(object sender, System.Windows.RoutedEventArgs e)
{
    if (source.HasChanges)
    {
        nombreEntiteesModifiees = source.DomainContext.Entities.GetChanges().ModifiedEntities.Count();
        source.SubmitChanges();
    }
    else
    {
        InfoWindow infoWindow = new InfoWindow();
        infoWindow.InfoToDisplay.Text = "Aucun changement détecté.";
        infoWindow.Show();
    }
}

private void source_SubmittedChanges(object sender, SubmittedChangesEventArgs e)
{
    InfoWindow infoWindow = new InfoWindow();
    infoWindow.InfoToDisplay.Text = nombreEntiteesModifiees + " modifications effectuées";
    infoWindow.Show();
    nombreEntiteesModifiees = 0;
}

Et ajoutez ce « using » :

using System.Windows.Ria.Data;

Enfin ajoutez l’abonnement à l’évènement SubmittedChanges au niveau de la source dans le XAML :

SubmittedChanges="source_SubmittedChanges"

On a désormais une expérience utilisateur plus « intégrée » :

RIAImage19

Et on a surtout la main sur le design de la fenêtre affichée. J’aurais pu y mettre de la vidéo, une belle image, etc.Mise en place d’une règle de validation

Maintenant que nous laissons l’utilisateur sauvegarder les données, il serait peut-être bon d’ajouter un peu de validation de ses saisies. Retournons dans l’application ASP.NET et plus précisément dans le fichier « CustomersService.metadata.cs » qui fut généré automatiquement grâce à la case « Generate associated classes for metadata ».

Rendez-vous sur le champ Country (Pays). Nous allons spécifier une expression régulière sur ce champ indiquant que les valeurs autorisées ne peuvent qu’être des caractères alphabétiques commençant par une majuscule. Cela nous donne le résultat suivant :

[RegularExpression("[A-Z][A-Za-z]*", ErrorMessage="Les caractères non alphabétiques ne sont pas autorisés")]
public string Country;

On indique également le message d’erreur a affiché si l’utilisateur s’égard à vouloir insérer des caractères étranges pour saisir le nom du pays. Compilez l’ensemble et testez la solution.

RIAImage20

Si vous tentez alors de modifier un nom de pays en ajoutant un chiffre, le message d’erreur de validation est automatiquement levé. Aucun aller/retour serveur n’a été nécessaire pour cela, la logique est bien embarquée coté client Silverlight. Il suffit d’aller le voir dans le proxy client généré :

[DataMember()]
[RegularExpression("[A-Z][A-Za-z]*", ErrorMessage="Les caractères non alphabétiques ne sont pas autorisés")]
[StringLength(15)]
public string Country
{
    get
    {
        return this._country;
    }
    set
    {
        if ((this._country != value))
        {
            this.ValidateProperty("Country", value);
            this.OnCountryChanging(value);
            this.RaiseDataMemberChanging("Country");
            this._country = value;
            this.RaiseDataMemberChanged("Country");
            this.OnCountryChanged();
        }
    }
}

Un ensemble d’attributs peuvent ainsi être spécifiés sur les champs de vos objets métiers couvrant les scénarios les plus communs. Vous pouvez également implémenter votre propre logique de validation avec une classe de type « Shared » qui sera automatiquement répliquée coté client.

Concluons et observons le résultat final en action en vidéo dans le 4ème et dernier billet.