mercredi 5 août 2009

Accéder à IIS

Il arrive fréquemment, que lors de vos développements, vous deviez interagir avec un dossier relatif à IIS. Effectivement, comme vous le savez, SharePoint utilise des dossiers "à lui" contenus principalement dans le dossier 12. Cependant, SharePoint utilise également beaucoup de fonctionnalités relatives à l'ASP.NET et ayant souvent attrait à IIS lui même, donc au dossier C:\inetpub\wwwroot\wss\VirtualDirectories. Dans ce dossier se trouvent d'autres dossiers contenant chacun les site collections de SharePoint.

Les exemples ne manquent pas pour illustrer la problématique. Par exemple, si vous écrivez un "Control Adapter", vous devrez déployer un fichier .browser dans le dossier App_Browsers d'IIS. Si vous utilisez des ressources dans une de vos pages applicatives et que vous voulez les utiliser directement dans le code ASP de cette page, vous devrez les déployer dans le dossier App_GlobalResources,...

Pour faire tout cela, il existe une fonction qui se nomme ApplyApplicationContentToLocalServer. Cette fonction s'exécute au niveau de la ferme et possède certain désavantage. Effectivement, celle-ci est prévu pour fonctionner exclusivement de la manière dont elle a été programmée, donc elle gère les fichiers ressources, les fichiers de sitemap mais, je ne pense pas qu'elle gère les fichiers .browser. D'un manière générale, cette fonction s'exécute sans nous permettre d'interagir sur la manière dont les fichiers seront copiés,... En plus, les fichiers seront copiés dans TOUS les dossiers d'IIS, ce qui n'est pas toujours le comportement désiré.

Il existe une alternative permettant d'accéder au dossier d'IIS et qui plus est, ne nécessite qu'une feature de Web Application et non une feature de ferme. Nous allons voir cela par un simple exemple. Cette feature sera un event handler de feature permettant simplement d'exécuter la copie de certains fichiers dans les répertoires adéquat d'IIS. Pour cela, commencez par créer un projet WSP Builder vide à l'aide de Visual Studio 2008. Nous allons ensuite ajouter une feature de Web Application. Pour cela, cliquez avec le bouton droit de votre souris sur votre projet choisissez Add > New Item. Dans la partie gauche de la fenêtre, choisissez WSP Builder et Blank feature dans la partie de droite. Nommez cette feature WebAppCopy.



Dans la fenêtre qui apparait ensuite, remplissez les champs de cette manière :



Ici, étant donné que notre feature ne fera rien, il n'est pas utile de garder le fichier elements.xml, supprimez le donc du dossier de votre feature. Ouvrez ensuite le fichier feature.xml pour supprimer la référence au fichier que nous venons de supprimer. Modifiez donc ce passage :

<ElementManifests>
<ElementManifest Location="elements.xml" />
</ElementManifests>


Pour qu'il ne devienne que :

<ElementManifests />


Nous allons maintenant ajouter un event handler à cette feature pour exécuter du code à son activation. Cliquez donc avec le bouton droit de votre souris sur votre projet et choisissez Add > Class et nommez cette classe WebAppCopy.cs. Avant de continuer, nous allons devoir ajouter une référence à SharePoint pour le projet. Cliquez donc avec le bouton droit sur le dossier References de votre projet et choisissez Add Reference. Dans la liste qui apparaît, choisissez Windows SharePoint Services et validez votre choix. Avant de continuer, nous allons créer des fichiers "factices" à déployer dans les répertoires d'IIS. Ceux ci ne serviront à rien d'autre que de montrer que le déploiement a fonctionné. Créez donc un dossier que vous appellerez "Files" dans le dossier de votre feature. Dans ce fichier, ajoutez deux fichiers texte que vous nommerez "file1.txt" et "file2.txt". Rentrez ce que vous désirez dans ces fichiers.

Ouvrez ensuite le fichier .cs que vous venez de créer. La première chose à faire est d'ajouter les directives suivantes :

using Microsoft.SharePoint;
using Microsoft.SharePoint.Administration;
using System.IO;
using Microsoft.SharePoint.Utilities;


La deuxième chose à faire est de permettre à notre classe de surcharger les fonctions de l'activation d'une feature. Pour cela, faites la dériver de la classe SPFeatureReceiver et surchargez les 4 méthodes de cette classe :

