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