Silverlight 4 + RIA Services - Prêt pour les affaires

| Brad Abrams | Deepin Prayag (Traduction) |

Traducteur

Deepin

Ces articles font partie d'une série de traductions d'articles de Brad Abrams sur le développement d'applications métier avec Silverlight 4 et .NET RIA Services.

Cette série se concentre uniquement sur la base des applications métier : l'interrogation, la mise à jour, la validation et la sécurisation de vos données métier importantes.

Elle sera également utilisée pour mettre à jour certains billets de la série Silverlight 3.

N'hésitez pas à commenter cette série d'articles : Commentez

- Titre : Silverlight 4 + RIA Services - Prêt pour les affaires

- Auteurs : Brad Abrams, Deepin Prayag (Traduction)

- Traducteur : Deepin

- Parution : 1 mars 2012

- Licence : Copyright ® 2012 Brad Abrams. Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.

Sommaire

- Introduction générale

- Prérequis

- Traduction

- Démarrer un nouveau projet avec le modèle d'application métier

- Conclusion

- Traduction

- Exposer les données d'Entity Framework

- Conclusion

- Traduction

- Consommer des données dans le client Silverlight

- Conclusion

- Traduction

- Mettre à jour les données dans le client

- Conclusion

- Traduction

- Valider les données

- Conclusion

- Traduction

- Authentification et personnalisation

- Conclusion

- Traduction

- Optimisation pour les moteurs de recherche (SEO)

- Étape 1 : faire lier en profondeur le contenu important pour l'application

- Étape 2 : mettre les moteurs de recherche au courant de tous ces liens profonds avec un sitemap

- Étape 3 : fournir une version de « bas niveau » du contenu important

- Résumé

- Conclusion

- Traduction

- Exposer des services OData

- Conclusion

- Traduction

- Exposer des services WCF (SOAP/WSDL)

- Conclusion

- Traduction

- Ajax Endpoint

- Conclusion

- Traduction

- Localiser les applications métier

- Conclusion

- Liens

- Remerciements

Introduction générale

Cette série d'articles est la traduction de 'Silverlight 4 + RIA Services - Ready for Business' de Brad Abrams.

Brad Abrams a voulu présenter, par le biais de cette série, quelques unes des fonctionnalités clés et des outils qui font de Silverlight une excellente plateforme pour la construction d'applications métier.

Prérequis

La procédure pas à pas requiert :

- Visual Studio 2010 (ou la version express gratuite) ;

- Silverlight 4 Tools (inclut RIA Services).

Vous pouvez télécharger l'application complète.

J'ai implémenté cela avec Silverlight 4 RC, mais cela devrait fonctionner avec Silverlight 4 RTM.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Starting a New Project with the Business Application Template

Démarrer un nouveau projet avec le modèle d'application métier

Pour lancer notre série, je voulais me concentrer sur notre objectif : vous aider à vous concentrer sur votre métier, et non pas sur du code de plomberie. Cela est visible dès le modèle d'application métier. Il décrit une structure d'application normative, est visuellement agréable et est facilement personnalisable.

Après avoir correctement installé Silverlight 4 pour les développeurs (qui comprend RIA Services), vous aurez quelques nouveaux projets dans la section Silverlight. Nous allons nous concentrer sur le modèle d'application métier (Business Application Template).

Remarquez que cela crée une solution unique avec deux projets. BusinessApplication1 est la partie cliente de l'application et BusinessApplication2 est la partie serveur. Ce sont des projets connexes qui se connaissent l'un l'autre.

Et par défaut, vous obtenez la structure pour une application visuellement agréable, et professionnelle.

L'application de démarrage est entièrement localisable et personnalisable. Par exemple, je veux changer le nom de l'application de « Application Name » à « My Foodie Explorer » : cela se fait facilement dans le répertoire Assets\Resources en éditant le fichier ApplicationStrings.resx.

Jetez un coup d'œil à la procédure pas à pas plus approfondie, elle est un peu ancienne, mais toujours valide et Tim Heuer a récemment posté SNEAK PEEK: New Silverlight application themes tels que :

Profitez-en !

Conclusion

Ceci conclut la première partie de cette série. Dans la deuxième partie nous verrons comment exposer vos données du coté serveur de votre application.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Exposing Data from Entity Framework

Exposer les données d'Entity Framework

Pour continuer notre série, j'ai voulu ensuite regarder la façon d'exposer vos données du côté serveur de votre application.

Les données intéressantes dans vos applications métier proviennent d'une grande variété de sources de données. D'une base de données SQL, d'Oracle DB, de SQL Azure, de SharePoint ou d'un mainframe et vous avez probablement déjà choisi un modèle de données tel que NHibernate, Linq2Sql, Entity Framework, une procédure stockée ou encore un service. Le but de RIA Services dans ce release est de faciliter le travail avec les données d'une ou de plusieurs sources, d'une manière transparente, à partir d'une application Silverlight. Cette procédure pas à pas utilisera Entity Framework pour accéder à la base de données SQL Express, mais le concept de base s'applique à n'importe quelle source de données.

Ajoutez DishView.mdf à BusinessApplication1.Web\App_Data - Bien entendu, dans un exemple réel vous devriez juste avoir une chaîne de connexion à une base de données existante.

Ensuite, créez un modèle Entity Framework au-dessus de celui-ci.

Comme vous pouvez le voir, nous avons un jeu d'entités très simple. Chaque Restaurant peut avoir n'importe quel nombre de Plats.

Ensuite, nous avons besoin de définir une certaine logique métier qui contrôlera et façonnera les données telles qu'elles sont perçues par le client Silverlight. Pour ce faire, nous ajoutons un nouveau DomainService. Un DomainService est simplement un type particulier de service WCF qui rend BEAUCOUP PLUS facile d'interroger, de mettre à jour, de sécuriser et de valider vos données. Mais si vous êtes un expert WCF et connaissez tous les détails pour la configuration d'un service WCF, vous pouvez certainement personnaliser ce service, d'une façon à ce qu'il corresponde exactement à vos besoins. Bien sûr, dans 90% des cas, nous espérons que vous n'aurez pas besoin d'y avoir recours.

Ensuite, il nous est donné la possibilité de pré-remplir le DomainService.

Dans ce cas, nous allons exposer Plat et Restaurant, mais seul Plat pourra être mis à jour. Nous allons également générer une classe de métadonnées pour accrocher les attributs de validation afin que vous puissiez régénérer le modèle EF sans perdre aucune personnalisation. Nous obtenons une classe de démarrage DishViewDomainService. Je l'ai mise à jour aux lignes 8-9 avec un peu de logique métier. Examinons chaque ligne pas à pas...

1:     [EnableClientAccess]
2:     public class DishViewDomainService : LinqToEntitiesDomainService<DishViewEntities>
3:     {
4:
5:         public IQueryable<Restaurant> GetRestaurants()
6:         {
7:             return this.ObjectContext.Restaurants
8:                 .Where (r=>r.Region != "NC")
9:                 .OrderBy(r=>r.ID);
10:         }
11:

Ligne 1 : Cet attribut marque le DomainService comme étant accessible via http. Sans lui le service ne peut être appelé que du processus. Ceci est utile dans le scénario ASP.NET Webforms / Dynamic Data.

Ligne 2 : Cette classe est dérivée de LinqToEntitiesDomainService. C'est une classe utilitaire qui fournit quelques helpers pour travailler avec EF. Le vrai travail est effectué par la classe de base DomainService, celle-ci étant la classe à partir de laquelle vous dérivez pour POCO et d'autres scénarios personnalisés.

Ligne 5 : Remarquez que nous renvoyons un IQueryable de notre type DAL Restaurant. IQueryable est l'interface sur laquelle est construite LINQ. Cela offre des fonctionnalités de requêtage supplémentaires au client depuis la couche de données, et ce en prenant en compte votre logique métier personnalisée : possibilité de trier, filtrer, paginer, etc. Cela signifie qu'aucune donnée supplémentaire n'est aspirée dans la couche intermédiaire et le client, mais vous n'avez pas à tordre votre logique métier pour faire face à cela.

Lignes 8-9 : Nous avons une logique métier qui élimine tout Restaurant en Caroline du Nord et met les résultats dans un ordre prédéfini. Vous pouvez, bien sûr, imaginer une logique métier plus intéressante.

Conclusion

Ceci conclut la deuxième partie de cette série. Dans la troisième partie nous verrons comment consommer les données dans le client Silverlight.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Consuming Data in the Silverlight Client

Consommer des données dans le client Silverlight

Pour continuer notre série, voyons d'où vient le plaisir quand je regarde la facilité avec laquelle on peut consommer du client. D'abord, juste pour vous aider à comprendre ce qui se passe derrière la porte, jetons un coup d'œil à une solution code-behind. Dans View\Home.xaml mettez un simple DataGrid sur le formulaire.

<sdk:DataGrid Name="dataGrid1" Height="152" Width="692" />

Ensuite ajoutez ces lignes de code à Home.xaml.cs

1: var context = new DishViewDomainContext();
2: this.dataGrid1.ItemsSource = context.Restaurants;
3:
4: context.Load(context.GetRestaurantsQuery());
5:

À la ligne 1, nous créons un DishViewDomainContext. Remarquez que cela est automatiquement généré (via une tache MSBuild) à partir du DishViewDomainService sur le serveur.

À la ligne 2, vous pouvez remarquer que nous avons une propriété Restaurants - nous fournissons cette propriété car il y a au moins une méthode de requête qui renvoie les restaurants. Remarquez l'effet magique de la liaison de données. Nous n'avons pas encore rempli ces données à partir du serveur, mais nous allons et les lionsde l'avant lier ces données. Cela évite toute logique de rappel complexe.

À la ligne 4, nous chargeons explicitement les données. C'est l'appel du réseau, donc nous voulons être sûrs que ce soit clair pour le développeur quand cela arrive. Comme argument, nous passons la méthode de requête exacte (et tous les arguments) à utiliser.

Maintenant nous l'exécutons et obtenons nos données.

Sans grands efforts, mais cela devient encore plus facile ;-)