public class WebAppCopy : SPFeatureReceiver
{

public override void FeatureActivated(SPFeatureReceiverProperties properties)
{

}

public override void FeatureDeactivating(SPFeatureReceiverProperties properties)
{

}

public override void FeatureInstalled(SPFeatureReceiverProperties properties) {}
public override void FeatureUninstalling(SPFeatureReceiverProperties properties) {}


}


Ici, nous ne surchargerons que les deux premières méthodes, c'est à dire l'activation et la désactivation de la feature. Commençons d'abord par la fonction FeatureActivated et tapez :

SPWebApplication CurrentWebApp = properties.Feature.Parent as SPWebApplication;

if (CurrentWebApp != null)
{

foreach (SPUrlZone Zone in CurrentWebApp.IisSettings.Keys)
{

SPIisSettings CurrentSettings = CurrentWebApp.IisSettings[Zone];

string sourcePath = string.Format("{0}\FEATURES\WebAppCopy\Files\file1.txt", SPUtility.GetGenericSetupPath("Template"));
string destinationPath = Path.Combine(CurrentSettings.Path.ToString(), "App_GlobalResourcesfile1.txt");
File.Copy(sourcePath, destinationPath, true);

sourcePath = string.Format("{0}\FEATURES\WebAppCopy\Files\file2.txt", SPUtility.GetGenericSetupPath("Template"));
destinationPath = Path.Combine(CurrentSettings.Path.ToString(), "_app_binfile2.txt");
File.Copy(sourcePath, destinationPath, true);


}


}
else
throw new ApplicationException("The Web Application object is null.");



Vous allez voir, ce n'est pas très compliqué. Tout d'abord, nous récupérons une référence vers l'objet SPWebApplication identifiant la Web Application sur laquelle la feature est activée. Effectivement, la feature étant "scopée" au niveau de la Web Application, la propriété Parent de cette feature contiendra une référence à cette Web Application. Étant donné qu'il existe 4 scopes différents, nous sommes obligés de caster cette propriété.

Tout d'abord, nous faisons une simple vérification pour voir si la Web Application n'est pas nulle. Si c'est le cas, nous itérons sur le contenu de la collection Keys de la propriété IisSettings de l'objet référençant la Web Application. Dans la boucle, nous récupérons l'objet SPIisSettings courant. Pour cela, nous faisons passer la clé courante dans la collection IisSettings. Un objet de type SPIisSettings contient plusieurs propriétés permettant d'en savoir plus sur IIS, ici, nous n'utiliserons qu'une seule de ces propriétés.

Les 6 lignes suivantes servent à copier les deux fichiers dans les répertoires adéquats d'IIS. Nous n'expliquerons que les 3 premières étant donné que les 3 suivantes sont les mêmes, mais copient l'autre fichier dans un dossier différent d'IIS. La première ligne permet de constituer le chemin d'accès vers le premier fichier à copier. Effectivement, il ne suffit pas de taper son nom. Ici, la subtilité est de récupérer le chemin d'accès vers le dossier TEMPLATE de SharePoint. Pour cela, nous utilisons la fonction GetGenericSetupPath de la classe SPUtility. Nous faisons passer en paramètre le nom du dossier que nous voulons récupérer et cette fonction nous renvoie le chemin d'accès vers ce dossier. Nous concaténons ce chemin d'accès avec le reste de l'emplacement du fichier "file1.txt". La deuxième ligne va nous permettre de constituer la destination du fichier, c'est donc ici que nous allons récupérer le chemin d'accès vers le dossier courant du dossier IIS. Comme vous le voyez, nous utilisons la propriété Path de l'objet SPIisSettings.

Effectivement, la collection IisSettings va contenir une référence à tous les dossiers IIS relatifs à la Web Application. Donc dans le cas où 2 site collections sont attachées à la même Web Application, nous sommes obligé de boucler sur la collection pour copier les fichiers dans les deux dossiers. Une fois que nous avons la source et la destination, il nous suffit de faire passer ces deux paramètres dans la fonction Copy de la classe statique File. Le troisième argument définit si les fichiers seront écrasés ou non, nous la mettons donc à true pour être sûr que les fichiers soient les bons dans le cas d'une mise à jour. Comme vous le voyez, ce n'est vraiment pas très compliqué.

