Développer un add-in Word pour SharePoint

Dans cet article, vous découvrirez comment récupérer des données d'une ferme SharePoint dans un document Word au travers d'un exemple simple.

- Titre : Développer un add-in Word pour SharePoint

- Parution : 20 décembre 2008

- Licence : Copyright ®  Developpez. 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

- I. Pré-requis

- II. Introduction

- III. L'exemple en image

- IV. Avant de commencer....

- V. Création du projet pas à pas

- V-A. Création du projet Visual Studio 2008

- V-B. Ajout de la référence Web

- V-C. Ajout du task pane

- V-D. Ajout du ruban

- VI. Tester le projet

- VII. Téléchargement

I - Pré-requis

Les pré-requis pour pouvoir recréer le projet présenté dans cet article sont :

- disposer d'en environnement SharePoint 2007

- disposer de Visual Studio 2008

- connaître un peu .NET :). Aucune connaissance de SharePoint n'est nécessaire. Le niveau de l'article est très simple.

II - Introduction

SharePoint étant au centre des produits Office, son interaction avec ceux-ci ne va cesser de croître. En plus de bénéficier des intégrations standards, vous avez la possibilité de créer vos propres add-ins pour effectuer toutes sortes d'opérations. Le but de cet article est de vous présenter un exemple dont le scénario est le suivant :

Vous disposez d'une liste de clients dans SharePoint contenant leurs coordonnées et adresses. Vous souhaitez pouvoir récupérer facilement celles-ci lorsque vous créez un document Word destiné à être envoyé à vos clients.

Bien que simpliste, cet exemple fournit néanmoins les bases nécessaires pour appréhender l'interaction entre Office et SharePoint.

III - L'exemple en image

La liste des clients stockés dans SharePoint :

Le ruban Word avec un bouton permettant d'ouvrir le custom task pane :

Le custom task pane permettant d'effectuer une recherche dans la liste des clients et d'effectuer un choix.

Enfin, la fiche client qui est insérée dans le document lorsque le choix est fait.

IV - Avant de commencer....

Avant de commencer à réaliser le projet pas à pas, il me paraît intéressant de s'attarder une minute sur l'environnement architectural dans lequel évolue un add-in Office s'il est en interaction avec un serveur SharePoint.

Dans l'hypothèse où l'add-in va chercher directement ses données dans SharePoint, le contexte d'exécution se déroulera dans un environnement client-serveur simple. Même si cela paraît évident, ceci force donc le développeur à faire appel aux services web de SharePoint ou à développer son propre service et à le déployer au sein de la ferme.

En effet, l'API SharePoint ne peut être utilisée dans ce contexte car les clients Office sont externes à la ferme SharePoint. Ceci a pour impact de compliquer un peu la tâche :) car les services web standard sont plus difficiles à manipuler que l'API.

Donc, en conclusion, lorsque vous développez un add-in Office qui doit interagir avec SharePoint, vous avez deux possibilités :

- Utiliser les services web standard de SharePoint

- Développer votre propre service web si les besoins en terme d'interaction ne peuvent pas être couverts facilement par l''utilisation des services standard. Si vous optez pour ce choix, vous devrez également penser à déployer votre service sur tous les frontaux de la ferme.

V - Création du projet pas à pas

Dans cet article, nous utiliserons le service web standard lists.asmx proposé par SharePoint pour récupérer les fiches clients.

V-A - Création du projet Visual Studio 2008

Démarrez Visual Studio 2008 et créez un nouveau projet de type Word add-in comme illustré ci-dessous :

Le projet crée automatiquement la classe principale nommée ThisAddin.cs contenant certaines méthodes. Ajoutez ce code dans la méthode ThisAddin_Startup

Word.Document DocumentWord = Globals.ThisAddIn.Application.ActiveDocument as Word.Document;
Globals.ThisAddIn.CustomTaskPanes.Add(
new ClientControl(), "Controle Client", DocumentWord.ActiveWindow);

Il s'agit simplement d'ajouter le contrôle utilisateur ClientControl dans la collection de taskpanes de l'add-in. Nous créerons ce contrôle un peu plus loin dans cet article.

V-B - Ajout de la référence Web

Le projet doit communiquer avec le service standard lists.asmx de SharePoint. Il faut donc ajouter une référence web. Cliquez sur References puis sur Add Service Reference.