Allez-y et effacez ce code et le XAML. Ensuite prêtez attention à la fenêtre DataSources

Remarquez que ceci est simplement une représentation visuelle exacte de ce que nous faisions dans le code.

Remarquez que je peux modifier l'interface utilisateur par défaut à générer et quelle méthode de requête utiliser.

Faites glisser Restaurants sur le formulaire et bingo, nous avons nos données.

Exécutez-le et nous avons nos données. Qu'est ce qui pourrait être plus facile ?

Cliquez sur les entêtes de colonne. Remarquez que le tri fonctionne ! Vous n'aviez pas à écrire de code sur le client ou le serveur pour l'activer. Cela survient gratuitement en retournant un IQueryable.

Maintenant ajoutons la pagination...

Tout d'abord nous glissons et déposons le contrôle DataPager sur le formulaire, ensuite nous faisons un peu de mise en page et nous nous retrouvons avec quelque chose qui semble pas mal.

Mais il nous faut encore relier le DataPager au même DataSource sous-jacent. Cela est assez facilement effectué en glissant et déposant le même élément Restaurants de la fenêtre DataSources sur le DataPager. Vous saurez que vous l'avez bien fait si le DataPager est activé.

Remarquez que la pagination fonctionne à merveille. Encore une fois, pas de code nécessaire sur le client ou le serveur ; tout est fait en utilisant la simple requête LINQ que nous avons écrite sur le serveur. Mais qu'en est-il de la pagination ET du tri. Fonctionnent-ils bien ensemble ? Triera-t-il seulement la page de données qui est chargée localement ? La réponse, bien sûr, est que le tri et la pagination fonctionnent très bien ensemble et que le tri est en fait envoyé à la couche de données et exécuté là-bas d'une manière très efficace.

Maintenant ajoutons le filtrage. Avec un peu plus de travail dans l'interface utilisateur, nous pouvons ajouter un moyen de filtrer par code postal.

<sdk:Label Name="label1" Content="Postal Code:" FontSize="14" Margin="0,0,462,13" />
<TextBox Height="23" HorizontalAlignment="Left" Margin="110,12,0,0" Name="postalCodeText

Ensuite nous devons relier cela à la requête. Bien sur, nous voulons que le filtre soit appliqué au plus près possible des données. Nous ne voulons pas télécharger toutes les entités localement pour ensuite les filtrer. Nous ne voulons pas non plus toutes les tirer vers la couche intermédiaire et ensuite les filtrer. Ce que nous voulons, c'est que le filtre soit appliqué au niveau même de la base de données. Heureusement, cela est très facile à faire avec la composition LINQ.

D'abord nous sélectionnons le DomainDataSource dans la vue Document Outline.

Operator - typiquement vous devriez définir cela à « Contains ». Si vous utilisez le « IsEqualTo » par défaut, le premier chargement (quand il n'y a pas de filtre) se traduira par aucun résultat renvoyé.

PropertyPath - ceci est la propriété de l'entité sur laquelle vous filtrez. Tapez seulement le nom simple.

Value - c'est là où on va obtenir la valeur avec laquelle comparer. C'est plus facile de faire une liaison Interface Utilisateur-à-Interface Utilisateur à la propriété Text du TextBox.

Voici la boîte de dialogue une fois que nous avons fini :

Et le XAML résultant :

<riaControls:DomainDataSource Name="restaurantDomainDataSource" AutoLoad="True"
d:DesignData="{d:DesignInstance my:Restaurant,
CreateList=true}"
Height="0"  Width="0"
LoadedData="restaurantDomainDataSource_LoadedData_1"
QueryName="GetRestaurantsQuery">
<riaControls:DomainDataSource.FilterDescriptors>
<riaControls:FilterDescriptor Operator="Contains"
PropertyPath="PostalCode"
Value="{Binding ElementName=postalCodeTextbox, Path=Text}" />
</riaControls:DomainDataSource.FilterDescriptors>
<riaControls:DomainDataSource.DomainContext>
<my:DishViewDomainContext />
</riaControls:DomainDataSource.DomainContext>
</riaControls:DomainDataSource>

Remarquez qu'aucun changement particulier n'a été effectué, ni au niveau du code ni à celui de la logique métier dans le DomainService.

Conclusion

Ceci conclut la troisième partie de cette série. Dans la quatrième partie nous verrons comment mettre à jour les données dans le client Silverlight.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Updating Data in the Client

Mettre à jour les données dans le client