La fonction FeatureDeactivated va être du même acabit à l'exception près qu'ici, il ne faudra plus copier, mais supprimer les fichiers. Tapez donc dans cette fonction :

SPWebApplication CurrentWebApp = properties.Feature.Parent as SPWebApplication;

if (CurrentWebApp != null)
{

foreach (SPUrlZone Zone in CurrentWebApp.IisSettings.Keys)
{

SPIisSettings CurrentSettings = CurrentWebApp.IisSettings[Zone];

File.Delete(Path.Combine(CurrentSettings.Path.ToString(), "_app_binfile2.txt"));
File.Delete(Path.Combine(CurrentSettings.Path.ToString(), "App_GlobalResourcesfile1.txt"));


}


}
else
throw new ApplicationException("The Web Application object is null.");


Comme vous voyez, ici, nous faisons exactement la même chose, mais nous appelons la fonction Delete de la classe File pour supprimer les deux fichiers que la Web Application feature déploie. A ce stade il manque encore quelque chose à votre feature. Effectivement, nous n'avons pas fait le lien entre votre fichier de définition de la feature et votre code. Compilez donc votre projet et récupérez en la signature grâce à Reflector (vu dans la partie Matériel de ce site). Ouvrez ensuite votre fichier feature.xml et rajoutez ces deux attributs à l'élément Feature :

ReceiverAssembly = "[Signature]"
ReceiverClass = "WebApplicationCopy.WebAppCopy"


Ceci sert à faire le lien entre l'activation de votre feature et le code que vous venez de taper. Bien évidemment, vous devez remplacer [Signature] par la signature de la assembly récupérée grâce à Reflector. Si vous n'avez pas nommé votre classe et votre namespace de la même façon que dans ce tutoriel, vous devrez également modifier quelque peu l'attribut ReceiverClass.

Compilez et déployez maintenant votre feature. Une fois ceci fait, ouvrez votre centrale d'administration et allez dans Application Management > Manage web application features. Dans la fenêtre qui apparaît, sélectionnez la Web Application sur laquelle vous voulez déployer vos fichiers dans la liste de choix en haut à droite de la liste des features. Une fois ceci fait, cliquez sur Activate pour activer votre feature. Ensuite, allez dans les répertoire d'IIS et vérifiez que les fichiers s'y trouvent bien. Désactivez alors votre feature et vous remarquerez que les deux fichiers auront disparu.

Plus de tutoriel sur AreaProg et Développez

Modifier une vue


Au cours de mes développements, j'ai été confronté à un étrange bug de SharePoint. Celui-ci me laisse encore dubitatif car je ne comprend vraiment pas ce qui provoque un tel comportement. Cela survient lorsque nous désirons modifier une vue via le code. Nous n'allons pas faire une grande explication mais juste expliquer le "bug" et comment le contourner. Imaginez que vous vouliez modifier la vue par défaut d'une liste pour y ajouter un champ, le code logique ressemblerait à ceci :

list.DefaultView.ViewFields.Add("Field");
list.DefaultView.Update();


Et cela serait tout à fait normal, cependant, cela ne fonctionne pas. Les opérations effectuées sont pourtant assez simples, nous ajoutons un champ aux ViewFields de la vue par défaut et ensuite, nous mettons à jour cette vue pour que celle-ci enregistre les changements apportés à celle-ci. Bien que ce code ne fonctionne pas, voici un bout de code qui, lui, fonctionne :

SPView view = list.DefaultView;
view.ViewFields.Add("Field");
view.Update();


La différence entre ce code et le précédent est que nous stockons la référence à la vue dans l'objet view. C'est ensuite sur cet objet que nous effectuons nos opérations et c'est cet objet que nous mettons à jour. Étrangement, cela fonctionne lorsque nous procédons de la sorte. Je n'ai jamais compris ce qui expliquait ce bug, mais maintenant vous voilà prévenu, lorsque vous voulez modifier une vue, il vous faut stocker une référence à celle-ci dans un objet et effectuer les modifications sur l'objet en question et non directement sur la vue.


Plus de tutoriel sur AreaProg et Développez

Résolution de l'erreur "La validation de la sécurité de cette page n'est pas valide"

