Utilisation du modèle objet de client géré SharePoint Foundation 2010
Résumé : Apprenez à utiliser le modèle objet de client géré SharePoint Foundation 2010 pour écrire des applications basées sur .NET Framework qui accèdent au contenu SharePoint de clients, sans installer de code sur le serveur qui exécute SharePoint Foundation 2010.
Dernière modification : lundi 9 mars 2015
S’applique à : Business Connectivity Services | Office 2010 | Open XML | SharePoint Designer 2010 | SharePoint Foundation 2010 | SharePoint Online | SharePoint Server 2010 | Visual Studio
Dans cet article
Vue d’ensemble
Utilisation du modèle objet de client géré SharePoint Foundation 2010
Fonctionnement du modèle objet côté client
Créer des applications du modèle objet de client géré sur console Windows
Vue d’ensemble du modèle objet de client géré SharePoint Foundation 2010
Utilisation des identités d’objets
Limiter les jeux de résultats
Création et remplissage d’une liste
Utilisation du langage CAML pour interroger des listes
Filtrage de la collection d’enfants renvoyée par le chargement à l’aide de LINQ
Utilisation de la méthode LoadQuery
Amélioration des performances en imbriquant des instructions Include dans LoadQuery
Filtrage de la collection d’enfants renvoyée par LoadQuery à l’aide de LINQ
Mise à jour des objets clients
Suppression des objets clients
Détection du schéma des champs
Accès à des grandes listes
Traitement asynchrone
Ressources supplémentaires
Auteur : Eric White, Microsoft Corporation
Sommaire
Vue d’ensemble
Utilisation du modèle objet de client géré SharePoint Foundation 2010
Fonctionnement du modèle objet côté client
Créer des applications du modèle objet de client géré sur console Windows
Vue d’ensemble du modèle objet de client géré SharePoint Foundation 2010
Utilisation des identités d’objets
Limiter les jeux de résultats
Création et remplissage d’une liste
Utilisation du langage CAML pour interroger des listes
Filtrage de la collection d’enfants renvoyée par le chargement à l’aide de LINQ
Utilisation de la méthode LoadQuery
Amélioration des performances en imbriquant des instructions Include dans LoadQuery
Filtrage de la collection d’enfants renvoyée par LoadQuery à l’aide de LINQ
Mise à jour des objets clients
Suppression des objets clients
Détection du schéma des champs
Accès à des grandes listes
Traitement asynchrone
Ressources supplémentaires
Vue d’ensemble
Grâce au modèle objet de client géré SharePoint Foundation 2010, vous pouvez concevoir des applications clientes qui accèdent au contenu SharePoint, sans installer de code sur le serveur qui exécute Microsoft SharePoint Foundation 2010. Par exemple, vous pouvez générer de nouvelles catégories d’applications qui incluent notamment des applications basées sur Microsoft .NET Framework, des composants WebPart interactifs enrichis, des applications Microsoft Silverlight et des applications ECMAScript (JavaScript, JScript) qui s’exécutent côté client dans un composant WebPart SharePoint. Par exemple :
Une responsable d’équipe crée un site SharePoint qui comporte de nombreuses listes nécessaires pour gérer la mission de son équipe. Elle souhaite modifier ces listes de façon ad-hoc : mettre à jour des affectations et des estimations basées sur une feuille de calcul au format XML ouvert, ou déplacer des éléments d’une liste SharePoint vers une autre. Elle souhaite écrire une petite application personnalisée dans ce but.
Une société de logiciels qui vend une importante application cliente traditionnelle veut intégrer des bibliothèques de documents et des listes SharePoint à son application, et que cette intégration soit transparente pour ses utilisateurs.
Un développeur SharePoint veut construire un composant WebPart enrichi pour un déploiement SharePoint qui insère le contenu de listes dans son code Web AJAX personnalisé. Il souhaite également générer une application Silverlight encore plus enrichie qui effectue la même chose.
Quel est le critère commun à toutes ces personnes ? Ils peuvent utiliser le modèle objet de client géré SharePoint Foundation 2010 pour réaliser leurs objectifs. Le modèle objet de client géré SharePoint Foundation 2010 vous permet d’écrire du code côté client pour travailler avec tous les objets courants des sites SharePoint. Via le modèle objet, vous pouvez ajouter et supprimer des listes, ajouter, mettre à jour et supprimer des éléments dans une liste, modifier des documents dans des bibliothèques de documents, créer des sites, gérer des autorisations concernant des éléments, et ajouter et supprimer des composants WebPart sur une page.
Auparavant, il n’y avait que peu d’options. Vous pouviez utiliser des services Web pour interagir avec des listes SharePoint ou d’autres fonctionnalités, mais c’était complexe. Si les services Web ne fournissaient pas les possibilités nécessaires, vous pouviez écrire du code côté serveur pour offrir un nouveau service Web (tâche encore plus difficile). Certains départements informatiques refusent le code côté serveur, ou acceptent uniquement le code écrit par leurs informaticiens ; cette option n’était donc pas toujours possible. Le modèle objet de client géré SharePoint Foundation 2010 permet de nouvelles sortes d’applications et facilite l’écriture de code côté client qui interagit avec le contenu SharePoint.
Utilisation du modèle objet de client géré SharePoint Foundation 2010
Pour utiliser le modèle objet de client géré SharePoint Foundation 2010 (modèle objet client), vous écrivez du code managé basé sur .NET Framework qui utilise une API qui ressemble au modèle objet utilisé sur un serveur qui exécute SharePoint Foundation. Le modèle objet client comporte des classes pour accéder aux informations de collection de sites, aux informations de site, ainsi qu’aux informations de liste et d’élément de liste.
Pour les composants WebPart, vous utilisez une interface de programmation ECMAScript (JavaScript, JScript) qui ressemble à l’API .NET Framework. Pour Silverlight, vous utilisez un sous-ensemble de l’API qui est disponible via .NET Framework sur le client. Bien que la plupart des informations présentées dans cet article aient rapport aux API JavaScript et Silverlight, cet article traite principalement de la façon d’utiliser le modèle objet de client géré SharePoint Foundation 2010 à partir d’une application cliente basée sur .NET Framework.
Le modèle objet de client géré SharePoint Foundation 2010 est composé de deux assemblys qui contiennent cinq espaces de noms. Si vous regardez les classes disponibles dans ces espaces de noms, vous en compterez beaucoup. Vous n’avez pas à vous en soucier ; la plupart de ces classes sont utilisées de façon interne par le modèle objet. Seul un sous-ensemble de ces classes vous concerne, principalement celles qui ont des équivalents directs avec certaines classes connues du modèle objet de serveur SharePoint Foundation.
Tableau 1. Classes côté client et leurs équivalents côté serveur
Client |
Serveur |
---|---|
Notez que le modèle objet de client géré SharePoint Foundation 2010 utilise le même modèle hérité de dénomination pour les collections de sites et les sites que le modèle objet serveur. La classe Site représente les collections de sites, et la classe Web représente les sites. Je préfère, lorsque j’utilise ces classes, nommer les variables de sorte que le nom de la variable indique s’il s’agit d’une collection de sites ou d’un site, bien qu’il faille utiliser les classes Site et Web pour les déclarer.
L’exemple suivant illustre ma façon de nommer les variables.
ClientContext clientContext = new ClientContext(siteUrl);
Site siteCollection = clientContext.Site;
Web site = clientContext.Web;
Fonctionnement du modèle objet côté client
Une application qui utilise du contenu SharePoint interagit avec l’API de plusieurs manières : elle appelle des méthodes, obtient les valeurs de retour, passe une requête Langage CAML (Collaborative Application Markup Language), obtient les résultats, définit ou lit des propriétés. Une fois que vous avez utilisé l’API pour effectuer une tâche spécifique, le modèle objet de client géré SharePoint Foundation 2010 rassemble ces interactions avec l’API dans du code XML qu’il envoie au serveur qui exécute SharePoint Foundation. Le serveur reçoit cette demande, effectue les appels appropriés au modèle objet sur le serveur, collecte les réponses, les met en forme en JSON (JavaScript Object Notation), et renvoie ce JSON au modèle objet de client géré SharePoint Foundation 2010. Le modèle objet client analyse le JSON et passe les résultats à l’application sous forme d’objets .NET Framework (ou d’objets JavaScript pour JavaScript). Le schéma suivant illustre ces interactions.
Figure 1. Le modèle objet de client géré SharePoint Foundation 2010
Il est important de savoir que vous avez le contrôle du moment où le modèle objet de client géré SharePoint Foundation 2010 envoie le code XML au serveur et reçoit en retour le JSON du serveur.
Le regroupement de plusieurs appels de méthodes dans un seul appel au serveur est dicté par les contraintes telles que vitesse du réseau, latence du réseau et performances recherchées. Si le modèle objet de client géré SharePoint Foundation 2010 interagissait avec le serveur à chaque appel de méthode, les performances du système et l’accroissement du trafic réseau rendraient le système impraticable.
Comme je l’ai mentionné, vous contrôlez explicitement le moment où le modèle objet de client géré SharePoint Foundation 2010 rassemble les appels de méthodes et envoie une demande au serveur. Dans le cadre de ce processus, avant de commencer l’interaction avec le serveur, vous devez explicitement spécifier quel contenu récupérer du serveur. C’est la plus grande différence entre le modèle objet de client géré SharePoint Foundation 2010 et le modèle objet SharePoint Foundation 2010. Une fois que vous avez compris le modèle, il n’y a pas de difficulté. La façon la plus simple de comprendre la différence est d’observer une application simple.
Créer des applications du modèle objet de client géré sur console Windows
Notes
J’utilise des applications console Windows pour le code d’exemple. Cependant, vous pouvez utilisez la même approche avec d’autres types d’application.
Pour générer l’application, vous devez ajouter les références à deux assemblys, Microsoft.SharePoint.Client.dll et Microsoft.SharePoint.Client.Runtime.dll. L’installation SharePoint Foundation installe ces assemblys sur le serveur. Ces deux assemblys se trouvent dans le répertoire suivant :
%ProgramFiles%\Common Files\Microsoft Shared\web server extensions\14\ISAPI
Important
Pour SharePoint Foundation Bêta et Microsoft SharePoint Server 2010 Bêta, vous devez copier les deux assemblys et les placer à un endroit commode sur votre ordinateur client de développement. Vous devez parcourir jusqu’à ces assemblys pour ajouter les références à ceux-ci lorsque vous configurez des projets qui utilisent le modèle objet de client géré SharePoint Foundation 2010.
Pour générer l’application
Démarrez Microsoft Visual Studio 2010.
Dans le menu Fichier, pointez sur Nouveau, puis cliquez sur Projet.
Dans la boîte de dialogue Nouveau projet, dans le volet Modèles récents, développez Visual C#, puis cliquez sur Windows.
À droite du volet Modèles récents, cliquez sur Application console.
Par défaut, Visual Studio crée un projet qui a pour cible .NET Framework 4, mais vous devez cibler .NET Framework 3.5. Dans la liste de la partie supérieure de la boîte de dialogue Ouvrir, sélectionnez .NET Framework 3.5.
Dans la zone Nom, tapez le nom voulu pour votre projet, par exemple FirstClientApiApplication.
Dans la zone Emplacement, entrez l’emplacement où vous souhaitez placer le projet.
Figure 2. Création d’une solution dans la boîte de dialogue Nouveau projet
Cliquez sur OK pour créer la solution.
Pour ajouter des références aux assemblys Microsoft.SharePoint.Client et Microsoft.SharePoint.Client.Runtime
Les classes utilisées dans une application du modèle objet client se trouvent dans Microsoft.SharePoint.Client.dll et Microsoft.SharePoint.Client.Runtime.dll. Comme je l’ai mentionné, avant d’ajouter les références, vous devez copier ces assemblys du serveur qui exécute SharePoint Foundation sur l’ordinateur client de développement.
Dans le menu Projet, cliquez sur Ajouter la référence pour ouvrir la boîte de dialogue Ajouter la référence.
Sélectionnez l’onglet Parcourir, naviguez jusqu’à l’emplacement où placer Microsoft.SharePoint.Client.dll et Microsoft.SharePoint.Client.Runtime.dll. Sélectionnez les deux DLL, puis cliquez sur OK comme indiqué dans la Figure 3.
Figure 3. Ajout des références aux assemblys
Pour ajouter l’exemple de code à la solution
Dans Visual Studio, remplacez le contenu du fichier Program.cs source par code suivant.
using System; using Microsoft.SharePoint.Client; class DisplayWebTitle { static void Main() { ClientContext clientContext = new ClientContext("http://intranet.contoso.com"); Web site = clientContext.Web; clientContext.Load(site); clientContext.ExecuteQuery(); Console.WriteLine("Title: {0}", site.Title); } }
Remplacez l’URL dans le constructeur ClientContext(String) par l’URL du site SharePoint. Générez et exécutez la solution. L’exemple imprime le titre du site.
Tout comme avec le modèle objet de serveur SharePoint Foundation, vous créez un contexte pour le site SharePoint auquel vous souhaitez accéder. Vous pouvez ensuite récupérer une référence au site à partir du contexte.
L’appel à la méthode ExecuteQuery() provoque l’envoi de la demande par le modèle objet de client géré SharePoint Foundation 2010 au serveur. Aucun trafic réseau n’est généré avant que l’application appelle la méthode ExecuteQuery().
Un point important à noter dans cet exemple est que l’appel à la méthode Load() n’effectue aucun chargement. En fait, cela informe le modèle objet client que lorsque l’application appelle la méthode ExecuteQuery(), vous souhaitez charger les valeurs des propriétés de l’objet siteCollection.
Voici le modèle que suivent toutes les interactions avec le serveur :
Vous informez le modèle objet de client géré SharePoint Foundation 2010 des opérations que vous souhaitez faire. Cela inclut l’accès aux valeurs des propriétés d’objets (par exemple, des objets de la classe List, de la classe ListItem et de la classe Web), les requêtes CAML que vous souhaitez exécuter, et des objets, tels que les objets ListItem, que vous voulez insérer, mettre à jour ou supprimer.
Ensuite, vous appelez la méthode ExecuteQuery(). Aucun trafic réseau n’est généré avant l’appel à la méthode ExecuteQuery(). Jusqu’à ce point, votre application ne fait qu’enregistrer ses demandes.
Comme cet exemple le montre, de manière la plus simple, vous configurez d’abord une requête, puis vous exécutez celle-ci. Cela provoque l’envoi de trafic au serveur par le modèle objet client, puis la réception d’une réponse du serveur. La section suivante examine le modèle en détail et montre pourquoi il est ainsi conçu, et comment l’utiliser pour générer des applications.
Vue d’ensemble du modèle objet de client géré SharePoint Foundation 2010
Certains aspects du modèle objet client sont importants à examiner :
Approches que le modèle objet client doit suivre pour minimiser le trafic réseau
Construction des requêtes
Techniques pour améliorer les performances du serveur
Comment créer, mettre à jour et supprimer des objets clients
Comment travailler avec les très grandes listes
Avant d’approfondir ces sujets, examinons le problème de l’identité des objets.
Utilisation des identités d’objets
L’idée fondamentale derrière l’identité des objets est que les objets clients font référence à leurs correspondants dans le modèle objet de serveur SharePoint Foundation avant et après leur appel à la méthode ExecuteQuery(). Ils continuent de faire référence au même objet via les divers appels à la méthode ExecuteQuery().
Cela signifie que lors de la configuration de la requête, le modèle objet client retourne les objets que vous pouvez utiliser pour définir davantage la requête avant d’appeler la méthode ExecuteQuery(). Cela vous permet d’écrire des requêtes plus complexes avant de générer du trafic à destination ou en provenance du serveur. Vous pouvez réaliser des opérations plus intéressantes dans une seule requête, en éliminant du trafic réseau.
L’exemple suivant récupère l’objet liste Announcements et récupère ensuite tous les éléments de cette liste au moyen de la requête CAML la plus simple possible.
using System;
using Microsoft.SharePoint.Client;
class Program
{
static void Main()
{
ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
List list = clientContext.Web.Lists.GetByTitle("Announcements");
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml = "<View/>";
ListItemCollection listItems = list.GetItems(camlQuery);
clientContext.Load(list);clientContext.Load(listItems);
clientContext.ExecuteQuery();
foreach (ListItem listItem in listItems)
Console.WriteLine("Id: {0} Title: {1}", listItem.Id, oListItem["Title"]);
}
}
Notez la séquence dans cet exemple :
D’abord, le code récupère un objet List à l’aide de la méthode GetByTitle(). Souvenez-vous que cet objet List ne contient aucune donnée ; aucune de ses propriétés ne contient des données tant que l’application n’appelle pas la méthode ExecuteQuery().
Le code appelle ensuite la méthode GetItems() sur l’objet list, bien qu’elle ne remplisse pas cet objet list de données.
Finalement, il appelle la méthode Load() sur l’objet list et sur l’objet listItems, puis appelle la méthode ExecuteQuery().
Le point fondamental à retenir est que le modèle objet client se souvient que l’objet list est celui que l’application a initialisé à l’aide de la méthode GetByTitle(), et que le modèle objet client doit exécuter la requête CAML sur ce même objet list après la récupération de l’objet list à partir de la base de données SharePoint. Toute classe qui dérive de la classe ClientObject possède cette sémantique.
Et, comme mentionné précédemment, vous pouvez continuer à utiliser des objets clients pour configurer des requêtes supplémentaires après l’appel à la méthode ExecuteQuery(). Dans l’exemple suivant, le code charge l’objet list et appelle la méthode ExecuteQuery(). Il utilise ensuite l’objet client list pour appeler la méthode List.GetItems, puis appelle la méthode ExecuteQuery() à nouveau. L’objet list a conservé son identité lors de l’appel à la méthode ExecuteQuery().
using System;
using Microsoft.SharePoint.Client;
class Program
{
static void Main()
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
List list = clientContext.Web.Lists
.GetByTitle("Announcements");
clientContext.Load(list);
clientContext.ExecuteQuery();
Console.WriteLine("List Title: {0}", list.Title);
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml = "<View/>";
ListItemCollection listItems = list.GetItems(camlQuery);
clientContext.Load(listItems);
clientContext.ExecuteQuery();
foreach (ListItem listItem in listItems)
Console.WriteLine("Id: {0} Title: {1}",
oListItem.Id, listItem["Title"]);
}
}
Certaines propriétés et méthodes retournent des objets ou des types de valeur qui ne dérivent pas de la classe ClientObject. Vous tirez profit de l’utilisation de l’identité de l’objet client pour accéder aux méthodes et aux propriétés uniquement lorsque ces méthodes et ces propriétés retournent des objets clients ou des collections de ces objets. Par exemple, certaines classes, comme la classe FieldUrlValue et la classe FieldLookupValue dérivent de la classe ClientValueObject, et vous ne pouvez pas utiliser les propriétés que retournent ces types avant l’appel à la méthode ExecuteQuery(). Certaines propriétés retournent des types .NET Framework, tels que chaîne ou entier, et vous ne pouvez pas utiliser les propriétés ou les méthodes qu’ils retournent avant l’appel à la méthode ExecuteQuery(). Comme vous ne pouvez pas utiliser les valeurs des propriétés tant que ces valeurs ne sont pas remplies par l’appel ExecuteQuery(), vous ne pouvez pas, par exemple, rechercher un élément dans une liste, ni utiliser la valeur de l’un des champs de cet élément pour sélectionner des éléments dans une requête supplémentaire. Si vous essayez d’utiliser une propriété avant qu’elle ne soit remplie par la méthode ExecuteQuery(), le modèle objet client génère une exception PropertyOrFieldNotInitializedException.
Attention |
---|
L’identité d’objet client n’est valide que pour un seul objet ClientContext. Si vous initialisez un autre objet ClientContext sur le même site SharePoint, vous ne pouvez pas utiliser des objets clients d’un contexte client avec l’autre. |
Plusieurs exemples ultérieurs dans cet article utilisent le comportement de l’identité d’objet.
Notes
Cet exemple ne fait aucun traitement d’erreur. Si la liste Annonces n’existe pas, le modèle objet client génère une exception dans l’appel à la méthode ExecuteQuery(). Vous devez prévoir d’intercepter les exceptions lors de l’écriture du code qui peut échouer si vous demandez des objets susceptibles de ne pas exister.
Limiter les jeux de résultats
SharePoint Foundation est souvent déployé dans des organisations ayant des milliers d’utilisateurs. Lors de la construction d’une application qui accède à SharePoint Foundation via le réseau, il est judicieux de la générer de sorte qu’elle provoque le moins de trafic réseau possible. Le modèle objet client peut vous y aider de plusieurs façons. L’approche la plus simple consiste à utiliser des expressions lambda pour spécifier exactement quelles propriétés le modèle objet client doit renvoyer à l’application.
L’exemple suivant montre comment spécifier qu’au moment où le modèle objet client charge l’objet du site, il doit charger uniquement les propriétés Title et Description. Cela réduit la taille de la réponse JSON que le serveur renvoie au client.
using System;
using Microsoft.SharePoint.Client;
class Program
{
static void Main()
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
Web site = clientContext.Web;
clientContext.Load(site,
s => s.Title,
s => s.Description);
clientContext.ExecuteQuery();
Console.WriteLine("Title: {0} Description: {1}",
site.Title, site.Description);
}
}
Par défaut, si vous n’incluez pas ces expressions lambda dans l’appel à la méthode Load(), un beaucoup plus grand nombre de propriétés sont chargées (mais pas toutes). Le premier des deux exemples a appelé la méthode Load() sans spécifier quelles propriétés charger, si bien que le paquet JSON que le serveur retourne est plus volumineux que nécessaire. Dans ces petits exemples, la différence est mineure, mais si des milliers d’éléments de liste peuvent être chargées, prenez soin de spécifier les propriétés requises afin de limiter le trafic réseau.
Lors de l’utilisation des expressions lambda, vous pouvez spécifier une liste de propriétés à la méthode Load(). La diminution du trafic réseau n’est pas le seul avantage dont vous bénéficiez en utilisant les expressions lambda du modèle objet client. Plus loin dans cet article, vous verrez comment filtrer les jeux de résultats à l’aide des expressions lambda.
Je vais vous présenter un exemple qui crée une liste et y ajoute un certain contenu. Cela vous donne un échantillon de contenu sur lequel travailler dans le reste de cet article.
Création et remplissage d’une liste
L’exemple suivant crée une liste, et y ajoute des champs et quelques éléments.
using System;
using Microsoft.SharePoint.Client;
class Program
{
static void Main()
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
Web site = clientContext.Web;
// Create a list.
ListCreationInformation listCreationInfo =
new ListCreationInformation();
listCreationInfo.Title = "Client API Test List";
listCreationInfo.TemplateType = (int)ListTemplateType.GenericList;
List list = site.Lists.Add(listCreationInfo);
// Add fields to the list.
Field field1 = list.Fields.AddFieldAsXml(
@"<Field Type='Choice'
DisplayName='Category'
Format='Dropdown'>
<Default>Specification</Default>
<CHOICES>
<CHOICE>Specification</CHOICE>
<CHOICE>Development</CHOICE>
<CHOICE>Test</CHOICE>
<CHOICE>Documentation</CHOICE>
</CHOICES>
</Field>",
true, AddFieldOptions.DefaultValue);
Field field2 = list.Fields.AddFieldAsXml(
@"<Field Type='Number'
DisplayName='Estimate'/>",
true, AddFieldOptions.DefaultValue);
// Add some data.
ListItemCreationInformation itemCreateInfo =
new ListItemCreationInformation();
ListItem listItem = list.AddItem(itemCreateInfo);
listItem["Title"] = "Write specs for user interface.";
listItem["Category"] = "Specification";
listItem["Estimate"] = "20";
listItem.Update();
listItem = list.AddItem(itemCreateInfo);
listItem["Title"] = "Develop proof-of-concept.";
listItem["Category"] = "Development";
listItem["Estimate"] = "42";
listItem.Update();
listItem = list.AddItem(itemCreateInfo);
listItem["Title"] = "Write test plan for user interface.";
listItem["Category"] = "Test";
listItem["Estimate"] = "16";
listItem.Update();
listItem = list.AddItem(itemCreateInfo);
listItem["Title"] = "Validate SharePoint interaction.";
listItem["Category"] = "Test";
listItem["Estimate"] = "18";
listItem.Update();
listItem = list.AddItem(itemCreateInfo);
listItem["Title"] = "Develop user interface.";
listItem["Category"] = "Development";
listItem["Estimate"] = "18";
listItem.Update();
clientContext.ExecuteQuery();
}
}
Dans de nombreux cas, où vous pouvez créer un objet client, l’application peut appeler une méthode Add qui accepte comme argument un objet qui spécifie les informations de création. Cet exemple montre comment utiliser la classe ListCreationInformation pour créer un objet List, et comment utiliser la classe ListItemCreationInformation pour créer un objet ListItem. Souvent, vous définissez les propriétés de la classe des informations de création après son instanciation. Vous pouvez constater que le code définit la propriété Title et la propriété TemplateType de l’objet ListItemCreationInformation. Notez que pour créer une liste, vous appelez la méthode Add(), mais pour créer un objet ListItem, vous appelez la méthode AddItem(). La méthode Add() crée une liste dans la collection, et la méthode AddItem() crée un simple élément de liste.
La création de champs dans une liste n’utilise pas non plus une méthode Add, parce que lorsque vous créez des champs, vous ne créez pas réellement une instance de la classe Field. Vous créez une instance d’une classe qui dérive de la classe Field. Il existe de nombreuses options pour ces classes dérivées, et utiliser une méthode Add compliquerait considérablement la conception d’une classe FieldCreationInformation. Pour cette raison, le modèle objet client n’inclut pas pas une telle classe. Le moyen le plus simple de créer un champ est plutôt de spécifier un bout de code XML qui définit le champ, et de passer ce code XML à la méthode AddFieldAsXml(). Il y a une méthode Add() que vous pouvez utiliser pour créer un champ, mais au lieu d’accepter un objet FieldCreationInformation, elle accepte un autre objet Field comme paramètre qu’elle utilise comme prototype du champ à créer. Cela est utile dans certains scénarios.
Conseil |
---|
La section Détection du schéma des champs de cet article vous montre un moyen simple de savoir quel code XML spécifier pour les champs que vous voulez créer. |
Notez, bien sûr, qu’aucun objet n’est en fait ajouté à la base de données SharePoint tant que l’application n’appelle pas la méthode ExecuteQuery().
Un autre point intéressant est à noter dans cet exemple : après l’appel à la méthode AddItem(), l’exemple définit trois propriétés indexées. Le code définit les valeurs des champs ajoutés précédemment à la liste. Après la définition de ces propriétés, l’application doit appeler la méthode Update(), informant le modèle objet client que ces objets ont été modifiés. Le modèle objet client ne fonctionne pas correctement si vous ne procédez pas ainsi. Vous voyez la méthode Update() utilisée dans des exemples ultérieurs, quand je montre comment modifier les objets clients existants.
Maintenant que vous disposez de certaines données, voyons quelques moyens intéressants de les interroger et de les changer.
Utilisation du langage CAML pour interroger des listes
L’exemple suivant montre comment utiliser CAML pour interroger la liste créée dans le dernier exemple. Cet exemple imprime les éléments Development de notre liste de test.
using System;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
List list = clientContext.Web.Lists.GetByTitle("Client API Test List");
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml =
@"<View>
<Query>
<Where>
<Eq>
<FieldRef Name='Category'/>
<Value Type='Text'>Development</Value>
</Eq>
</Where>
</Query>
<RowLimit>100</RowLimit>
</View>";
ListItemCollection listItems = list.GetItems(camlQuery);
clientContext.Load(
listItems,
items => items
.Include(
item => item["Title"],
item => item["Category"],
item => item["Estimate"]));
clientContext.ExecuteQuery();
foreach (ListItem listItem in listItems)
{
Console.WriteLine("Title: {0}", listItem["Title"]);
Console.WriteLine("Category: {0}", listItem["Category"]);
Console.WriteLine("Estimate: {0}", listItem["Estimate"]);
Console.WriteLine();
}
}
}
Cet exemple génère la sortie suivante :
Title: Develop proof-of-concept.
Category: Development
Estimate: 42
Title: Develop user interface.
Category: Development
Estimate: 18
Vous pouvez constater une différence entre les expressions lambda que vous spécifiez dans cet exemple et celles de l’exemple présenté dans la section Limiter les jeux de résultats. Vous devez utiliser la méthode d’extension Include() pour spécifier les propriétés que vous voulez charger pour chaque élément de la collection chargée. Le paramètre items de l’expression lambda est du type ListItemCollection, qui bien sûr ne contient pas une propriété indexée qui nous permet de spécifier quelles propriétés charger pour les éléments de la collection. Au lieu de cela, vous appelez la méthode d’extension Include(), qui permet de spécifier quels paramètres de cette collection d’enfants charger. Les paramètres des expressions lambda dans la méthode d’extension Include() sont du type des éléments de la collection. Par conséquent, vous pouvez spécifier les propriétés que vous voulez charger pour chaque élément de la collection.
Là encore, il n’est pas absolument nécessaire de comprendre la sémantique exacte de cette utilisation des expressions lambda. Rappelez-vous simplement de deux idiomes de codage :
Si vous demandez que le modèle objet client charge certaines propriétés d’un objet client (et pas une collection), spécifiez les propriétés dans l’expression lambda que vous ajoutez directement dans la méthode Load().
clientContext.Load(site,
s => s.Title,
s => s.Description);
Si vous demandez que le modèle objet client charge certaines propriétés pour chaque élément dans une collection d’objets clients, utilisez la méthode d’extension Include(), et passez les expressions lambda qui spécifient les propriétés voulues à la méthode Include().
clientContext.Load(
listItems,
items => items.Include(
item => item["Title"],
item => item["Category"],
item => item["Estimate"]));
Filtrage de la collection d’enfants renvoyée par le chargement à l’aide de LINQ
Comme la méthode d’extension Include() retourne IQueryable<T>, vous pouvez enchaîner de la méthode Include() dans la méthode d’extension IQueryable<T>.Where. Cela produit un moyen succinct de filtrer le jeu de résultats. Vous ne devez utiliser cette possibilité que pour interroger des collections d’objets clients autres que les collections d’objets ListItem, car bien que vous puissiez utiliser cette technique pour filtrer des collections d’objets ListItem, l’utilisation de CAML donne de meilleures performances. Il est important de souligner à nouveau le point suivant :
Attention |
---|
N’utilisez jamais la méthode d’extension IQueryable<T>.Where pour interroger des objets ListItem. En effet, le modèle objet client évalue d’abord le résultat de la requête CAML, récupère les résultats, puis filtre la collection résultante en utilisant LINQ. Si vous filtrez une très grande liste en utilisant LINQ au lieu de CAML, le modèle objet client essaie de récupérer tous les éléments de la liste avant de filtrer avec LINQ et émet des requêtes qui nécessitent trop de ressources système, ou bien la requête échoue. La raison n’est pas évidente si vous ne savez pas comment le modèle objet client fonctionne en interne. Vous devez utiliser CAML pour interroger des éléments de liste. |
L’exemple suivant interroge le modèle objet client à propos de toutes les listes non masquées. Notez que vous devez inclure une directive using pour l’espace de noms System.Linq.
using System;
using System.Linq;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
ListCollection listCollection = clientContext.Web.Lists;
clientContext.Load(
listCollection,
lists => lists
.Include(
list => list.Title,
list => list.Hidden)
. Where(list => ! list.Hidden)
);
clientContext.ExecuteQuery();
foreach (var list in listCollection)
Console.WriteLine(list.Title);
}
}
Sur mon serveur, cet exemple produit la sortie suivante :
Announcements
Calendar
Client API Test List
Content and Structure Reports
Customized Reports
Eric's ToDo List
Eric's Wiki
Form Templates
Links
Reusable Content
Shared Documents
Site Assets
Site Collection Documents
Site Collection Images
Site Pages
Style Library
Tasks
Team Discussion
Workflow Tasks
Utilisation de la méthode LoadQuery
La méthode LoadQuery() est similaire du point de vue fonctionnel à la méthode Load(), sauf que dans certaines circonstances, le modèle objet client peut traiter les requêtes plus efficacement et optimiser l’utilisation de la mémoire. Elle permet par ailleurs un style de programmation plus souple.
La méthode LoadQuery() propose une sémantique différente de la méthode Load(). Tandis que la méthode Load() remplit l’objet client (ou la collection d’objets clients) avec les données du serveur, la méthode LoadQuery() remplit et retourne une nouvelle collection. Cela signifie que vous pouvez interroger la même collection d’objet plusieurs fois et conserver des jeux de résultats distincts pour chaque requête. Par exemple, vous pouvez interroger tous les éléments d’une liste de projets qui sont attribués à une certaine personne, séparément interroger tous les éléments dont les heures estimées dépassent un certain seuil, et accéder aux deux jeux de résultats à la fois. Cela vous permet également de laisser ces collections devenir hors de portée, et ainsi éligibles pour la récupération de mémoire. Les collections que vous chargez à l’aide de la méthode Load() sont éligibles pour la récupération de mémoire seulement si la variable du contexte client devient elle-même hors de portée. Mises à part ces différences, la méthode LoadQuery() offre la même fonctionnalité que la méthode Load().
L’exemple suivant utilise la méthode LoadQuery() pour récupérer une liste de toutes les listes du site.
using System;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
Web site = clientContext.Web;
ListCollection lists = site.Lists;
IEnumerable<List> newListCollection = clientContext.LoadQuery(
lists.Include(
list => list.Title,
list => list.Id,
list => list.Hidden));
clientContext.ExecuteQuery();
foreach (List list in newListCollection)
Console.WriteLine("Title: {0} Id: {1}",
list.Title.PadRight(40), list.Id.ToString("D"));
}
}
Notez que la méthode LoadQuery() retourne une nouvelle collection de listes sur laquelle vous pouvez itérer. Cette collection a le type IEnumerable<List> au lieu de ListCollection.
Il y a un aspect dans la sémantique de la méthode LoadQuery() qui mérite votre attention. Dans l’exemple précédent, les valeurs des propriétés de la variable des listes d’origine ne sont pas remplies au retour de la méthode ExecuteQuery(). Si vous souhaitez que cette liste soit remplie, vous devez explicitement appeler la méthode Load(), en indiquant quelles propriétés charger.
Amélioration des performances en imbriquant des instructions Include dans LoadQuery
Dans l’appel à la méthode LoadQuery(), vous pouvez spécifier plusieurs niveaux de propriétés à charger. Cela permet au modèle objet client d’optimiser son accès au serveur qui exécute SharePoint Foundation en diminuant le nombre de fois où le modèle doit appeler le serveur qui exécute SharePoint Foundation pour récupérer les données voulues. La requête suivante récupère toutes les listes du site, et tous les champs de chaque liste. Elle les affiche ensuite sur la console et indique si chaque liste ou champ est masqué.
using System;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;
class Program
{
static void Main()
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
IEnumerable<List> lists = clientContext.LoadQuery(
clientContext.Web.Lists.Include(
list => list.Title,
list => list.Hidden,
list => list.Fields.Include(
field => field.Title,
field => field.Hidden)));
clientContext.ExecuteQuery();
foreach (List list in lists)
{
Console.WriteLine("{0}List: {1}",
list.Hidden ? "Hidden " : "", list.Title);
foreach (Field field in list.Fields)
Console.WriteLine(" {0}Field: {1}",
field.Hidden ? "Hidden " : "",
field.Title);
}
}
}
Cette approche rend la partie serveur du modèle objet client plus efficace que si l’application avait d’abord chargé une liste de listes, puis chargé les champs de chaque liste.
Filtrage de la collection d’enfants renvoyée par LoadQuery à l’aide de LINQ
La méthode LoadQuery() accepte un objet de type IQueryable<T> comme paramètre, ce qui vous permet d’écrire des requêtes LINQ au lieu de CAML pour filtrer les résultats. Cet exemple retourne une collection de toutes les bibliothèques de documents non masquées.
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
ListCollection listCollection = clientContext.Web.Lists;
IEnumerable<List> hiddenLists = clientContext.LoadQuery(
listCollection
. Where(list => !list.Hidden &&
list.BaseType == BaseType.DocumentLibrary));
clientContext.ExecuteQuery();
foreach (var list in hiddenLists)
Console.WriteLine(list.Title);
}
}
Mise à jour des objets clients
La mise à jour des objets clients à l’aide du modèle objet client est assez simple. Vous récupérez les objets, modifiez des propriétés, appelez la méthode Update pour chaque objet que vous modifiez, puis appelez la méthode ExecuteQuery(). L’exemple suivant modifie des éléments dans la liste Client API Test List, en améliorant l’estimation pour l’ensemble des éléments du développement de 50 % (une opération courante).
using System;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
List list = clientContext.Web.Lists.GetByTitle("Client API Test List");
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml =
@"<View>
<Query>
<Where>
<Eq>
<FieldRef Name='Category'/>
<Value Type='Text'>Development</Value>
</Eq>
</Where>
</Query>
<RowLimit>100</RowLimit>
</View>";
ListItemCollection listItems = list.GetItems(camlQuery);
clientContext.Load(
listItems,
items => items.Include(
item => item["Category"],
item => item["Estimate"]));
clientContext.ExecuteQuery();
foreach (ListItem listItem in listItems)
{
listItem["Estimate"] = (double)listItem["Estimate"] * 1.5;
listItem.Update();
}
clientContext.ExecuteQuery();
}
}
Suppression des objets clients
La suppression des objets clients est tout aussi simple. Cependant, il y a une dynamique importante autour de la suppression des objets clients d’une collection d’objets clients. Vous ne pouvez pas itérer sur la collection pour supprimer les objets. Dès que vous supprimez le premier objet, cela provoque un dysfonctionnement de l’itérateur de la collection d’objets clients. L’itérateur peut générer une exception, ou terminer silencieusement sans visiter tous les éléments de la collection. Vous devez plutôt matérialiser la collection dans une List<T> à l’aide de la méthode ToList, puis itérer sur cette liste en supprimant les objets clients.
L’exemple suivant supprime des éléments de test de notre Client API Test List. Il montre l’utilisation de la méthode ToList pour matérialiser la collection avant d’itérer sur celle-ci :
using System;
using System.Linq;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
ClientContext clientContext = new ClientContext("http://intranet.contoso.com");
List list = clientContext.Web.Lists.GetByTitle("Client API Test List");
CamlQuery camlQuery = new CamlQuery();
camlQuery.ViewXml =
@"<View>
<Query>
<Where>
<Eq>
<FieldRef Name='Category'/>
<Value Type='Text'>Test</Value>
</Eq>
</Where>
</Query>
<RowLimit>100</RowLimit>
</View>";
ListItemCollection listItems = list.GetItems(camlQuery);
clientContext.Load(
listItems,
items => items.Include(
item => item["Title"]));
clientContext.ExecuteQuery();
foreach (ListItem listItem in listItems.ToList())
listItem.DeleteObject();
clientContext.ExecuteQuery();
}
}
L’exemple de code suivant illustre l’approche incorrecte.
clientContext.Load(
listItems,
items => items.Include(
item => item["Title"]));
clientContext.ExecuteQuery();
// The ToList() method call is removed in the following line.
foreach (ListItem listItem in listItems)
listItem.DeleteObject();
clientContext.ExecuteQuery();
Finalement, pour libérer la liste Client API Test List, voici un exemple qui supprime la liste et ses éléments.
using System;
using Microsoft.SharePoint.Client;
class DisplayWebTitle
{
static void Main()
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
clientContext.Web.Lists.GetByTitle("Client API Test List")
.DeleteObject();
clientContext.ExecuteQuery();
}
}
Détection du schéma des champs
Comme promis, cette section présente un moyen simple de déterminer le schéma XML à utiliser pour créer les champs voulus dans une liste. Créez d’abord, sur le site SharePoint, une liste qui contient des colonnes configurées comme vous le souhaitez. Vous pouvez ensuite utiliser l’exemple suivant pour générer le code XML qui crée ces champs.
L’exemple suivant imprime les schémas des champs que j’ai ajoutés à la liste Client API Test List.
using System;
using System.Linq;
using System.Xml.Linq;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
List list = clientContext.Web.Lists
.GetByTitle("Client API Test List");
clientContext.Load(list);
FieldCollection fields = list.Fields;
clientContext.Load(fields);
clientContext.ExecuteQuery();
foreach (var f in fields)
{
XElement e = XElement.Parse(f.SchemaXml);
string name = (string)e.Attribute("Name");
if (name == "Category" || name == "Estimate")
{
e.Attributes("ID").Remove();
e.Attributes("SourceID").Remove();
e.Attributes("ColName").Remove();
e.Attributes("RowOrdinal").Remove();
e.Attributes("StaticName").Remove();
Console.WriteLine(e);
Console.WriteLine("===============");
}
}
}
}
Lorsque vous exécutez cela après avoir créé la liste en utilisant le programme d’exemple de la section Création et remplissage d’une liste, vous obtenez le résultat suivant.
<Field Type="Choice" DisplayName="Category" Format="Dropdown" Name="Category">
<Default>Specification</Default>
<CHOICES>
<CHOICE>Specification</CHOICE>
<CHOICE>Development</CHOICE>
<CHOICE>Test</CHOICE>
<CHOICE>Documentation</CHOICE>
</CHOICES>
</Field>
===============
<Field Type="Number" DisplayName="Estimate" Name="Estimate" />
===============
L’exemple supprime des attributs qui ne sont pas requis pour créer le champ.
Accès à des grandes listes
Les instructions de développement SharePoint indiquent de ne pas essayer de récupérer plus de 2 000 éléments dans une même requête. Si c’est une possibilité dans votre application, envisagez d’utiliser l’élément RowLimit dans vos requêtes CAML pour limiter la quantité de données que le modèle objet client récupère pour votre application. Vous devez parfois accéder à tous les éléments dans une liste qui peut en contenir plus de 2 000. Dans ce cas, il est recommandé de parcourir les 2 000 éléments à la fois. Cette section présente une approche de pagination à l’aide de la propriété ListItemCollectionPosition.
using System;
using System.Linq;
using Microsoft.SharePoint.Client;
class Program
{
static void Main()
{
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
List list = clientContext.Web.Lists
.GetByTitle("Client API Test List");
// First, add 20 items to Client API Test List so that there are
// enough records to show paging.
ListItemCreationInformation itemCreateInfo =
new ListItemCreationInformation();
for (int i = 0; i < 20; i++)
{
ListItem listItem = list.AddItem(itemCreateInfo);
listItem["Title"] = String.Format("New Item #{0}", i);
listItem["Category"] = "Development";
listItem["Estimate"] = i;
listItem.Update();
}
clientContext.ExecuteQuery();
// This example shows paging through the list ten items at a time.
// In a real-world scenario, you would want to limit a page to
// 2000 items.
ListItemCollectionPosition itemPosition = null;
while (true)
{
CamlQuery camlQuery = new CamlQuery();
camlQuery.ListItemCollectionPosition = itemPosition;
camlQuery.ViewXml =
@"<View>
<ViewFields>
<FieldRef Name='Title'/>
<FieldRef Name='Category'/>
<FieldRef Name='Estimate'/>
</ViewFields>
<RowLimit>10</RowLimit>
</View>";
ListItemCollection listItems = list.GetItems(camlQuery);
clientContext.Load(listItems);
clientContext.ExecuteQuery();
itemPosition = listItems.ListItemCollectionPosition;
foreach (ListItem listItem in listItems)
Console.WriteLine(" Item Title: {0}", listItem["Title"]);
if (itemPosition == null)
break;
Console.WriteLine(itemPosition.PagingInfo);
Console.WriteLine();
}
}
}
Cet exemple produit la sortie suivante :
Item Title: Write specs for user interface.
Item Title: Develop proof-of-concept.
Item Title: Write test plan for user interface.
Item Title: Validate SharePoint interaction.
Item Title: Develop user interface.
Item Title: New Item #0
Item Title: New Item #1
Item Title: New Item #2
Item Title: New Item #3
Item Title: New Item #4
Paged=TRUE&p_ID=10
Item Title: New Item #5
Item Title: New Item #6
Item Title: New Item #7
Item Title: New Item #8
Item Title: New Item #9
Item Title: New Item #10
Item Title: New Item #11
Item Title: New Item #12
Item Title: New Item #13
Item Title: New Item #14
Paged=TRUE&p_ID=20
Item Title: New Item #15
Item Title: New Item #16
Item Title: New Item #17
Item Title: New Item #18
Item Title: New Item #19
Traitement asynchrone
Si vous construisez une application qui doit joindre des sites SharePoint qui peuvent être indisponibles, ou si vous devez régulièrement soumettre des requêtes qui peuvent durer, vous devez envisager d’utiliser un traitement asynchrone. Ainsi votre application peut rester réactive pendant que la requête s’exécute sur un autre thread. Dans le thread principal, vous pouvez définir un minuteur afin de savoir si la requête dure plus longtemps que le seuil voulu, tenir l’utilisateur informé sur le statut de la requête, et lorsque finalement la requête s’achève, afficher les résultats.
La version JavaScript du modèle objet client et la version Silverlight (lorsqu’il modifie l’interface utilisateur) utilisent un traitement asynchrone. La rubrique Data Retrieval Overview du kit de développement de SharePoint 2010 contient des exemples sur la façon d’utiliser un traitement asynchrone avec JavaScript et Silverlight.
Lors de la génération d’une application traditionnelle basée sur .NET Framework, telle qu’une application Windows Forms ou WPF, vous pouvez utiliser un traitement asynchrone. L’exemple suivant utilise la méthode BeginInvoke pour exécuter une requête de façon asynchrone. Notez que le code passe une expression lambda d’instruction à la méthode BeginInvoke, qui rend commode la création de ce mode, car l’expression lambda d’instruction peut référer aux variables automatiques dans la méthode qui la contient. Vous pouvez constater que l’expression lambda d’instruction a accès à la variable clientContext et à la variable newListCollection . Avec les clôtures Microsoft Visual C#, le langage fonctionne comme voulu.
using System;
using System.Collections.Generic;
using Microsoft.SharePoint.Client;
class Program
{
static void Main(string[] args)
{
AsynchronousAccess asynchronousAccess = new AsynchronousAccess();
asynchronousAccess.Run();
Console.WriteLine("Before exiting Main");
Console.WriteLine();
Console.WriteLine("In a real application, the application can");
Console.WriteLine("continue to be responsive to the user.");
Console.WriteLine();
Console.ReadKey();
}
}
class AsynchronousAccess
{
delegate void AsynchronousDelegate();
public void Run()
{
Console.WriteLine("About to start a query that will take a long time.");
Console.WriteLine();
ClientContext clientContext =
new ClientContext("http://intranet.contoso.com");
ListCollection lists = clientContext.Web.Lists;
IEnumerable<List> newListCollection = clientContext.LoadQuery(
lists.Include(
list => list.Title));
AsynchronousDelegate executeQueryAsynchronously =
new AsynchronousDelegate(clientContext.ExecuteQuery);
executeQueryAsynchronously.BeginInvoke(
arg =>
{
clientContext.ExecuteQuery();
Console.WriteLine("Long running query completed.");
foreach (List list in newListCollection)
Console.WriteLine("Title: {0}", list.Title);
}, null);
}
}
Cet exemple produit la sortie suivante :
About to start a query that will take a long time.
Before exiting Main
In a real application, the application can
continue to be responsive to the user.
Long running query completed.
Title: Announcements
Title: Cache Profiles
Title: Calendar
Title: Client API Test List
Title: Content and Structure Reports
Title: Content type publishing error log
Title: Converted Forms
Title: Customized Reports
Title: Eric's ToDo List
Title: Eric's Wiki
Title: Form Templates
Title: Links
Ressources supplémentaires
Pour plus d’informations, consultez les ressources suivantes :