Cliquez sur le bouton Advanced :

Et ensuite sur Add Web Reference :

Et insérez-y vos paramètres, c'est à dire http://votre site SP/_vti_bin/lists.asmx et SPService comme nom de référence et terminez en cliquant sur Add Reference.

V-C - Ajout du task pane

Le task pane n'est qu'un simple contrôle utilisateur. Il est utilisé pour présenter l'interface graphique à l'utilisateur. Cliquez sur le bouton droit de votre projet et choisissez Ajouter puis Nouvel Elément. Ensuite, procédez comme illustré :

Disposez à présent quelques contrôles afin d'obtenir ceci :

- (1) : TextBox, Nom : SiteUrl

- (2) : TextBox, Nom : Nom

- (3) : TextBox, Nom : Prenom

- (4) : Button, Nom : OkBtn

- (5) : ComboBox, Nom : ComboClients

- (6) : Label, Nom : NbClients

Double-cliquez sur le bouton OkBtn et ajoutez ce code :

DataTable TableClients = new DataTable();
TableClients.Columns.Add("ID");
TableClients.Columns.Add("Nom Complet");
TableClients.Rows.Add("", "");
try
{
XmlNode Items = GetListItems(
string.Format(
"<Where><Or><Contains><FieldRef Name=\"Nom\" />
<Value Type=\"Text\">{0}</Value></Contains><Contains>
<FieldRef Name=\"Pr_x00e9_nom\" />
<Value Type=\"Text\">{1}</Value></Contains></Or></Where>",
Nom.Text, Prenom.Text));

XmlNodeList Clients = Items.SelectNodes(
string.Format("{0}{1}",
"//*[local-name() = 'data' and namespace-uri() = 'urn:schemas-microsoft-com:rowset']",
"/*[local-name() = 'row' and namespace-uri() = '#RowsetSchema']"));
NbClients.Text = string.Format("Nombre de clients : {0}", Clients.Count);
if (Clients.Count > 0)
{
for (int i = 0; i < Clients.Count; i++)
{
TableClients.Rows.Add(
Clients[i].Attributes["ows_ID"].Value,
string.Format("{0} {1}",
Clients[i].Attributes["ows_Nom"].Value,
Clients[i].Attributes["ows_Pr_x00e9_nom"].Value
));

}
TableClients.AcceptChanges();
}
}
catch (SoapException WebSrvEx)
{
MessageBox.Show(
string.Format(
"Problème de communication avec le service lists.asmx : {0}",
WebSrvEx.Message));
}
ComboClients.DisplayMember = "Nom Complet";
ComboClients.ValueMember = "ID";
ComboClients.DataSource = TableClients;

Ce code crée une datatable pour stocker le retour du service web auquel il envoie une requête CAML. Cette requête CAML correspond au SQL suivant :

SELECT Nom,Prenom,Adresse FROM Clients WHERE Nom LIKE '%..%' OR Prenom LIKE '%...%'

Ensuite, le retour est traité par XPATH et la datatable est remplie et liée au ComboBox. Ce code fait appel à la méthode GetListItems qu'il nous faut encore implémenter. Insérez le code suivant au dessus de la méthode OkBtn_Click().

private XmlNode GetListItems(string SpQuery)
{
SPListService.Lists SPService = new SPListService.Lists();
SPService.Url = SiteUrl.Text + "_vti_bin/lists.asmx";
SPService.Credentials = System.Net.CredentialCache.DefaultNetworkCredentials;

XmlNode Lists = SPService.GetListCollection();
XPathNavigator Navigator = Lists.CreateNavigator();
XPathNavigator ClientAdresse =
Navigator.SelectSingleNode(string.Format("//*[@Title='{0}']", ListName));

if (ClientAdresse != null)
{
XmlDocument Doc = new XmlDocument();
XmlNode NoeudViewFields =
Doc.CreateNode(XmlNodeType.Element, "ViewFields", "");
XmlNode QueryOptionsNode =
Doc.CreateNode(XmlNodeType.Element, "QueryOptions", "");
QueryOptionsNode.InnerXml = "<IncludeMandatoryColumns>FALSE</IncludeMandatoryColumns>";

XmlNode SearchQuery = Doc.CreateNode(
XmlNodeType.Element, "Query", "");
NoeudViewFields.InnerXml = "<FieldRef Name='ID'/><FieldRef Name='Nom'/><FieldRef Name='Pr_x00e9_nom'/>";
SearchQuery.InnerXml = SpQuery;
XmlNode Items = SPService.GetListItems(ListName, null, SearchQuery, NoeudViewFields, null, QueryOptionsNode, null);
return Items;
}
else
{
MessageBox.Show("La liste d'adresse n'a pu être trouvée");
}
return null;
}

