Il est question dans ce billet de discuter des méthodes de
déploiement de paramétrages dans le fichier web.config d'une application web SharePoint, aussi bien à la création d'une nouvelle application, que dans le cas d'une
modification incrémentale sur des applications existantes.
La méthode la plus "naturelle" consiste à intégrer ces paramétrages dans votre solution SharePoint (WSP). Pour ceux qui ne connaissent pas le principe, voici comment cela fonctionne:
Lors de la création d'une application web, WSS copie le web.config depuis C:\Program Files\Common Files\Microsoft Shared\web server extensions\12\CONFIG vers le dossier racine de l'application web. Avant de copier ce fichier, WSS vérifie l'existence de fichiers du type webconfig.*.xml dans le dossier CONFIG et fusionne le contenu de ces fichiers avec le web.config.
Exemple de fichier webconfig.*.xml:
<actions>
<add path="configuration/SharePoint/SafeControls">
<SafeControl
Assembly="System.Web, Version=1.0.5000.0, Culture=neutral,
PublicKeyToken=b03f5f7f11d50a3a"
Namespace="System.Web.UI.WebControls"
TypeName="*"
Safe="True"/>
</add>
<remove path="configuration/SharePoint/RuntimeFilter"/>
<add path="configuration/SharePoint">
<RuntimeFilter
Assembly="Company.Product, Version=1.0.1000.0,
Culture=neutral, PublickKeyToken=1111111111"
Class="MyRuntTimeFilter",
BuilderUrl="MyBuilderUrl"/>
</add>
</actions>Vous pouvez ensuite packager ce fichier webconfig.*.xml dans votre wsp et modifier le manifest.xml en conséquence:
<Solution xmlns=”http://schemas.microsoft.com/sharepoint/” SolutionId=”GUID”>
<RootFiles>
<RootFile Location=”CONFIG\
webconfig.myname.xml”/>
</RootFiles>
...
</Solution>Mais malheureusement, ce système n'est valable que pour la création d'une nouvelle application web...
Imaginons maintenant, que vous ayez une solution WSP déjà déployée sur plusieurs applications web de votre ferme et que vous ayez besoin de modifier "en masse" des fichiers web.config existants.
Par exemple, une nouvelle webpart à été ajouté à votre solution et vous souhaitez la mettre à disposition sur les sites existants. Vous avez donc peut être besoin de rajouter un tag SafeControl sur plusieurs web.config d'applications existantes.
Quelles sont les solutions qui s'offrent à nous ?
- à la main: Nan je déconnes...
- La commande stsadm.exe -o copyappbincontent permet de copier les fichiers spécifiques aux application du dossier 12\CONFIG vers les dossiers appropriés des applications. Mais attention: toutes les applications (sauf l'administration centrale) sont impactées et il est nécessaire d'effectuer cette opération sur CHACUN des frontaux. De plus, si vous aviez déjà déployé des paramètres avec un fichier webconfig.*.xml, vous risquez de vous retrouver avec des déclarations en double !
- La classe SPWebConfigModification de Microsoft.SharePoint.Administration.dll permet d'écrire des nœuds et des attributs dans le web.config. Vous pouvez donc utiliser cette classe dans un FeatureReceiver pour ajouter le noeud à l'activation et le supprimer à la désactivation.
Exemple d'utilisation de SPWebConfigModification :
SPWebService service = SPWebService.ContentService;
SPWebConfigModification myModification = new SPWebConfigModification();
myModification.Path = “configuration/SharePoint/SafeControls”;
myModification.Name = “SafeControl[@Assembly='MyCustomAssembly'][@Namespace='MyCustomNamespace'][@TypeName='*'][@Safe='True']“;
myModification.Sequence = 0;
myModification.Owner = “User Name“;
myModification.Type = SPWebConfigModification.SPWebConfigModificationType.EnsureChildNode;
myModification.Value = “<SafeControl Assembly=’MyCustomAssembly’ Namespace=’MyCustomNamespace’ TypeName=’*’ Safe=’True’ />”;
service.WebConfigModifications.Add(myModification);
/*Call Update and ApplyWebConfigModifications to save changes*/
service.Update();
service.ApplyWebConfigModifications();
- Utiliser le web.config Modification Manager téléchargeable à cette adresse:
http://blog.thekid.me.uk/archive/2007/03/24/web-config-modification-manager-for-sharepoint.aspx
En conclusion, la mise à jour des fichiers de config est une opération délicate qui doit être automatisé mais avec beaucoup de prudence. La solution du
SPWebConfigModification me semble la plus sûre,
à condition de bien tester vos modifications, mais doit être utilisée en complément des fichiers webconfig.*.xml qui serviront toujours en cas de remontée après un crash par exemple.
La discussion est ouverte !
Lorsque l'expiration est activée sur une liste ou une bibliothèque, il est possible d'exclure (exempter) un élément de la stratégie d'expiration de façon manuelle:
En activant l'expiration sur une liste SharePoint contenant déjà des éléments, je me suis rendu compte que le workflow d'expiration (ou la suppression) ne se déclenchait pas sur ces éléments existants.
En fait, il semble que, par défaut, les éléments créés avant l'activation de l'expiration soient marqués comme exempts de l'expiration.
Ma liste contenant des centaines d'éléments, j'ai donc eu besoin de créer un petit batch pour supprimer l'exemption de façon automatique.
Voici la méthode qui permet de supprimer l'exemption sur un élément:
Microsoft.Office.RecordsManagement.InformationPolicy.Policy.RemoveExemption(SPListItem item)
Toutefois, attention si la date d'expiration est dépassée, votre workflow d'expiration (ou la suppression) va être déclenché !
Dans mon quotidien SharePointesque, je suis loin d'être un fervent adepte de SharePoint Designer. Il peut être pratique pour tester des modifications rapidement sur une page, en environnement de développement uniquement, mais il ne permet pas de créer/customiser dans SharePoint de façon pérenne. De plus, je ne le trouves pas très user-friendly.
Voici un exemple de ce qui me fait dire cela:
Dernièrement, j'ai été forcé d'utiliser SharePoint Designer pour apporter des modifications sur un workflow réalisé par un de mes prédécesseurs.
Ce workflow utilise l'action "Collect Data Task" qui permet de d'assigner une tâche à un utilisateur, d'y associer des meta-données et créé automatiquement le formulaire pour récupérer des informations saisies par l'utilisateur.
Bizarrement, lorsque je tentais de modifier les propriétés de ce formulaire, mes modifications n'étaient pas prises en compte. En prenant un peu de recul j'ai en effet constaté que le fichier aspx de ce formulaire n'était pas mis à jour...
C'est seulement lorsque j'ai supprimé ce fichier, et sauvegardé à nouveau le workflow que le fichier aspx à été recréer avec mes modifications... Un problème tout bête mais qui m'a bien fait perdre du temps...
J'écris ce petit post juste pour mettre de coté ce lien utile qui détaille notamment ce qu'il est possible ou non de faire sur SharePoint 2007 avec un client Office 2003:
http://www.sharepointusecases.com/index.php/2008/08/office-2003-and-sharepoint-2007-comparision/
Voici une des petites surprises dont SharePoint a le secret que je viens de rencontrer:
Je souhaitais modifier un workflow créé avec SharePoint Designer pour qu'il se déclenche automatiquement lors de la création d'un élément dans ma liste. Bizarrement, même après avoir effectué cette modification, le workflow pouvait toujours être démarré manuellement mais ne se déclenchait pas automatiquement.
Il s'agit en fait d'un correctif apporté par le SP1 qui empêche les workflows déclaratifs de se déclencher automatiquement
avec le compte systeme.
Après installation du SP1, les workflows déclaratifs ne se déclencheront pas automatiquement si les conditions suivantes sont réunies:
- L'application Web WSS s'exécute en utilisant un compte utilisateur de domaine
- L'utilisateur courant est connecté avec ce compte
- Le site affiche "Compte Système" comme nom d'utilisateur
Voici le lien vers la solution complète:
http://support.microsoft.com/kb/947284
Si vous désirez ajouter du code C# ou VB.Net dans une page maître ou un modèle de page, vous risquez de tomber sur l'erreur suivante:
Les blocs de code ne sont pas autorisés dans ce fichier.
Afin d'autoriser les blocs de codes dans vos pages, il est nécessaire de modifier le web.config de votre application Web en ajoutant le nœud suivant:
<PageParserPaths>
<PageParserPath VirtualPath="/_catalogs/masterpage/*" CompilationMode="Always" AllowServerSideScript="True" IncludeSubfolders="True" />
</PageParserPaths>
L'attribut VirtualPath contient le chemin vers les pages que vous voulez autoriser, dans cet exemple toutes les pages de la galerie des pages maîtres et modèles de pages et dans les sous-dossier sont autorisées à exécuter du script coté serveur.
Voici une feature disponible en téléchargement sur CodePlex permettant
de masquer les colonnes d'une liste de façon systématique ou suivant
des critères (comme l'appartenance du user à un groupe par exemple)
pour les 3 modes d'affichage (View, New, Edit).
Une feature qui peut être bien utile pour éviter la création de modèles de listes Custom...
http://www.codeplex.com/SPListDisplaySetting
Sinon vous pouvez faire le même genre de manipulation avec ce petit outil sous la forme d'une application WinForms:
http://patrikluca.blogspot.com/2008/08/hide-list-fields-upon-creation-of.html
Lorsque l'on a besoin de customiser une fonctionnalité dans SharePoint,
il est souvent nécessaire de repartir de zéro... Heureusement, il est
parfois possible de ré-utiliser en partie l'existant.
J'avais
besoin de créer un
workflow d'approbation dont le comportement réponde
réellement aux
contraintes de mon
client, mais que les formulaires de
validation gardent le
look-n-feel des formulaires
standard de
SharePoint :

Je ne vais pas revenir sur l'ensemble du processus de création d'un
Workflow utilisant des formulaires InfoPath (cette page le fait très
bien:
http://stephaneey.developpez.com/tutoriel/sharepoint/workflowforms/)
mais juste détailler ce qu'il faut savoir pour utiliser le formulaire
de tâche d'approbation "out-of-the-box" avec un workflow custom.
Pour cela, il faut déjà configurer la feature de mon workflow de la manière suivante:
<?xml version="1.0" encoding="utf-8"?>
<Elements xmlns="http://schemas.microsoft.com/sharepoint/">
<Workflow
Name="MonWorkflowAppro"
Description="Description de mon workflow d'approbation"
Id="e8c83205-5791-4c6d-8798-e35e5cad77ca"
CodeBesideClass="MonProjet.MonWorkflowAppro"
CodeBesideAssembly="MonProjet, Version=1.0.0.0, Culture=neutral, PublicKeyToken=99ad5b5da4846ba1"
TaskListContentTypeId="0x01080100C9C9515DE4E24001905074F980F93160"
AssociationUrl="_layouts/CstWrkflIP.aspx"
InstantiationUrl="_layouts/IniWrkflIP.aspx"
ModificationUrl="_layouts/ModWrkflIP.aspx"
StatusUrl="_layouts/WrkStat.aspx">
<Categories/>
<MetaData>
<InitiationType>OnNewItem</InitiationType>
<Task1_FormURN>urn:schemas-microsoft-com:office:infopath:workflow:ReviewRouting-Review:$Subst:LCID;</Task1_FormURN>
<AssociateOnActivation>true</AssociateOnActivation>
</MetaData>
</Workflow>
</Elements>Je
garde les url des pages aspx standard pour afficher les formulaires
(Association, Instanciation, etc) et je fournis l'urn du formulaire
InfoPath standard dans Task0_FormURN.
Un workflow peut créer
différents types de tâches qui sont identifiées par un entier (0, 1, 2,
...), ici j'ai associé l'urn de mon formulaire InfoPath au type 0, il
faut donc, lorsque que je créé ma tache dans mon workflow, lui assigner
le type 0.
Pour cela, dans la méthode Invoking de mon activité
CreateTask, je vais assigner 0 à la propriété TaskType de mes
TaskProperties.
Autre point: le formulaire standard contient 2
liens en bas de page "Réaffecter la tâche" et "Demande de
modification". Vous ne souhaitez peut être pas implémenter ces
fonctionnalités dans votre workflow, il peut donc vous être utile de
désactiver ces liens.
Pour cela, toujours dans le méthode Invoking
de votre activité CreateTask, il faut mettre la valeur False aux
entrées ows_AllowDelegation et ows_AllowChangeRequests des
ExtendedProperties de vos TaskProperties:
MyTaskProps.ExtendedProperties["ows_AllowDelegation"] = "False"; MyTaskProps.ExtendedProperties["ows_AllowChangeRequests"] = "False";Une
fois votre tâche correctement créée par votre workflow vous voulez à
juste titre récupérer la réponse saisie par l'utilisateur "Approuvé" ou
"Rejeté".
Pour cela, dans votre méthode qui sera déclenché à la
modification de la tâche, vous allez pouvoir utiliser la collection
AfterProperties qui contient les valeurs du formulaire.
Pour récupérer le statut, il faut tester la valeur de
MyTaskAfterProps.ExtendedProperties["TaskStatus"] qui prend "#" si approuvé et "@" si rejeté.
Si vous n'avez pas désactivé les boutons de délégation ou demande de modification, vous pouvez récupérer les infos suivantes:
Délégation:
MyTaskProps.ExtendedProperties["Decline"] prend les valeurs 0, 1 ou 2
0 = pas de délégation
1 = délégué à l'initiateur du workflow
2 = délégué à la personne définie par
MyTaskProps.ExtendedProperties["DelegateTo"]
Demande de modification
MyTaskProps.ExtendedProperties["dcr"] prend les valeurs 0, 1 ou 2
0 = pas de demande
1 = demande à l'initiateur du workflow
2 = demande à la personne définie par
MyTaskProps.ExtendedProperties["DelegateTo"]Voila vous avez maintenant toutes les billes pour vous interfacer avec ce formulaire de tâche d'approbation.
Pour le développement d'un workflow SharePoint, j'avais besoin de créer
dynamiquement une tâche et de l'assigner à un utilisateur sélectionné,
via une colonne de type Personne, lors de la création de l'élément qui
déclenche mon workflow.
Et là problème: la valeur que je
récupère de ma colonne est une chaîne de caractères du type "18;#NOM,
Prénom" (l'id du SPUser et le display name).
C'est un problème que j'avais déjà rencontré lors du développement d'event handlers.
string columnName = "Nom de ma colonne";
string columnValue = workflowProperties.Item[columnName].ToString();
SPFieldUser userField = (SPFieldUser)workflowProperties.List.Fields.GetField(columnName);
SPFieldUserValue fieldValue = (SPFieldUserValue)userField.GetFieldValue(columnValue);
SPUser user = fieldValue.User;
Ensuite, avec mon SPUser, je peux récupérer toutes les infos dont j'ai besoin, notamment le LoginName.
Il peut être très utile dans certains cas de communiquer avec
SharePoint en utilisant ses WebServices, depuis une machine en dehors
de la ferme par exemple.
Ceux-ci sont relativement simples
d'utilisation lorsque vous êtes habitués à utiliser des WebServices et
l'essentiel du modèle objet est exposé:
- Administration
- Alerts
- Authentication
- Copy
- Document Workspace
- Forms
- Imaging
- List Data Retrieval
- Lists
- Meetings
- People
- Permissions
- SharePoint Directory Management
- Site Data
- Sites
- Search
- Users and Groups
- Versions
- Views
- Web Part Pages
- Webs
La référence est disponible sur MSDN:
http://msdn.microsoft.com/en-us/library/cc752745.aspxVoici un exemple d'instanciation du WebService Users and Groups:
UserGroup wsUserGroup = new UserGroup();
wsUserGroup.Credentials = System.Net.CredentialCache.DefaultCredentials;
wsUserGroup.Url = mySiteUrl + "/_vti_bin/UserGroup.asmx";Mais attention ! Les méthodes retournent toutes des objets XmlNode qui sont dépendants de namespace URI.
Donc
pour éviter de galérer avec des SelectSingleNode qui retourne null, il
est nécessaire de rajouter des préfixes à vos requêtes XPath:
Exemple
avec la méthode GetGroupCollectionFromSite() de mon WebService
précédemment instancié qui me retourne un XmlNode du type:
<GetGroupCollectionFromSite xmlns="http://schemas.microsoft.com/sharepoint/soap/directory/">
<Groups>
<Group ID="3" Name="Group1" Description="Description" OwnerID="1" OwnerIsUser="False" />
<Group ID="15" Name="Group2" Description="Description" OwnerID="12" OwnerIsUser="True" />
<Group ID="16" Name="Group3" Description="Description" OwnerID="7" OwnerIsUser="False" />
</Groups>
</GetGroupCollectionFromSite>Voici
donc une méthode qui déclare toutes les URI utilisées par SharePoint
avec des préfixes choisis arbitrairement qui seront utilisés par nos
requêtes XPath:
private XmlNodeList RunXPathQuery(XmlNode XmlNodeToQuery, string XPathQuery)
{
// load the complete XML node and all its child nodes into a XML document
XmlDocument Document = new XmlDocument();
Document.LoadXml(XmlNodeToQuery.OuterXml);
// all the possible namespaces used by SharePoint and a randomly choosen prefix
const string SharePointNamespacePrefix = "sp";
const string SharePointNamespaceURI = "http://schemas.microsoft.com/sharepoint/soap/";
const string ListItemsNamespacePrefix = "z";
const string ListItemsNamespaceURI = "#RowsetSchema";
const string PictureLibrariesNamespacePrefix = "y";
const string PictureLibrariesNamespaceURI = "http://schemas.microsoft.com/sharepoint/soap/ois/";
const string WebPartsNamespacePrefix = "w";
const string WebPartsNamespaceURI = "http://schemas.microsoft.com/WebPart/v2";
const string DirectoryNamespacePrefix = "d";
const string DirectoryNamespaceURI = "http://schemas.microsoft.com/sharepoint/soap/directory/";
// now associate with the xmlns namespaces (part of all XML nodes returned
// from SharePoint) a namespace prefix which we can then use in the queries
XmlNamespaceManager NamespaceMngr = new XmlNamespaceManager(Document.NameTable);
NamespaceMngr.AddNamespace(SharePointNamespacePrefix, SharePointNamespaceURI);
NamespaceMngr.AddNamespace(ListItemsNamespacePrefix, ListItemsNamespaceURI);
NamespaceMngr.AddNamespace(PictureLibrariesNamespacePrefix, PictureLibrariesNamespaceURI);
NamespaceMngr.AddNamespace(WebPartsNamespacePrefix, WebPartsNamespaceURI);
NamespaceMngr.AddNamespace(DirectoryNamespacePrefix, DirectoryNamespaceURI);
// run the XPath query and return the result nodes
return Document.SelectNodes(XPathQuery, NamespaceMngr);
}Je serai donc capable d'executer des requêtes XPath sur mon noeud de la maniere suivante:
XmlNode myNode = wsUserGroup.GetGroupCollectionFromSite();
XmlNodeList myGroupList = RunXPathQuery(myNode, "/d:Groups/d:Group[@ID='16']"); Cet exemple de requête XPath me retourne les noeuds dont l'attribut ID est égale à 16.
Références:
Talk to SharePoint Through its Web Services
Voici une page sur laquelle vous pouvez télécharger une WebPart permettant d'afficher le formulaire et les résultats d'un sondage.
Cette WebPart se branche sur une liste de type "Enquête" et affiche dans une seule et même zone le questionnaire ou les résultats si l'utilisateur courant a déjà répondu.
Les résultats peuvent être affichés en barre ou en camembert, et sont générés en PNG avec la librairie GDI+ de System.Drawing.
Tout cela est disponible en Setup ou avec les sources C# donc facilement "customizable".
http://darrenjohnstone.net/2008/02/17/sharepoint-quick-surveys/
Au cours de mes expériences d'intégration de SharePoint en entreprise,
j'ai entendu pas mal de sons de cloche différents sur les diverses limitations réelles
ou recommandées : nombres de documents par liste, taille de la base de contenu, etc.
Voici quelques éléments pour faire le point:
Tout d’abord, la taille des bases de contenu :
Il n'y a aucune limitation liée à SharePoint,
cela dépend juste de la version de SQL Server utilisée :
- Avec SQL Server 2005 Express, vous avez une limitation dans la taille des bases de 4Go, et 2Go avec la version précédente : MSDE.
- Avec SQL Server 2005, il n'y a aucune limite à part l'espace disponible sur le disque et les éventuelles contraintes imposées par les DBAs.
Dans la plupart des cas, pour conserver des performances correctes, Microsoft recommande de ne pas dépasser les 100Go pour une base de contenu, comme l'indique ce document publié par l'équipe SharePoint:
Performance recommendations for storage planning and monitoring
Pour ce qui concerne les composants SharePoint, voici un article sur technet qui fixe les limites à ne pas dépasser pour garder des performances acceptables, il n'y a aucune limite technique qui soit gravée dans le marbre:
http://technet.microsoft.com/en-us/library/cc262787.aspx
Voici un petit bout de code permettant de lister les Web App d'une
ferme, puis pour chaque WebApp, les collections de sites. N'oubliez pas
de référencer le namespace Microsoft.SharePoint.Administration.
SPFarm farm = SPFarm.Local;
SPWebService service = farm.Services.GetValue<SPWebService>("");
foreach (SPWebApplication webApp in service.WebApplications)
{
foreach (SPSite site in webApp.Sites)
{
/***
ici traitement pour
chaque collection de site
**/
}
}
Les 10 derniers blogs postés
-
TechDays 2010 Genève : Retrouvez-moi pour une session sur la Haute disponibilité et le ScaleOut avec SQL Server par
SQL Server vu par Christian Robert le il y a 15 heures et 54 minutes
-
[MIX10] Keynote deuxième journée – Internet Explorer 9, Html5, Visual Studio 2010, OData par
Atteint de JavaScriptite Aiguë [Cyril Durand] le 03-17-2010, 19:40
-
Certifications beta .NET 4 par
Kévin Gosse le 03-17-2010, 19:33
-
[Mix 2010] – Microsoft Translator Technology Preview V2 par
RedoBlog - The .NET Gentleman !!! le 03-17-2010, 18:53
-
Lancement en Preview de Cyclone lors des TechDays 2010! par
Blog de Frédéric Queudret le 03-17-2010, 16:30
-
[WP7] Je ne veux pas d’un nouvel iPhone par
Le blog de FremyCompany le 03-17-2010, 13:11
-
[WF4] Pourquoi utiliser le ContentPresenter dans l’ActivityDesigner? par
Blog de Jérémy Jeanson le 03-17-2010, 07:54
-
[Mix 2010] – Telechargez Internet Explorer 9 en Preview ! par
RedoBlog - The .NET Gentleman !!! le 03-17-2010, 00:58
-
[MIX 2010] – Keynote Day 2 online : Windows Internet Explorer 9, jQuery, OData et Dallas CTP2 ! par
RedoBlog - The .NET Gentleman !!! le 03-17-2010, 00:18
-
[Mix 2010] – Retour d’expérience développement Seesmic sur Windows Phone 7 par
RedoBlog - The .NET Gentleman !!! le 03-16-2010, 23:44