Pour continuer notre série, jetons un coup d'œil à la mise à jour de données. J'ai créé une page Plates.xaml avec une structure très similaire à celle du dessus. Pour les détails sur la création de cette page, regardez ma procédure pas à pas de démonstration lors du Professional Developers Conference de 2009 (PDC '09).

Maintenant, regardons la mise à jour des données Plate.

D'abord nous allons créer une interface utilisateur « formulaire » par défaut en glissant une entité de la fenêtre DataSources, en grande partie de la même façon que nous l'avions fait précédemment.

Mais avant de créer l'interface utilisateur, remarquez l'ordre des champs - il correspond à l'ordre dans lequel ils vont être générés dans l'interface utilisateur. Ils sont dans l'ordre alphabétique, mais ce n'est pas toujours ce que vous voulez. Par exemple, je pense que Name devrait arriver en premier.

Pour y remédier, dans le projet serveur, ouvrez le fichier DishViewService.metadata.cs et ajoutez l'attribut Display au champ Name de la classe Plate. Pendant que nous y sommes, « Number Updates » n'est pas une si bonne appellation à utiliser dans l'interface utilisateur, changeons donc cela pour quelque chose de plus lisible.

[Display(Order = 0)]
public string Name { get; set; }
[Display(Name="Update Count")]
public Nullable<int> NumberUpdates { get; set; }

Maintenant, en revenant au projet client, nous voyons Name au-dessus

et quand nous le déposons sur le formulaire, nous voyons Update Count.

Maintenant nous avons un formulaire, et il est déjà relié à la même source de données que la grille. Ajoutons un bouton « Save » au formulaire pour enregistrer les modifications.

Et relions sa propriété Command (celle-ci est appelée quand le bouton est pressé).

Maintenant nous devons relier cela à la commande Submit sur le DomainDataSource. Donc nous utilisons la liaison élément-à-élément. Sélectionnez le plateDomainDataSource

Ensuite sélectionnez le chemin au SubmitChangesCommand

Vous savez que vous avez le droit d'enregistrer les modifications , si le bouton est désactivé (même sur la surface de conception... après tout, il n'y a aucun changement à soumettre, n'est-ce pas ?)

Maintenant, exécutez-le et voyez les résultats

Remarquez que quand vous changez de sélection dans la grille, l'interface utilisateur contenant les détails est automatiquement mise à jour, pas de code nécessaire. Aussi, remarquez que le bouton « Save » est désactivé jusqu'à ce qu'il y ait des modifications à soumettre au serveur. Essayez d'ajouter un bouton « Cancel » afin d'enlever les changements locaux. Il est très facile de relier au RejectChangesCommand de la même manière.

Maintenant voyons comment réellement mettre à jour les données dans la base. Définissez un point d'arrêt dans la méthode UpdatePlate

Remarquez que cette méthode de mise à jour est appelée deux fois : une fois pour chaque entité modifiée sur le client.

Maintenant, ajoutons un peu plus de logique à la mise à jour afin de gérer la logique métier.

1:         public void UpdatePlate(Plate currentPlate)
2:         {
3:             currentPlate.NumberUpdates++;
4:
5:             var orginal = this.ChangeSet.GetOriginal(currentPlate);
6:
7:             if (orginal.Price != currentPlate.Price)
8:             {
9:                 // add 1 dollar fee for changing price
10:                 currentPlate.Price += 1;
11:             }
12:         }

Premièrement, à la ligne 3, nous mettons à jour le compteur NumberUpdates, ensuite si le prix de cet élément de menu change, nous ajoutons une redevance d'un dollar. Remarquez que nous pouvons facilement vérifier si cette valeur est modifiée du point de vue de ce client en utilisant sa valeur d'origine... Le client envoie au serveur la valeur d'origine, qu'il avait obtenu à partir du serveur quand il a été chargé la première fois, pour n'importe quel champ qui a changé, est une clef, ou est marqué comme un jeton d'accès concurrentiel. (Ceci est une petite modification par rapport à la version précédente où *toutes* les valeurs d'origine étaient renvoyées au serveur. Par exemple, sur une mise à jour qui ne change que le décompte de Calorie, en inspectant la valeur d'origine, nous voyons :

Si vous vouliez inclure Price sur n'importe quelle mise à jour, vous devriez le marquer comme un jeton d'accès concurrentiel. Pour marquer un champ comme un jeton d'accès concurrentiel dans Entity Framework, sélectionnez le champ dans le concepteur et changez le Concurrency Mode à « fixed »

Avec ce changement, la valeur d'origine pour le prix sera toujours envoyée au serveur. Cela ne coûte qu'un tout petit peu plus cher pour la plupart des champs, donc je recommande de le faire chaque fois que la logique métier l'exige.

Conclusion

Ceci conclut la quatrième partie de cette série. Dans la cinquième partie nous verrons comment valider les données.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Validating Data

Valider les données

Pour continuer notre série, regardons la validation de données de nos applications métier. C'est bien de mettre à jour les données, mais quand vous activez la mise à jour de données, vous devez souvent vérifier les données pour vous assurer qu'elles soient valides. RIA Services propose une façon propre et clairement définie de gérer cela. D'abord, voyons ce que vous obtenez gratuitement. La valeur pour n'importe quel champ entré doit être valide pour le domaine de ce type de données. Par exemple, vous n'avez jamais besoin d'écrire du code pour vous assurer que quelqu'un n'a pas tapé « quarante-deux » dans une zone de texte liée à un champ de type int. Vous bénéficiez également d'une belle présentation de la validation, qui se comporte bien, dans l'interface utilisateur.

Note : si vous ne voyez pas cela, assurez-vous que « ValidatesOnExceptions=True » est dans l'expression de liaison pour chaque champ.

Bien sûr, ce type de validation a une limite : dans une application réelle, vous avez besoin de validation plus étendue. Et pour cette validation vous devez absolument vérifier la validation avant que votre logique métier ne soit lancée car vous ne savez pas quel client pourrait être en train de vous transmettre la mise à jour, et en plus, vous voulez vérifier la validation sur le client afin d'offrir à l'utilisateur une expérience utilisateur très agréable et réduire le nombre de cas d'erreur de visite sur votre serveur, ce qui réduit la charge du serveur. Dans les modèles traditionnels, vous devez effectuer le contrôle des erreurs deux fois pour couvrir ces deux cas. Mais cela est de toute évidence sujet à l'erreur et peut facilement se désynchroniser. RIA Services offre donc un modèle commun pour la validation.

Les cas les plus communs sont couverts par un ensemble d'attributs personnalisés que vous appliquez à votre modèle sur le serveur. Ces attributs sont communs à l'ensemble du framework .NET et pris en charge par ASP.NET Dynamic Data, ASP.NET MVC et RIA Services. Vous pouvez trouver l'ensemble complet dans System.ComponentModel.DataAnnotations. Mais pour vous donner un avant-goût :

[Display(Order = 0)]
[Required(ErrorMessage = "Please provide a name")]
public string Name { get; set; }
[Range(0, 999)]
public Nullable<decimal> Price { get; set; }
[RegularExpression(@"^http\://[a-zA-Z0-9\-\.]+\.[a-zA-Z]{2,3}(/\S*)?$",
ErrorMessage = "Please use standard Url format")]
public string Uri { get; set; }

Comme vous pouvez le voir ci-dessus, les validations sur le client sont gérées automatiquement, mais elles sont également exécutées à nouveau sur le serveur AVANT que votre méthode Update ne soit appelée. Cela vous permet de garder le cambouis de la validation hors de votre logique métier pour la mise à jour. L'autre atout est que ces validations s'appliquent exactement de la même façon quel que soit l'endroit où est utilisée cette entité dans l'interface utilisateur, car elles sont intégrées dans le modèle. Vous pouvez bien sûr localiser les messages d'erreurs en passant un identifiant de ressource plutôt qu'une chaîne codée en dur. Vous pouvez également lire ces métadonnées de validation à partir d'un fichier de configuration externe ou d'une base de données plutôt que d'utiliser des attributs dans le code.

Mais il est clair que cela ne couvre pas tous les cas. Souvent vous devez écrire un peu de code de procédure réelle. Prenons un exemple de validation de la description pour s'assurer qu'elle est vraiment complète. Il n'y a pas d'attribut personnalisé pour ça ;-). Donc, écrivons un peu de code C#. Nous devons d'abord indiquer que le champ Description a une certaine validation personnalisée.

[CustomValidation(typeof(PlateValidationRules),
"IsDescriptionValid")]
public string Description { get; set; }

Ensuite vous pouvez facilement créer cette classe et implémenter la méthode IsDescriptionValid.

1:         public static ValidationResult IsDescriptionValid(string description)
2:         {
3:             if (description != null && description.Split().Length < 5)
4:             {
5:                 var vr = new ValidationResult("Valid descriptions must have 5 or more words.",
6:                     new string[] { "Description" });
7:                 return vr;
8:             }
9:
10:             return ValidationResult.Success;
11:         }
12:

Remarquez qu'à la ligne 1, la signature de la méthode doit renvoyer un ValidationResult - c'est une classe de DataAnnotations qui contient des informations sur les erreurs de validation. La méthode doit aussi prendre un paramètre qui est du même type que le champ auquel cette méthode est en train d'être appliquée. Vous pourriez le faire au niveau entité pour effectuer une validation de champ croisée.

Ensuite, à la ligne 3, j'implémente un algorithme bidon pour déterminer si la description est valide ou non.

Aux lignes 5 et 6, je renvoie une erreur et j'indique le champ auquel cela s'applique.

Maintenant, exécutez l'application. Vous verrez que nous pouvons éditer la description et appuyer sur la touche de tabulation sans erreur, mais si nous soumettons, nous obtenons à ce moment-là une erreur exactement de la même manière que nous avons vue auparavant. Remarquez que j'aurais pu envoyer plusieurs entités et chacune d'entre elle pourrait comporter des erreurs. RIA Services s'adapte par rapport à chacune d'entre elles (nous vous donnons même une liste) et pendant que l'utilisateur édite, nous affichons une interface utilisateur comme celle-ci.

Note : si à la place vous voyez ce genre de dialogue :

Cela signifie probablement que vous devez écrire un gestionnaire pour SubmitChanges sur votre DomainDataSource.

<riaControls:DomainDataSource  SubmittedChanges="plateDomainDataSource_SubmittedChanges"

private void plateDomainDataSource_SubmittedChanges(object sender, SubmittedChangesEventArgs e)
{
if (e.HasError &&
e.EntitiesInError.All(t => t.HasValidationErrors))
{
e.MarkErrorAsHandled();
}
else
{
System.Windows.MessageBox.Show(e.Error.ToString(), "Load Error", System.Windows.MessageBoxButton.OK);
e.MarkErrorAsHandled();
}
}

Ça c'est très cool parce que nous avons toute la puissance de .NET pour écrire nos règles de validation. Mais l'inconvénient est que je ne reçois la validation qu'une fois le changement soumis. Ce que je voudrais vraiment faire dans certains cas, c'est d'écrire une certaine logique de validation personnalisée et qu'elle soit exécutée à la fois sur le serveur ET le client. Heureusement, nous avons la puissance de. NET sur le client et le serveur afin que nous puissions utiliser la validation du code partagé. Pour activer cela il suffit de changer le nom du fichier contenant la règle de validation pour inclure le suffixe « .shared.cs ». Ce changement entraîne la logique RIA Services MSBuild à compiler le fichier pour le client et le serveur.

Maintenant, exactement le même code sera exécuté sur le client et le serveur. Donc, s'il y a une mauvaise description, nous n'avons plus besoin de faire un aller-retour vers le serveur pour y remédier.

Bien sûr, dans un cas réel, vous êtes susceptible d'avoir des règles de validation qui ne s'exécutent que sur le serveur, et d'autres qui sont partagées ; RIA Services prend aussi en charge ce scenario. Définissez simplement les règles de validation partagée dans les fichiers .shared.cs et toute règle de validation serveur-uniquement dans un autre fichier.

Conclusion

Ceci conclut la cinquième partie de cette série. Dans la sixième partie nous aborderons l'authentification et la personnalisation.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Authentication and Personalization

Authentification et personnalisation

Pour continuer notre série, dans les applications métier réelles nos données sont souvent très précieuses et donc, nous devons savoir qui accède à quelles données et nous devons donner accès à certaines données uniquement aux utilisateurs privilégiés. Heureusement cela est très facile à faire avec RIA Services. Par exemple, disons que nous voulons que seuls les utilisateurs authentifiés aient accès à nos données dans cet exemple. C'est aussi facile à réaliser que l'ajout d'un attribut, voir la ligne 2 ci-dessous.

1:     [EnableClientAccess]
2:     [RequiresAuthentication]
3:     public class DishViewDomainService : LinqToEntitiesDomainService<DishViewEntities>
4:     {
5:

Quand on exécute l'application, nous obtenons maintenant une erreur. Vous pouvez clairement faire un peu mieux d'un point de vue utilisateur. Mais le message est assez clair.

Remarquez qu'il y a une option de login, afin que nous puissions nous connecter.

Et même créer un nouvel utilisateur.

Et avec une actualisation, nous obtenons désormais nos données.

Et l'application sait qui je suis sur le client et me donne une façon de me déconnecter.

Maintenant vous pouvez également interagir facilement avec l'utilisateur actuel sur le serveur. Donc, par exemple, renvoyez uniquement les dossiers qu'ils ont édités, ou dans ce cas présent, enregistrez tous les accès :

1:         public IQueryable<Restaurant> GetRestaurants()
2:         {
3:             File.AppendAllLines(@"C:\Users\brada\Desktop\log.txt", new string[] {
4:                 String.Format("{0}:{1}", DateTime.Now,
5:                 this.ServiceContext.User.Identity.Name)});
6:             return this.ObjectContext.Restaurants
7:                 .Where (r=>r.Region != "NC")
8:                 .OrderBy(r=>r.ID);
9:         }
10:

La ligne 5 est la plus importante. Nous sommes en train d'accéder aux utilisateurs actuels sur le serveur. Cela nous donne un journal simple et agréable.

3/7/2010 9:42:57 PM:darb
3/7/2010 9:43:05 PM:darb

Maintenant nous pouvons également personnaliser un peu tout cela. Disons que nous voulons que nos utilisateurs soient capables de nous donner une couleur préférée et nous gardons une trace de cela sur le serveur et le client, de sorte que cela fonctionne en toute transparence depuis n'importe quelle machine.

Tout d'abord, nous devons ajouter BackgroundColor à notre emplacement de sauvegarde. Dans ce cas j'utilise le stockage de profil ASP.NET, donc j'ajoute les éléments appropriés au web.config

Ensuite je dois y accéder depuis le client Silverlight, donc j'ajoute une propriété à l'instance User dans le Models\User.cs

public partial class User : UserBase
{
public string FriendlyName { get; set; }
public string BackgroundColor { get; set; }
}

Finalement, nous devons y accéder sur le client. Dans main.xaml ajoutez les lignes 2 et 3 :

1:   <Grid x:Name="LayoutRoot" Style="{StaticResource LayoutRootGridStyle}"
2:         Background="{Binding Path=User.BackgroundColor}"
3:         DataContext="{StaticResource WebContext}">
4:
5:

Exécutez-le et nous obtenons notre belle couleur de fond par défaut !

Maintenant, ça c'est bien, mais ce serait encore mieux de donner à l'utilisateur la possibilité d'éditer ses paramètres. Donc, dans About.xaml, nous utilisons un modèle très similaire à celui ci-dessus.

<Grid x:Name="LayoutRoot"
DataContext="{StaticResource WebContext}">

et

<sdk:Label Content="Background Color:" />
<TextBox Text="{Binding Path=User.BackgroundColor, Mode=TwoWay}" Height="23" />

Ensuite reliez un bouton « Save »

private void button1_Click(object sender, System.Windows.RoutedEventArgs e)
{
WebContext.Current.Authentication.SaveUser(false);
}

Et ça fonctionne !

Et ce qui est mieux c'est que si vous l'exécutez à partir d'un autre navigateur, sur une autre machine, une fois que vous vous connectez vous obtenez exactement les mêmes préférences !

Conclusion

Ceci conclut la sixième partie de cette série. Dans la septième partie nous aborderons l'optimisation pour les moteurs de recherche (SEO).

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Search Engine Optimization (SEO).

Optimisation pour les moteurs de recherche (SEO)

Pour continuer notre série, jetons un œil à SEO et Silverlight. La grande majorité du trafic Web est dictée par la recherche. Les moteurs de recherche sont la première étape pour beaucoup d'utilisateurs de l'internet public et cela augmente aussi dans les environnements professionnels. La recherche est aussi la technologie-clé qui entraîne la plus grande partie des revenus publicitaires. Donc, inutile de le préciser, le SEO est important. Mais comment fonctionne le SEO dans une application Silverlight où la plupart du contenu intéressant est généré dynamiquement ? Je vous présenterai un modèle d'application pour faire du SEO dans une application Silverlight avec un minimum de travail additionnel.

Il y a trois étapes faciles et amusantes pour rendre votre application Silverlight « SEO friendly ».

- Étape 1 : faire lier en profondeur le contenu important pour l'application ;

- Étape 2 : mettre les moteurs de recherche au courant de tous ces liens profonds avec un sitemap ;

- Étape 3 : fournir une version de « bas niveau » du contenu important.

Perçons chacun de ces domaines à travers un exemple. Je vais utiliser ma démo PDC2009 « Foodie Explorer » comme point de départ. Vous devriez envisager de lire mon précédent état des lieux  (PDC09 Talk: Building Amazing Business Applications with Silverlight 4, RIA Services and Visual Studio 2010) pour avoir un certain aperçu avant qu'on ne commence.

Télécharger l'exemple complet.

Étape 1 : faire lier en profondeur le contenu important pour l'application

Tout contenu sur votre site dont vous souhaitez qu'il puisse faire l'objet d'une recherche individuelle doit être accessible via une URL. Si je veux que vous soyez en mesure d'utiliser Bing (ou Google, ou autre) pour « Country Fried Steak » et atterrir sur ma page listant des photos de Country Fried Steak, j'ai besoin de proposer une URL qui vous renvoie exactement à ce contenu.

http://foo.com/foodieExplorer.aspx n'est pas assez bien, je dois proposer une URL telle que http://foo.com/foodieExplorer.aspx?food="Country Fried Steak" . Notez qu'il y a également d'autres gros avantages à cette technique. Par exemple, les utilisateurs peuvent tweeter, envoyer par courriel ou par messagerie instantanée ces adresses URL pour discuter du contenu particulier de votre application.

Heureusement, avec la fonctionnalité de navigation de Silverlight, c'est très facile d'ajouter le support pour les liens profonds. Jetons un œil sur comment faire cela dans une application Silverlight.

Ce que nous voulons faire c'est fournir une URL qui permet d'identifier un certain restaurant ou un restaurant et un plat en particulier. Pour le SEO ainsi qu'une meilleure lisibilité pour l'homme, nous voudrions l'URL dans un format tel que http://www.hanselman.com/abrams/restaurant/25/plate/4, pour indiquer que ceci est le restaurant=25 et le plat=4. Pour ce faire, définissons les itinéraires dans le projet Web dans global.asax.

1:     public class Global : HttpApplication
2:     {
3:         void Application_Start(object sender, EventArgs e)
4:         {
5:             RegisterRoutes(RouteTable.Routes);
6:         }
7:
8:         void RegisterRoutes(RouteCollection routes)
9:         {
10:             routes.MapPageRoute(
11:                 "deepLinkRouteFull",
12:                 "restaurant/{restaurantId}/plate/{plateId}",
13:                 "~/default.aspx",
14:                 false,
15:                 new RouteValueDictionary { { "restaurant", "-1" },
16:                                            { "plate", "-1" } });
17:
18:             routes.MapPageRoute(
19:                 "deepLinkRoute",
20:                 "restaurant/{restaurantId}",
21:                 "~/default.aspx",
22:                 false,
23:                 new RouteValueDictionary { { "restaurant", "-1" } });
24:

Aux lignes 12 et 20 nous définissons le modèle des liens profonds que nous prenons en charge avec les « place holders » pour le restaurantId et plateId pour les valeurs dans l'adresse URL. Nous les définissons dans l'ordre du plus complexe au moins complexe. Les valeurs par défaut sont données aux lignes 15 et 23 si les identifiants sont omis de l'adresse URL.

Maintenant voyons comment parser cette URL sur le client Silverlight. Dans Plates.xaml.cs :

1:         // Executes when the user navigates to this page.
2:         protected override void OnNavigatedTo(NavigationEventArgs e)
3:         {
4:             int plateID = -1;
5:             int restaurantId  =-1;
6:             var s = HtmlPage.Document.DocumentUri.ToString().Split(new char[] {'/','#'});
7:             int i = Find(s, "plate");
8:             if (i != -1)
9:             {
10:                 plateID = Convert.ToInt32(s[i + 1]);
11:                 plateDomainDataSource.FilterDescriptors.Add(
12:                     new FilterDescriptor("ID",
13:                         FilterOperator.IsEqualTo, plateID));
14:             }
15:             i = Find(s, "restaurant");
16:             if (i != -1) restaurantId = Convert.ToInt32(s[i + 1]);
17:             else restaurantId = Convert.ToInt32(NavigationContext.QueryString["restaurantId"]);
18:             plateDomainDataSource.QueryParameters.Add(
19:                new Parameter()
20:                {
21:                   ParameterName = "resId",
22:                   Value = restaurantId
23:                }
24:             );
25:         }
26:

Fondamentalement, ce que le code ci-dessus fait c'est d'obtenir l'URL complète et de distinguer les parties de l'URL et distinguer les identifiants restaurant et plate. Aux lignes 18-23, nous passons le restaurantId comme paramètre à la méthode de requête et aux lignes 11-14 ci-dessus, nous n'utilisons pas une méthode de requête, mais appliquons plutôt un descripteur de filtre qui ajoute une clause « where » à la requête LINQ envoyée au serveur. Résultat, nous n'avons pas besoin de changer de code serveur.

Une autre petite chose que nous devons faire est de nous assurer que le client se retrouve bien sur la page Plates. Ceci est géré par le framework de navigation de Silverlight en utilisant la balise d'ancrage « #/Plates ». Parce que les balises d'ancrage sont une fonction client seulement, les moteurs de recherche ne peuvent pas les traiter de manière efficace. Donc, nous devons ajouter cela sur le client. J'ai trouvé que c'était assez facile de le faire en utilisant juste un peu de JavaScript. J'émets ceci de la page Default.aspx sur le serveur.

1: protected void Page_Init(object sender, EventArgs e)
2: {
3:    string resId = Page.RouteData.Values["restaurant"] as string;
4:    if (resId != null) { Response.Write("<script type=text/javascript>window.location.hash='#/Plates';</script"+">"); }
5: }
6:

Une petite chose à surveiller est qu'avec cette fonction de routage activée, maintenant la page Default.aspx est activée à partir d'une URL différente de sorte que les chemins relatifs de silverlight.js et MyApp.xap ne fonctionneront pas. Vous allez voir, par exemple, des requêtes pour http://www.hanselman.com/abrams/restaurant/25/plate/4/Silverlight.js plutôt que http://www.hanselman.com/abrams/silverlight.js. Et cela se traduira par une erreur telle que :

Line: 56
Error: Unhandled Error in Silverlight Application
Code: 2104
Category: InitializeError
Message: Could not download the Silverlight application. Check web server settings

Pour y remédier,

<script type="text/javascript" src='<%= this.ResolveUrl("~/Silverlight.js") %>'></script>

et

<param name="source" value="<%= this.ResolveUrl("~/ClientBin/MyApp.xap") %>"/>

Maintenant nous donnons une adresse URL qui inclut un PlateId telle que :

http://localhost:30045/restaurant/48/plate/119#/Plates

En conséquence, nous obtenons notre objet individuel.

Étape 2 : mettre les moteurs de recherche au courant de tous ces liens profonds avec un sitemap

Maintenant nous avons notre application liée en profondeur, avec chaque partie intéressante de données ayant une URL unique. Mais comment est-ce qu'un moteur de recherche sera capable de trouver ces URL ? Nous espérons certainement qu'à mesure que les gens en parlent (et par conséquent se connectent) sur notre site, sur des réseaux sociaux, les moteurs de recherche reprendront certains d'entre eux, mais nous voudrions peut-être faire un travail plus complet. Nous voudrions peut-être fournir au moteur de recherche TOUS les liens profonds de l'application. Nous pouvons le faire avec un sitemap.

Le format sitemap est accepté par tous les principaux moteurs de recherche... vous pouvez trouver de plus amples renseignements à ce sujet sur http://sitemap.org.

Pour comprendre comment cela fonctionne, regardons le procédé qu'utiliserait un moteur de recherche pour indexer un site intéressant piloté par les données : http://amazon.com. Quand un moteur de recherche tombe pour la première fois sur un tel site, il lit le fichier robots.txt à la racine du site. Dans le cas présent : http://www.amazon.com/robots.txt

Dans cet exemple, vous pouvez voir en haut du fichier une liste de répertoires que les moteurs de recherche sont invités à ignorer. Ensuite au bas de cette page, il y a une liste de sitemaps que le moteur de recherche doit utiliser pour indexer tout le contenu du site.

Note : vous n'avez pas, à proprement parler, besoin d'utiliser un sitemap. Vous pouvez utiliser les outils du webmestre fournis par les principaux moteurs de recherche pour inscrire directement votre sitemap.

Si nous accédons à l'une de ces URL, nous voyons un fichier de sitemap, comme indiqué ci-dessous :

Dans ce cas, parce qu'Amazon.com est tellement vaste, ces liens sont en fait pour plusieurs sitemap. (Ce fichier est connu comme un fichier d'index de sitemap). Quand on arrive au bout, nous obtenons des liens vers de réels produits.

Comme vous pouvez le voir, le format ressemble à :

<urlset xmlns="http://www.google.com/schemas/sitemap/0.84">
<url>
<loc>http://www.amazon.com/GAITHER-COMMITTEE-EISENHOWER-COLD-WAR/dp/081425005X</loc>
</url>
<url>
<loc>http://www.amazon.com/CONTROLLING-VICE-REGULATING-PROSTITUTION-CRIMINAL/dp/0814250076</loc>
</url>

Une chose qui est intéressante ici est que ces liens sont constamment en train de changer au fur et à mesure que les objets sont ajoutés et enlevés du catalogue Amazon.

Voyons comment créer un sitemap comme celui-ci pour notre site.

Dans le projet Web, ajoutez un nouveau Search Sitemap en utilisant le dialogue Add New Item dans VS et sélectionnez l'item Search Sitemap.

Veillez à installer le toolkit RIA Services afin d'avoir ce support.

Quand on fait cela, on obtient un fichier robots.txt qui ressemble à ça :

# This file provides hints and instructions to search engine crawlers.
# For more information, see http://www.robotstxt.org/.

# Allow all
User-agent: *

# Prevent crawlers from indexing static resources (images, XAPs, etc )
Disallow: / ClientBin /

# Register your sitemap(s) here.
Sitemap : /Sitemap.aspx

... et un fichier sitemap.aspx

Pour plus d'informations consultez: Uncovering web-based treasure with Sitemaps.

Pour construire ce sitemap, nous devons créer une autre vue des mêmes données à partir de notre PlateViewDomainService. Dans ce cas, nous le consommons à partir d'une page web ASP.NET. Pour ce faire nous utilisons l'objet DomainDataSource. Vous pouvez le configurer dans le concepteur en :

glissant et déposant un contrôle Repeater au formulaire et nous obtenons l'expérience de conception ci-dessous :

Ensuite faites un clic droit dessus et configurez la source de données.

Sélectionnez une nouvelle source de données

Finalement on se retrouve avec deux séries de liens dans notre sitemap.

1: <asp:DomainDataSource runat="server" ID="RestaurauntSitemapDataSource"
2:         DomainServiceTypeName="MyApp.Web.DishViewDomainService"
3:         QueryName="GetRestaurants" />
4:
5: <asp:Repeater runat="server" id="repeater" DataSourceID="RestaurauntSitemapDataSource" >
6:     <HeaderTemplate>
7:         <urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
8:     </HeaderTemplate>
9:     <ItemTemplate>
10:               <url>
11:                  <loc><%= Request.Url.AbsoluteUri.ToLower().Replace("sitemap.aspx",string.Empty) + "restaurant/"%><%# HttpUtility.UrlEncode(Eval("ID").ToString()) %></loc>
12:               </url>
13:  </ItemTemplate>
14: </asp:Repeater>
15:
16: <asp:DomainDataSource ID="PlatesSitemapDataSource" runat="server"
17:     DomainServiceTypeName="MyApp.Web.DishViewDomainService"
18:     QueryName="GetPlates">
19: </asp:DomainDataSource>
20:
21: <asp:Repeater runat="server"  id="repeater2" DataSourceID="PlatesSitemapDataSource">
22:     <ItemTemplate>
23:             <url>
24:                <loc><%= Request.Url.AbsoluteUri.ToLower().Replace("sitemap.aspx",string.Empty) + "restaurant/"%><%# HttpUtility.UrlEncode(Eval("RestaurantID").ToString()) + "/plate/" + HttpUtility.UrlEncode(Eval("ID").ToString()) %></loc>
25:             </url>
26:     </ItemTemplate>
27:     <FooterTemplate>
28:         </urlset>
29:     </FooterTemplate>
30: </asp:Repeater>
31:

Comme vous pouvez le voir aux lignes 3 et 18, nous faisons appel aux méthodes GetRestaurant et GetPlates définies directement dans notre DomainService.

Maintenant, pour tout ensemble raisonnable de données, cela va être une page TRÈS coûteuse à exécuter.Elle analyse chaque ligne dans la base de données. Bien qu'il soit agréable de conserver les données fraîches, nous aimerions équilibrer la charge du serveur. Une manière simple de faire cela est d'utiliser la mise en cache de sortie pendant une heure. Pour plus d'informations consultez : ASP.NET Caching: Techniques and Best Practices.

<%@ OutputCache Duration="3600" VaryByParam="None" %>

Une autre approche pour les très grands ensembles de données serait de factoriser les données en plusieurs sitemaps comme le fait l'exemple amazon.com que nous avons vu ci-dessus.

Et si nous saisissons une de ces URL et que nous y accédons. Bingo ! Nous obtenons la bonne page.

Étape 3 : fournir une version de « bas niveau » du contenu important

C'est fantastique, nous avons des liens profonds, nous avons un moyen pour que les moteurs de recherche découvrent l'ensemble de ces liens, mais que trouvera le moteur de recherche quand il arrive sur cette page ? Eh bien, les moteurs de recherche, la plupart du temps, ne font que parser du HTML, donc si nous faisons un « Afficher la source » nous verrons ce que voit le moteur de recherche :

Ou si nous naviguons avec Silverlight désactivé « Outils > Gérer les modules complémentaires », nous voyons ceci :

Nous voyons une vieille page blanche ne contenant rien !

Certes, aucun contenu dynamique n'est présenté. Le code doit effectivement être exécuté pour que le contenu dynamique soit chargé. Je suis quasiment certain que les moteurs de recherche ne vont pas exécuter ce code Silverlight (ou Flash ou Ajax) dans leurs centres de données de sitôt. Donc ce dont nous avons besoin c'est du contenu alternatif.

Heureusement ceci assez facile à faire. Tout d'abord obtenons n'importe quel contenu alternatif à rendre. Il est important de noter que ce contenu n'est pas uniquement pour les moteurs de recherche. Du contenu écrit uniquement pour les moteurs de recherche est parfois appelé spoofing de moteur de recherche ou spam Web quand il est fait pour tromper les utilisateurs des moteurs de recherche sur la véritable nature du site. (The pernicious perfidy of page-level web spam.)

Au lieu de cela, ce contenu est un rendu alternatif de la page pour quelqu'un qui n'a pas installé Silverlight. Il pourrait ne pas avoir toutes les fonctionnalités, mais c'est une bonne expérience de bas niveau. Il se trouve que les robots d'indexation du moteur de recherche n'ont pas Silverlight installé, de manière à obtenir quelque chose de significatif et précis à indexer.

Ajoutez ce code HTML à votre page default.aspx.

<div id="AlternativeContent" style="display: none;">
<h2>Hi, this is my alternative content</h2>
</div>

Remarquez que c'est « display : none », ce qui veut dire que nous ne nous attendons pas à ce que ce soit rendu par le navigateur, à moins que Silverlight ne soit pas disponible. Pour accomplir cela, ajouter ce bout de code à la page :

<script type="text/javascript">
if (!isSilverlightInstalled()) {
var obj = document.getElementById('AlternativeContent');
obj.style.display = "";
}
</script>

Notez que la méthode vraiment cool isSilverlightInstalled est tirée du vieux mais excellent billet de Petr sur le sujet. J'ai simplement ajouté cette fonction à mon fichier Silverlight.js.

function isSilverlightInstalled() {
var isSilverlightInstalled = false;
try {
//check on IE
try {
var slControl = new ActiveXObject('AgControl.AgControl');
isSilverlightInstalled = true;
}
catch (e) {
//either not installed or not IE. Check Firefox
if (navigator.plugins["Silverlight Plug-In"]) {
isSilverlightInstalled = true;
}
}
}
catch (e) {
//we don't want to leak exceptions. However, you may
//to add exception tracking code here
}
return isSilverlightInstalled;
}

Quand nous l'exécutons à partir d'un navigateur sans Silverlight activé, nous obtenons le contenu alternatif :

Mais avec Silverlight installé, nous obtenons notre beau contenu de l'application Silverlight.

C'est très bien, mais comment pouvons-nous exposer le contenu correct ? Nous voulons afficher exactement les mêmes données que dans l'application Silverlight et nous voulons écrire le moins de code possible. Nous ne voulons vraiment pas maintenir plusieurs pages. Donc, ajoutons un peu de code très basique à la page dans notre div AlternativeContent. Ce ListView est pour les détails de Restaurant.

<asp:ListView ID="RestaurnatDetails" runat="server"
EnableViewState="false">
<LayoutTemplate>
<asp:PlaceHolder ID="ItemPlaceHolder" runat="server"/>
</LayoutTemplate>
<ItemTemplate>
<asp:DynamicEntity ID="RestaurnatEntity" runat="server"/>
</ItemTemplate>
</asp:ListView>

Maintenant nous devons le lier à notre source de données. Je trouve que c'est assez facile dans la vue design dans VS. Notez que vous devez rendre le div visible afin que vous puissiez travailler avec dans le concepteur.

Ensuite nous configurons la source de données. C'est très facile de sélectionner la méthode de requête que nous voulons utiliser.

Ensuite nous lions le paramètre de la méthode de requête basé sur les itinéraires que nous avons définis.

Maintenant nous faisons exactement la même chose pour notre ListView Plates.

Cela nous donne un peu de code aspx très simple :

1:            <asp:ListView ID="RestaurnatDetails" runat="server"
2:                         EnableViewState="false" DataSourceID="restaurantsDomainDataSource">
3:                <LayoutTemplate>
4:                      <asp:PlaceHolder ID="ItemPlaceHolder" runat="server"/>
5:                </LayoutTemplate>
6:                <ItemTemplate>
7:                     <asp:DynamicEntity ID="RestaurnatEntity" runat="server"/>
8:                </ItemTemplate>
9:            </asp:ListView>
10:
11:            <asp:DomainDataSource ID="restaurantsDomainDataSource" runat="server"
12:                               DomainServiceTypeName="MyApp.Web.DishViewDomainService"
13:                               QueryName="GetRestaurant">
14:               <QueryParameters>
15:                  <asp:RouteParameter name="id" RouteKey="restaurantId"
16:                                   DefaultValue ="-1" Type = "Int32"/>
17:               </QueryParameters>
18:            </asp:DomainDataSource>
19:

Ensuite nous voulons permettre à ces contrôles de générer dynamiquement l'interface utilisateur basée sur les données.

1:         protected void Page_Init(object sender, EventArgs e)
2:         {
3:             RestaurnatDetails.EnableDynamicData(typeof(MyApp.Web.Restaurant));
4:             PlateDetails.EnableDynamicData(typeof(MyApp.Web.Plate));
5:             string resId = Page.RouteData.Values["restaurant"] as string;
6:             if (resId != null) { Response.Write("<script type=text/javascript>window.location.hash='#/Plates';</script"+">"); }
7:         }
8:

Remarquez que nous avons ajouté les lignes 4-5 pour activer les données dynamiques sur ces deux ListViews.

La dernière étape est que nous devons ajouter l'ensemble des modèles qu'utilise DynamicData. Vous pouvez obtenir ceux-là de n'importe quel projet Dynamic Data. Vous n'avez qu'à les copier à la racine du projet Web.

Vous pouvez éditer ces modèles pour contrôler exactement la façon dont vos données sont affichées.

Dans le répertoire Entity Templates nous avons besoin de deux modèles pour chacune de nos entités (Plate et Restaurant dans ce cas). Ceci permet de contrôler la façon dont ils sont affichés.

1: <%@ Control Language="C#" CodeBehind="Restaurant.ascx.cs" Inherits="MyApp.Web.RestaurantEntityTemplate" %>
2:
3:       <asp:DynamicControl  ID="DynamicControl8" runat="server" DataField="ImagePath" />
4:       <ul class="restaurant">
5:       <li>
6:          <ul class="restaurantDetails">
7:              <li><h2><asp:DynamicControl ID="NameControl" runat="server" DataField="Name" /> </h2> </li>
8:              <li><asp:DynamicControl ID="DynamicControl1" runat="server" DataField="ContactName" /> (<asp:DynamicControl ID="DynamicControl2" runat="server" DataField="ContactTitle" />)</li>
9:              <li><asp:DynamicControl ID="DynamicControl3" runat="server" DataField="Address" />  </li>
10:              <li><asp:DynamicControl ID="DynamicControl4" runat="server" DataField="City" />, <asp:DynamicControl ID="DynamicControl5" runat="server" DataField="Region" />  <asp:DynamicControl ID="DynamicControl6" runat="server" DataField="PostalCode" />  </li>
11:              <li><asp:DynamicControl ID="DynamicControl7" runat="server" DataField="Phone" />  </li>
12:              <li><asp:HyperLink runat="server" ID="link"  NavigateUrl="<%#GetDetailsUrl() %>" Text="details.."></asp:HyperLink></li>
13:          </ul>
14:       </li>
15:       </ul>
16:

Remarquez qu'ici aussi, nous ne faisons qu'une mise en forme basique et nous ne mentionnons que les champs que nous voulons voir apparaître dans le contenu alternatif. Répétez l'opération pour Plate.

Maintenant nous sommes prêts à l'exécuter.

À la racine, sans paramètres de chaîne de requêtes nous obtenons la liste des restaurants, le tout en HTML bien sûr.

Ensuite nous pouvons ajouter les itinéraires pour le réduire à un plat dans un restaurant en particulier.

Mais voyons cela dans un vrai navigateur, pour être sûr que nous sachions à quoi cela ressemble pour un moteur de recherche. ViveLynx! Lynx a été le premier navigateur Web que j'ai utilisé en 1992 sur ma machine DEC2100 dans le laboratoire Leazar sur le campus de l'Université d'État de Caroline du Nord. Et il fonctionne toujours aussi bien aujourd'hui.

Et les détails

Ce navigateur en mode texte classique nous montre seulement le texte - exactement ce que les robots d'indexation des moteurs de recherche verront.

Maintenant passons au vrai test.

Nous allons utiliser Bing pour « my foodie Explorer Cooking Class with Joe... » et bien sûr, c'est là :

Cliquer sur le lien indiqué dans la capture ci-dessus nous aurait amener exactement à la bonne page avec les données correctes dans notre belle vue Silverlight.

Bien sûr cela fonctionne aussi très bien avec le moteur de recherche de l'autre gars... si vous êtes du genre à utiliser celui-là ;-)

Résumé

Ce que vous avez vu ici, ce sont les rudiments sur la façon de créer une application Web Silverlight pilotée par les données et qui est « SEO ready ». Nous avons passé en revue trois étapes faciles et amusantes :

- Étape 1 : faire lier en profondeur le contenu important pour l'application ;

- Étape 2 : mettre les moteurs de recherche au courant de tous ces liens profonds avec un sitemap ;

- Étape 3 : fournir une version de « bas niveau » du contenu important.

Profitez-en !

Pour plus d'informations consultez : Illuminating the path to SEO for Silverlight.

Conclusion

Ceci conclut la septième partie de cette série. Dans la huitième partie nous verrons comment exposer des services OData.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Exposing OData Services

Exposer des services OData

OData est un ensemble d'extensions émergentes pour le protocole Atom qui rend plus facile le partage de données sur le Web. Pour mettre en valeur OData dans RIA Services, continuons notre série. Nous pensons qu'il est très intéressant d'exposer OData à partir d'un DomainService pour faciliter le partage de données. Par exemple, je pourrais vouloir que les utilisateurs puissent accéder à mes données de manière riche dans Excel aussi bien que mon client Silverlight personnalisé. Je voudrais être en mesure de permettre cela sans écrire de multiples services ou sans dupliquer aucune logique métier ou logique d'accès aux données.

Ceci est très facile à activer avec RIA Services. En fait, il suffit d'une case à cocher ! Quand vous créez votre DomainService, cochez tout simplement la case « Expose OData endpoint » et cela exposera votre DomainService comme un flux OData.

Si vous avez déjà créé un DomainService, c'est facile d'activer OData dessus également en faisant les deux choses que fait cet assistant.

Premièrement, il ajoute un point de terminaison (en anglais, endpoint) à la section domainServices du web.config.

<system.serviceModel>
<domainServices>
<endpoints>
<add name="OData"
type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</endpoints>
</domainServices>

Deuxièmement, sur chaque méthode de requête sans paramètres que vous souhaitez exposer par l'intermédiaire d'OData, marquer la requête comme étant par défaut. Cela veut dire qu'à chaque fois qu'il y a une requête pour Plate, c'est cette méthode de requête qui sera utilisée.

[Query(IsDefault = true)]
public IQueryable<Plate> GetPlates()
{

Une fois que tout cela est fait vous pouvez interroger le service et voir un flux Atom. Le format de l'adresse URL est espace de noms (namespace)+nom du type (typename) pour le domainservice et les points sont remplacés par des tirets suivi de « .svc/Odata/ ». Donc, si la classe DomainService est MyApp.Web.DishViewDomainService alors l'adresse URL sera http://localhost:30045/myapp-web-DishViewDomainService.svc/OData/

Ensuite accédez avec cette URL : http://localhost:30045/myapp-web-DishViewDomainService.svc/OData/PlateSet

C'est vraiment cool que les données soient dans un format ouvert basé sur Atom. Mais ce qui est encore mieux, c'est qu'il y a un écosystème naissant de clients qui peuvent consommer ce flux. L'un des plus intéressants est l'add-in Excel appelé PowerPivot.

Une fois que vous l'avez installé avec Excel 2010, sélectionnez la fenêtre PowerPivot.

Ensuite vous pouvez utiliser toute la puissance d'Excel. Donc, si vous voulez trier par nombre de mises à jour, avec une note de 4 ou supérieur, avec un nombre de calories entre 3000 et 4000, et ensuite présenter cela dans un graphe d'une façon intéressante, vous pouvez facilement le faire... Le tout avec les données « live » sans aucun code d'application personnalisé.

Ce que nous avons montré dans cette procédure pas à pas, c'est comment exposer OData à partir de votre DomainService et consommer cela dans Excel. Ceci n'est qu'un avant-goût de la prise en charge OData à venir dans le futur dans RIA Services.

Conclusion

Ceci conclut la huitième partie de cette série. Dans la neuvième partie nous verrons comment exposer des services WCF (SOAP/WSDL).

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Exposing WCF (SOAP/WSDL) Services

Exposer des services WCF (SOAP/WSDL)

Poursuivant notre série, je voulais aborder la façon dont un RIA Services peut être exposé comme un service SOAP/WSDL. Cela est très utile si vous voulez permettre qu'exactement la même logique métier / logique d'accès aux données soit disponible aux clients autres que Silverlight. Par exemple pour une application WinForms ou WPF ou même une application console. SOAP est un modèle particulièrement adapté pour l'interopérabilité avec le monde Java/JEE également.

Tout d'abord, vous devez ajouter une référence à l'assembly Microsoft.ServiceModel.DomainSerivves.Hosting.EndPoints à partir du RIA Services Toolkit.

Ensuite vous devez modifier la section « endpoints » de la configuration domainServices dans le fichier web.config. Ci-dessous je vous montre les « endpoints » SOAP et OData activés.

<system.serviceModel>
<domainServices>
<endpoints>
<add name="OData"
type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
<add name="Soap"
type="Microsoft.ServiceModel.DomainServices.Hosting.SoapXmlEndpointFactory, Microsoft.ServiceModel.DomainServices.Hosting, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
</endpoints>
</domainServices>

Maintenant, tout ce qui vous reste à faire est d'aller vers cette URL dans votre navigateur :

http://localhost:21516/BusinessApplication1-web-DishViewDomainService.svc

Notez que le motif est [namespace]-[typename].svc

Et nous obtenons la très familière page de débogage du service WCF.

Et si vous voulez y aller en profondeur, voici le WSDL complet décrivant le service.

Vous avez fini sur le serveur, maintenant nous allons voir comment vous consommez cela sur le client. Par souci de simplicité, j'ai créé une nouvelle application console. Faites un clic droit sur le projet et sélectionnez Ajouter une référence de service (en anglais, « Add Service Reference »).

1: var context = new DishViewDomainServiceSoapClient();
2: var results = context.GetRestaurants();
3: foreach (var r in results.RootResults)
4: {
5:    Console.WriteLine(r.Name);
6: }
7:

Le code pour la requête est très simple. La ligne 1 crée le proxy client, la ligne 2 fait un appel synchrone au serveur. Ensuite nous devons tout simplement boucler à travers les résultats.

Mettre à jour les données est un peu plus intéressant. J'ai besoin de créer un ChangeSet que je remplis avec les mises à jour. Dans ce cas je mets à jour une seule valeur. Je dois aussi renvoyer la valeur d'origine vers le serveur.

1: var changeSet = new ChangeSetEntry();
2: var org = new Restaurant();
3: org.ID = entity.ID;
4: org.Name = entity.Name;
5: entity.Name = "Updated:" + entity.Name;
6: changeSet.Entity = entity;
7: changeSet.OriginalEntity = org;
8: changeSet.Operation = DomainOperation.Update;
9: var updateResults = context.SubmitChanges(new ChangeSetEntry[] { changeSet });
10:

Et vous pouvez voir ce qui résulte du fait d'exécuter cela plusieurs fois :

Pour plus d'informations, voir le billet de Deepesh Mohnani intitulé Configuring your DomainService for a Windows Phone 7 application.

Pour la V1.0, ce billet décrit l'histoire de RIA Services pour les applications WPF. Même si elle fonctionne, ce n'est clairement pas la solution complète et de bout en bout qu'offre RIA Services pour Silverlight. Par exemple, vous voudrez peut-être l'expérience complète : le DataContext, entités avec validation, requêtes LINQ, le suivi des modifications, etc. Ce niveau de support pour WPF est sur la feuille de route mais ne sera pas de la V1. Mais si vous aimez l'approche RIA Services, vous pourriez envisager DevForce d'IdeaBlade ou CSLA.NET  qui fonctionnent à la fois pour WPF et Silverlight. Les gens sympa d'IdeaBlade m'ont offert dix licences gratuites à distribuer. Si vous êtes intéressés, s'il vous plait faites-le-moi savoir.

Conclusion

Ceci conclut la neuvième partie de cette série. Dans la dixième partie nous verrons comment exposer des services JSON.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Ajax Endpoint

Ajax Endpoint

Poursuivant notre série, je voulais aborder la façon dont un service RIA peut être exposé à votre service dans JSON. Cela est très pratique pour les clients Ajax.

La bonne chose est qu'en activant l'endpoint JSON cela ne nécessite AUCUN changement au Domain Service. Tout ce que vous avez à faire pour l'activer est d'ajouter l'endpoint JSON dans web.config

1: <system.serviceModel>
2:  <domainServices>
3:    <endpoints>
4:      <add name="JSON"
5:          type="Microsoft.ServiceModel.DomainServices.Hosting.JsonEndpointFactory, Microsoft.ServiceModel.DomainServices.Hosting,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
6:      <add name="OData"
7:          type="System.ServiceModel.DomainServices.Hosting.ODataEndpointFactory, System.ServiceModel.DomainServices.Hosting.OData,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
8:      <add name="Soap"
9:          type="Microsoft.ServiceModel.DomainServices.Hosting.SoapXmlEndpointFactory, Microsoft.ServiceModel.DomainServices.Hosting,
Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" />
10:    </endpoints>
11:  </domainServices>
12:

Comme vous pouvez le constater, l'extrait ci-dessus montre l'ajout de l'endpoint JSON à partir du toolkit RIA Services aussi bien que ceux d'OData et SOAP.

Vous pouvez voir les résultats de l'endpoint en naviguant à l'adresse URL dans ce format :

http://localhost:21516/BusinessApplication1-web-DishViewDomainService.svc/Json/GetRestaurants

{"GetRestaurantsResult":{"TotalCount":-2,"IncludedResults":[],
"RootResults":[{"Address":"49 Gilbert St.","City":"London",
"ContactName":"Charlotte Cooper","ContactTitle":"Purchasing Manager",
"Fax":"(171) 555-2222","HomePage":"","ID":1,"ImagePath":
"Restaurant_Alinea.jpg","Name":"Alinea - Updated from Ajax",
"Phone":"(171) 555-2222","PostalCode":"EC1 4SD","Region":""},
{"Address":"P.O. Box 78934","City":"New Orleans","ContactName":
"Shelley Burke",
"ContactTitle":"Order

Comme vous pouvez le constater - du JSON agréable à voir. Maintenant pour écrire un client Ajax très simple.

Ci-dessous un exemple de méthode de requête dans le client Ajax :

function query() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.open("GET", "BusinessApplication1-web-DishViewDomainService.svc/Json/GetRestaurants", false);
xmlhttp.send();
var rawResults = JSON.parse(xmlhttp.responseText);
var results = rawResults.GetRestaurantsResult.RootResults;
var entity
for (var i = 0; i < results.length; i++)
{
entity = results[i];
document.getElementById('results').innerHTML += ' <br> ' + entity.Name;
}

}

Ceci est relié à un bouton très simple

<button type="button" onclick="query()">
Query</button>

La mise à jour est un peu plus compliquée, mais toujours basique :

function update() {
var operation = {};
operation.Entity = { "__type": "Restaurant:#BusinessApplication1.Web", "ID": 1, "Name": "Alinea - Updated from Ajax"};
operation.OriginalEntity = { "__type": "Restaurant:#BusinessApplication1.Web", "ID": 1, "Name": "Alinea" };
operation.Operation = 3; //update
var csData = JSON.stringify({ "changeSet": [operation] });
var xmlhttp = new XMLHttpRequest();
xmlhttp.open('POST', 'BusinessApplication1-web-DishViewDomainService.svc/Json/SubmitChanges', false);
xmlhttp.setRequestHeader("Content-Type", "application/json");
xmlhttp.send(csData);
var results = xmlhttp.responseText;
document.getElementById('results').innerHTML = results;
}

Dans cette démo, nous avons montré comment activer le client Ajax/JSON pour RIA Services.

Conclusion

Ceci conclut la dixième partie de cette série. Dans la onzième partie nous verrons comment localiser nos applications métier.

Traduction

Cet article est la traduction la plus fidèle possible de l'article original de Brad Abrams, Silverlight 4 + RIA Services - Ready for Business: Localizing Business Application

Localiser les applications métier

Pour poursuivre notre série, jetons un œil à la localisation de nos applications métier. Dans le village global d'aujourd'hui, il n'est souvent plus acceptable de prendre en charge une seule langue. Plusieurs applications métier dans le monde réel doivent prendre en charge plusieurs langues. Pour démontrer le modèle, regardons la localisation du modèle application métier Silverlight.

Mise à jour (28/03) : J'ai ajouté un peu plus d'explications sur la mise en place de la culture dans Visual Studio.

Vous pouvez télécharger la solution complète.

Ici elle est en anglais côte à côte avec une version localisée (remarquez que la version en hébreu est rendue correctement de droite à gauche) :

Commençons par créer une nouvelle application métier Silverlight. Remarquez qu'il y a un projet serveur et un projet client dans la solution. Les chaînes qui sont requises dans le projet serveur et le projet client sont situées dans le projet serveur et celles qui ne sont requises que dans le client Silverlight sont dans le projet client. Commençons par le serveur.

Dans le dossier Resources nous voyons deux ensembles de ressources. Un pour les messages d'erreur et un autre pour les détails d'enregistrement. Ceux-là sont au format ResX qui est un format de fichier XML qui existe depuis longtemps. Il y a de nombreux outils de localisation hormis VS qui prennent en charge ce format. Ici, je montrerai la localisation dans VS.

Une étape de configuration avant que nous ne commencions. Vous devez explicitement indiquer à Visual Studio quelles langues sont prises en charge par l'application Silverlight. Malheureusement, ceci n'est pas pris en charge par la boîte de dialogue Propriétés de projet VS, donc vous devez directement éditer le fichier csproj de l'application Silverlight.

Faites un clic droit sur le projet Silverlight et Décharger le projet

Faites un clic droit de nouveau et sélectionnez Éditer MyApp.csproj

Ensuite trouvez l'attribut SupportedCultures et ajoutez la liste complète des cultures que votre application prend en charge.

Ensuite faites un clic droit sur le projet et Recharger le projet.

OK, nous avons fini avec la configuration, maintenant place à la localisation elle-même.

La première étape est de créer une copie de chacun de ces fichiers avec l'extension [Local].resx. Par exemple, j'en ai créé un appelé « ErrorResources.he.resx ». Voici une liste complète decodes de cultures.

Ensuite ouvrez le fichier dans VS et vous voyez une expérience d'édition agréable. Il suffit de remplacer le texte en anglais par la version traduite.

Ci-dessous l'expérience de la traduction en cours :

Répétez l'opération pour le fichier RegistrationDataResources.resx.

Ensuite nous devons rendre ces nouveaux fichiers *.he.resx accessibles depuis le client. Tout d'abord trouvons le répertoire de ressources client. Remarquez que les ressources en anglais sont déjà liées ici. Nous devons ajouter celles en hébreu que nous venons juste de créer.

Faites un clic droit et ajoutez (Add) un élément existant (Existing Item)

Ensuite naviguez au fichier ErrorResources.he.resx dans le projet web.

Ensuite sélectionnez Ajouter en tant que lien

Répétez l'opération pour les autres fichiers localisés sur le client.

Dans ApplicationStrings.resx et ApplicationStrings.he.resx j'ai ajouté une valeur supplémentaire pour indiquer le sens du flux.

J'ai ensuite effectué deux petits réglages au modèle pour utiliser cette valeur. Dans chaque fenêtre de niveau supérieur, j'ai besoin de définir le sens du flux.

Dans Main.xaml :

<UserControl
x:Class="LocalizedBusinessApplication.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
FlowDirection="{Binding Path=ApplicationStrings.FlowDirection, Source={StaticResource ResourceWrapper}}"

Et dans View\LoginRegistrationWindow.xaml

<controls:ChildWindow
x:Class="LocalizedBusinessApplication.LoginUI.LoginRegistrationWindow"
x:Name="childWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
Width="400"
Title="{Binding Path=ApplicationStrings.LoginWindowTitle, Source={StaticResource ResourceWrapper}}"
Style="{StaticResource LogRegWindowStyle}"
FlowDirection="{Binding Path=ApplicationStrings.FlowDirection, Source={StaticResource ResourceWrapper}}"
Closing="LoginWindow_Closing">

Maintenant nous sommes prêts à lancer l'application.

C'est très bien mais tout est en anglais... comment pouvons-nous tester la localisation ? Changez simplement votre langue dans le navigateur.

Dans Internet Explorer c'est Outils\Options Internet.

Assurez-vous que l'hébreu soit tout en haut de la liste. Ensuite réactualisez la page et...

Ce que j'ai montré ici explique comment on peut localiser une application Silverlight en utilisant le modèle d'application métier.

Profitez-en !

Conclusion

Ceci conclut la onzième partie de cette série.

Liens

- Visual Studio 2010

- Visual Studio 2010 Express

- Silverlight 4 Tools

- RIA Services

- Télécharger l'application complète

Remerciements

Je tiens ici à remercier Brad Abrams pour m'avoir autorisé à traduire son article.
Je remercie tomlev et Jean-Michel Ormes pour leurs relectures technique et leurs propositions.
Je remercie également Erielle, _Max_ et ClaudeLELOUP pour leurs relectures orthographique et leurs propositions.