Durant mes développements, j'ai quelques fois eu une erreur très gênante. En français, cette erreur ressemble à :

"La validation de la sécurité de cette page n'est pas valide. Cliquez sur Précédente de votre navigateur et retentez l'opération."

En anglais, cela donne :

"The security validation for this page is invalid. Click Back in your Web browser..."

C'est une erreur très frustrante car la plupart du temps, on ne sait pas exactement d'où cela vient. La piste la plus probable que j'ai trouvé ferait intervenir le form digest. En fait, pour effectuer une requête POST modifiant le contenu de la base de données de contenu depuis une page ASPX, il est nécessaire que la page ASPX en question possède un "form digest". En résumé, le "form digest" est un mécanisme de sécurisation de la page générant une message "codé" permettant de prévenir les attaques.

A vrai dire, il est difficile de contourner ce problème avec une méthode bien précise. Jusqu'à présent, je suis tombé sur 3 solutions dont une à l'air de fonctionner sans arrêt. Dans ce cours, nous allons donc aborder ces 3 solutions en terminant par celle qui fonctionne le mieux dans mon cas.

AllowUnsafeUpdate

Il semblerait que cette erreur puisse survenir lors de la mise à jour d'un élément dans une liste SharePoint. Certaines sources sur Internet indiquent qu'il suffit d'encadrer l'appel à la fonction Update de ligne de code permettant les mises à jour non sécurisées. Par exemple, ce code :

using (SPSite site = new SPSite("site")) {

using (SPWeb web = site.OpenWeb()) {

SPList list = web.Lists["list"];

... Récupération d'un élément dans un objet nommé item ...

item.Update();


}


}


Sous certaines conditions, ce code pourrait lancer l'erreur visée par ce cours. Dans ce cas, il semble que faire ceci :


using (SPSite site = new SPSite("site")) {

site.AllowUnsafeUpdates = true;

using (SPWeb web = site.OpenWeb()) {

web.AllowUnsafeUpdates = true;

SPList list = web.Lists["list"];

... Récupération d'un élément dans un objet nommé item ...

item.Update();


}


}


Résolverait le problème. Personnellement, dans mon cas, cette solution n'a jamais rien résolu.

FormDigestSettings

Une deuxième solution que j'ai trouvé en fouillant le net consiste à modifier un paramètre de la Web Application. Personnellement, cela a semblé fonctionner un moment, puis finalement, pas tant que ça, j'ai donc abandonné cette solution. Pour ceux que ca intéresse, il suffirait de mettre ceci avant la ligne de code causant l'erreur :


SPWebApplication webApp = web.Site.WebApplication;
webApp.FormDigestSettings.Enabled = false;

[CODE]

webApp.FormDigestSettings.Enabled = true;


L'idée est donc de désactiver le "form digest" de la Web Application, effectuer les opérations voulues pour ensuite le réactiver. Cette solution n'est pas très conseillée étant donné que l'on modifie un paramètre général d'une Web Application.

SharePoint:FormDigest

Comme nous l'avons vu précédemment, lorsqu'une page tente de faire une requête POST destinée à modifier la base de données de contenu, une vérification est effectuée. Si nous prenons l'exemple d'une page applicative développée à partir de rien, rien n'est prévu pour effectuer cette vérification. Autrement dit, quand SharePoint va tenter de vérifier que la page est sécurisée, l'erreur sera générée. Pour résoudre ce problème, il y a une solution très simple, effectivement, il suffit d'intégrer un contrôle SharePoint:FormDigest pour qu'une fonction Javascript soit générée pour valider la page. Si nous regardons dans le code d'une page contenant ce contrôle, voici ce que nous pourrions voir :


<script> var MSOWebPartPageFormName = 'frmImagePicker';</script>
<script type="text/javascript" language="javascript" src="/_layouts/1033/init.js?rev=ck%2BHdHQ8ABQHif7kr%2Bj7iQ%3D%3D"></script>
<script type="text/javascript" language="javascript" src="/_layouts/1033/init.js?rev=ck%2BHdHQ8ABQHif7kr%2Bj7iQ%3D%3D"></script>
<script type="text/javascript">
//<![CDATA[

function WebForm_OnSubmit() {

UpdateFormDigest('u002f', 1440000);
return true;


}