Cette méthode communique avec le service web lists.asmx et exécute la requête SpQuery passée en argument en ayant au prélable construit tous les noeuds XML requis pour communiquer avec le service. Notez qu'aucun contrôle spécifique n'est effectué sur l'URL du site contenue dans la zone de texte SiteUrl afin de ne pas alourdir la lisibilité. Bien sûr, dans un contexte réel, vous devriez impérativement rendre ce code plus robuste.

Ajoutez à présent en en-tête de classe les deux constantes utilisées par GetListItems :

const string XmlNs = "";
const string ListName = "Clients";

Enfin, il ne reste plus qu'à associer un évènement au ComboBox afin d'insérer la fiche client dans le document lorsqu'une entrée est sélectionnée par l'utilisateur. Double-cliquez sur le ComboBox et insérez ce code :

StringBuilder FicheClient=null;
if (!String.IsNullOrEmpty(ComboClients.SelectedValue.ToString()))
{
FicheClient = new StringBuilder();
try
{
XmlNode Items =
GetListItems(
string.Format(
"<Where><Eq><FieldRef Name=\"ID\" /><Value Type=\"Counter\">{0}</Value></Eq></Where>",
ComboClients.SelectedValue.ToString()));

XmlNodeList Clients = Items.SelectNodes(
string.Format("{0}{1}",
"//*[local-name() = 'data' and namespace-uri() = 'urn:schemas-microsoft-com:rowset']",
"/*[local-name() = 'row' and namespace-uri() = '#RowsetSchema']"));

if (Clients != null && Clients.Count == 1)
{
FicheClient.AppendFormat("Nom : {0}\r\n", Clients[0].Attributes["ows_Nom"].Value);
FicheClient.AppendFormat("Prénom : {0}\r\n", Clients[0].Attributes["ows_Pr_x00e9_nom"].Value);
FicheClient.AppendFormat("Adresse : {0}\r\n", Clients[0].Attributes["ows_Adresse"].Value);
Word.Range currentRange = Globals.ThisAddIn.Application.Selection.Range;
currentRange.Text = FicheClient.ToString();
}
else
{
MessageBox.Show("Problème lors de la réception de la fiche client");
}
}
catch (SoapException WebSrvEx)
{
MessageBox.Show(
string.Format(
"Problème de communication avec le service lists.asmx : {0}",
WebSrvEx.Message));
}
}

L'ID du client est retourné par le ComboBox. Cette méthode exécute donc GetListItems pour récupérer la fiche client correspondante à l'ID sélectionné. Ensuite, cette fiche est insérée dans le document.

V-D - Ajout du ruban

Ajoutez un ruban en cliquant sur votre projet avec le bouton droit et choisissez Ajouter puis Nouvel Elément. Ensuite, procédez comme illustré :

Ajoutez ensuite un bouton au ruban et ajoutez-lui le code suivant :

CustomTaskPane ClientCtrl = Globals.ThisAddIn.CustomTaskPanes[0];
if (ClientCtrl != null)
{
ClientCtrl.Visible = !ClientCtrl.Visible;
}

Le but étant simplement d'afficher ou de masquer le task pane.

Note: pour associer une image Office à votre bouton, vous pouvez utiliser un outil très pratique ici. L'outil en question est le VSTO_PTRibbonIDs.exe qui permet de parcourir la galerie d'images et de récupérer leur ID.

VI - Tester le projet

Pour tester l'addin, il suffit de démarrer le projet depuis Visual Studio. Word démarre automatiquement et le ruban est ajouté. Pour "nettoyer", il faut faire un Clean depuis Visual Studio.

Notez que ceci n'est pas suffisant pour déployer l'add-in sur des machines clientes. Pour ce faire, vous devrez créer un projet d'installation.

VII - Téléchargement

Le projet ainsi qu'un template de la liste utilisée (template en anglais) sont disponibles en téléchargement ici.