//]]>

</script>


Comme vous le voyez, cette fonction Javascript permet de mettre à jour une valeur permettant de sécuriser la page en question, ainsi, vous ne devriez plus obtenir l'erreur visée par ce cours.


Plus de tutoriel sur AreaProg et Développez

SPSecurityTrimmedControl

Le contrôle spsecuritytrimmedcontrol permet d'effectuer un affichage conditionnel d'une partie du contenu d'une page Web. Nous allons illustrer cela par un exemple pratique qui va vous permettre de cacher le bouton Site Actions (Actions du site) pour tous les membres n'étant pas administrateur.

Ce contrôle fonctionne d'une manière assez particulière. Nous ne pouvons pas cacher un contenu à un certain visiteur ou bien à un certain groupe. En effet, cela fonctionne avec des niveaux de permissions. Nous allons voir cela tout de suite. Nous allons commencer par modifier la master page de notre site où se trouve le contrôle affichant les actions du site. Ouvrez donc votre site et allez dans Site action > Site settings et en dessous de la partie Galleries, sélectionnez Master pages. La page qui apparait alors vous affiche la liste des pages maitres de votre site. Voici à quoi cela ressemble pour un site avec une page maitre par défaut :

Nous allons maintenant modifier cette page maitre. Positionnez votre curseur dessus pour faire apparaitre une liste déroulante et choisissez Send To > Download a copy :

Et enregistrez la page sur votre bureau. Le mieux serait d'ouvrir cette page dans SharePoint Designer, mais pour ceux qui ne le possède pas, nous allons l'ouvrir dans le bloc note de Windows. Ouvrez donc ce dernier et ouvrez votre page maitre dedans. Nous allons maintenant repérer l'élément permettant d'afficher le menu Site Actions. Allez donc dans Edition > Rechercher et tapez Actions. Normalement, le bloc note devrait se positionner dans votre document à hauteur de ces éléments :

<SharePoint:SiteActions runat="server"
AccessKey="<%$Resources:wss,tb_SiteActions_AK%>"
id="SiteActionsMenuMain"
PrefixHtml="&lt;div&gt;&lt;div&gt;"
SuffixHtml="&lt;/div&gt;&lt;/div&gt;"
MenuNotVisibleHtml=" ">

...
</SharePoint:SiteActions>


C'est donc cette partie de code qui permet d'afficher le bouton Site Actions. Nous allons maintenant utiliser le contrôle SPSecurityTrimmedControl pour ne l'afficher que si l'utilisateur courant est un administrateur. Comme nous avons vu précédemment, il n'est pas possible de définir tel quel que le bouton ne s'affichera pas si l'utilisateur n'est pas un administrateur. Effectivement, la condition ne s'applique pas au groupe ni à l'utilisateur mais aux permissions de l'utilisateur. Effectivement, le contrôle SPSecurityTrimmedControl contient un attribut nommé PermissionString qui contiendra une ou plusieurs permission. Si les permissions liées à l'utilisateur se retrouvent dans cette liste, le contenu du contrôle sera affiché.

Ce contrôle contient 3 attributs importants. Le premier est PermissionString qui va contenir une liste de permissions séparées par des virgules. Ces permissions sont parmi les suivantes :

AddAndCustomizePages Peut ajouter, modifier ou supprimer des pages HTML ou des "WebPart Pages". Peut également éditer le site en utilisant un éditeur compatible SharePoint.
AddDelPrivateWebParts Peut ajouter ou supprimer des WebPart personnelles sur une WebPart Page.
AddListItems Peut ajouter des éléments à une liste et des documents à une librairie de document.
ApplyStyleSheets Peut appliquer des feuilles de styles au site.
ApplyThemeAndBorder Peut appliquer un theme au site.
ApproveItems Peut approuver une version mineure d'un élément d'une liste.
BrowseDirectories Peut énumérer les fichiers et dossiers d'un site en utilisant Microsoft Office SharePoint Designer 2007.
BrowseUserInfo Peut visualiser les informations sur les utilisateurs du site.
CancelCheckout Peut annuler ou faire un "check-in" sur un document qui est en "check out" par un utilisateur.
CreateAlerts Peut créeer des alertes.
CreateGroups Peut créer un groupe d'utilisateur.
CreateSSCSite Peut créer un site utilisant le "Self-Service Site Creation".
DeleteListItems Peut supprimer des éléments dans une liste et des documents dans une librairies de documents.
DeleteVersions Peut supprimer des versions d'un élément dans une liste.
EditListItems Peut éditer des éléments d'une liste, un document dans une librairie de document et personnaliser des WebPart Page dans une librairie de documents.
EditMyUserInfo Permet à l'utilisateur de changer ses informations personnelles.
EmptyMask Aucune permission accordée. (Non accessible depuis l'interface utilisateur)
EnumeratePermissions Permet d'énumérer les permissions d'un site, d'une liste, d'un dossier, d'un document ou d'un élément de liste.
FullMask Possède toute les permissions. (Non accessible depuis l'interface utilisateur)
ManageAlerts Peut gérer les alertes des utilisateurs du site.
ManageLists Peut créer et supprimer des listes, ajouter ou supprimer des colonnes ou des vues dans une liste.
ManagePermissions Permet de créer et de changer les niveaux de permissions du site et de les assigner à un utilisateur/groupe.
ManagePersonalViews Peut créer, modifier et supprimer des vue personnelles dans une liste.
ManageSubwebs Peut créer des sous sites.
ManageWeb Permission accordée pour toutes les tâches d'administration du site.
Open Peut ouvrir un site, une liste ou un dossier pour accéder aux éléments de ce dernier.
OpenItems View the source of documents with server-side file handlers.
UpdatePersonalWebParts Peut mettre à jouer les Webparts affichant des informations personnalisées.
UseClientIntegration Peut utiliser des fonctionnalités qui lancent des application cliente.
UseRemoteAPIs Peut utiliser SOAP, WebDAV, ou Microsoft Office SharePoint Designer 2007 pour accéder au site.
ViewFormPages Peut voir les formulaires, les vues et les pages applicatives. Peut également énumérer les listes.
ViewListItems Peut voir les éléments dans une liste et les documents dans une librairie de documents.
ViewPages Peut voir les pages d'un site.
ViewUsageData Peut visualiser les rapports sur l'utilisation du site.
ViewVersions Peut voir les anciennes versions des éléments d'une liste.

L'attribut PermissionString doit donc contenir une combinaison de ces valeurs. Si vous voulez en mettre plusieurs, il faut les séparer par une virgule. Le deuxième attribut important de ce contrôle est PermissionContext qui permet de définir l'objet duquel les permissions seront récupérées pour la comparaison. Cet attribut doit contenir une des valeurs suivantes :

CurrentFolder : Permission du dossier courant.
CurrentItem : Permission de l'élément courant.
CurrentList : Permission de la liste courante.
CurrentSite : Permission du site courant.
RootSite : Permission du site racine de la collection de site.

Enfin, nous avons l'attribut PermissionMode. Si la valeur de cet élément est All, l'utilisateur devra posséder toutes les permissions contenues dans PermissionString pour voir l'élément contenu dans le SPSecurityTrimmedControl. Par contre, si la valeur de cet attribut est Any, l'utilisateur aura accès à l'élément sécurisé s'il possède au moins une des permissions de l'attribut PermissionString.

Ici, nous allons encadrer le morceau de code précédent d'un SPSecurityTrimmedControl pour n'afficher le bouton Site Actions que si l'utilisateur est un administrateur. Tapez donc ceci au dessus de l'élément SharePoint:SiteActions :

<SharePoint:SPSecurityTrimmedControl PermissionString="ManageWeb" PermissionMode="All" PermissionContext="CurrentSite" runat="server">


Et ceci en dessous de l'élément de fermeture de SharePoint:SiteActions :

</SharePoint:SPSecurityTrimmedControl>


Ici, nous avons choisis ManageWeb comme permission car seuls les administrateurs du site possèdent cette permission. Sauvez et fermez maintenant votre page. Retournez dans la liste SharePoint contenant les pages maitres (même pages que précédemment) et uploadez votre nouvelle version de votre Master Page. Vous pouvez maintenant voir qu'en tant qu'administrateur, vous pourrez accéder au Site Actions, par contre, lorsque vous vous connectez en tant que simple membre, ce bouton ne sera plus accessible.

Plus de tutoriel sur AreaProg et Développez