03/01/2012

Permalink 20:30:31, Catégories: Outils, 23 mots   French (FR) , stephane eyskens

Webcast sur oData

Salut,

J'ai enregistré une webcast sur oData et SharePoint 2010 en Français.

http://msdn.microsoft.com/fr-fr/sharepoint/hh750361

Bonne visite :).

Happy Coding!

Vous devez être identifié pour poster un commentaire.

26/11/2011

Permalink 19:13:12, Catégories: SharePoint, Outils, Tutos, 304 mots   French (FR) , stephane eyskens

Problème potential avec des formulaires de liste personnalisés

Salut,

J'ai récemment travaillé sur la base de données WSS_Logging qui est la seule base de données SharePoint ouverte en lecture/écriture de manière supportée par Microsoft.

J'ai particulièrement regardé la vue [WSS_Logging].[dbo].[RequestUsage] car elle contient pas mal d'informations relatives à la fréquentation des sites et des éléments de listes, documents, pages etc...L'une des informations disponibles dans la vue est la colonne Title qui contient le titre de l'item de liste (doc/page/item) visité. Ceci est très intéressant car le titre est vraiment une donnée significative ayant une valeur métier importante dans la mesure où celle-ci est saisie par des gens du métier; elle aura donc d'autant plus de poids dans les rapports.

Cependant, j'ai observé un comportement inattendu avec une liste dont le formulaire d'affichage, le displayform avait été entièrement modifié, à tel point, qu'aucun contrôle SharePoint habituel n'y était déclaré.

A cause de cela, pour chaque ligne dans la vue RequestUsage, ce n'est non pas le titre de l'élément qui se retrouvait dans la vue mais le titre de la liste, ce qui forcément était beaucoup moins intéressant.

Après avoir comparé une dispform standard avec la dispform modifiée, j'ai constaté qu'il fallait au moins ajouter le code suivant dans le content placeholder PlaceHolderPageTitleInTitleArea :

 
<SharePoint:ListProperty Property="LinkTitle" runat="server"/>: <SharePoint:ListItemProperty runat="server"/>  

Avec ce simple contrôle, c'est à nouveau bel et bien la valeur du titre de l'item visité qui est loggé en DB et non plus la valeur de la liste. Je me suis donc dit que ça pourrait éventuellement vous intéresser car c'est une chose à savoir en amont d'un projet de préférence.

Happy Coding!

Vous devez être identifié pour poster un commentaire.

21/11/2011

Permalink 16:50:16, Catégories: Outils, 2300 mots   French (FR) , stephane eyskens

Développer et consommer un service oData pour SharePoint 2010

Salut,

Pour un projet récent, j'ai dû travailler sur l'optimisation d'un portail de type Intranet. La première idée fut d'implémenter un petit framework de caching permettant de mettre en cache et de lire les objets qui y sont placés de manière rapide et flexible.

Je vous raconte cette histoire car le sujet de ce post est dérivé de ce contexte métier. Donc, toujours avec l'objectif d'améliorer les performances et donc l'expérience utilisateur, nous avons implémenté des webparts asynchrones.

Bien sûr, on aurait pu choisir de travailler de manière classique avec jQuery/Lists.asmx ou encore jQuery/ListData.svc ou encore l'ECMAScript et nous l'avons d'ailleurs partiellement fait mais cela ne me suffisait pas.

Je préférais développer un web service qui irait lire via le framework de cache, les objets stockés en cache. De cette manière, on ne fait "que" solliciter la mémoire et le réseau au lieu de consommer le réseau et SQL.

D'autant que j'avais déjà 80% de mes données en cache, c'eût été idiot de s'en priver. J'ai d'abord décidé de créer un web service REST basé sur la factory MultipleBaseAddressWebServiceHostFactory mais je souhaitais aussi bénéficier d'un système de query puissant sans vouloir pour autant devoir développer toute une série de webmethod.

J'ai donc pensé à oData pour bénéficier d'un web service similaire à ListData.svc mais qui ramène des données stockées en cache plutôt que des données stockées dans des listes SharePoint.

L'un des objets principaux avec lesquels j'ai travaillé est le UserProfile, en effet, dans notre scénario métier, pas mal de nos composants agissent en fonction du profil de l'utilisateur connecté.

Pour une question de scalabitlity et de rapidité, on a décidé d'exécuter une requête via l'API de recherche qui ramène tous les profils utilisateurs, ensuite, on les mets en cache durant une journée entière et ensuite, tous les accès aux profils utilisateurs se font via ce système de cache. Cela a l'avantage d'être non seulement plus rapide mais aussi d'éviter de surcharger le User Profile store en s'y connectant en permanence. Voici donc un exemple partiel de ce que nous avons implémenté.

Dans cet article, je vais donc couvrir les points suivants:

  • Créer le framework de cache (juste une petite partie)
  • Créer le service CachedData.svc qui exposera des querystring oData sur les profils utilisateurs stockés en cache. Bien sûr, dans notre cas, ce service est étendu bien au-delà des profils utilisateurs mais je ne vais montrer qu'un petit exemple
  • Consommer notre service avec jQuery
  • Consommer notre service depuis un composant serveur, dans ce cas-ci, je vais prendre une application console

Prérequis

ADO.NET Data Services doit être installé sur le système, vous pouvez le télécharger ici:

http://www.microsoft.com/download/en/details.aspx?displaylang=en&id=2343

Créer le projet

  • Créez un projet de type SharePoint
  • Ajoutez les références suivantes:

    Microsoft.SharePoint.Client.ServerRuntime.dll
    Microsoft.Office.Server.Search.dll
    System.Data.Services
    System.Data.Services.Client.dll
    System.Runtime.Serialization
    System.ServiceModel.dll
    System.ServiceModel.Web.dll
    Microsoft.Office.Server.dll
    System.Web.dll
    System.Web.Services.dll

Créaction du framework de cache

Comme dit précédemment, ce petit framework permet de stocker/lire facilement des objets en cache. Il est composé de quelques classes dont j'ai enlevé pas mal de choses afin d'obtenir quelque chose de compréhensible puisque ce framework n'est pas le sujet principal de ce post. J'ai aussi hard-codé certaines valeurs et supprimé l'error handling pour plus de lisibilité.

La première chose est de définir une interface:

 
public interface ISharePointData<T>{ 
  List<T> GetItems(); 
  int CacheDurationInSeconds { get; set; } 
  int CacheDurationInMinutes { get; set; } 
  int CacheDurationInHours { get; set; } 


Ce contrat spécifie que chaque objet devra implémenter la méthode GetItems() et spécifier sa durée en cache avant expiration.

Voici maintenant la déclaration d'une classe de base implémentant notre interface:

 
public abstract class BaseSharePointData<T> : ISharePointData<T>{ 
  public abstract List<T> GetItems(); 
  private int _cacheDuration = 0; 
  public int CacheDurationInSeconds{ 
  get{ 
  return _cacheDuration; 
  } 
  set{ 
  _cacheDuration = value; 
  } 
  } 
  public int CacheDurationInMinutes{ 
  get{ 
  return CacheDurationInSeconds / 60; 
  } 
  set{ 
  CacheDurationInSeconds = value * 60; 
  } 
  } 
  public int CacheDurationInHours{ 
  get{ 
  return CacheDurationInMinutes / 60; 
  } 
  set{ 
  CacheDurationInMinutes = value * 60; 
  } 
  } 

Enfin, la déclaration de notre fameux objet User Profile

 
[Serializable()] [DataContractAttribute(IsReference = true)] 
[DataServiceKey("Id")] 
 public class UserProfileObject : BaseSharePointData<UserProfileObject> { 
  public UserProfileObject(){ 
  CacheDurationInMinutes = 10; 
  } 
  public UserProfileObject(string userName, string email, string pictureURL, string path, string jobTitle): base(){ 
 
  UserName = userName; 
  PictureURL = pictureURL; 
  Email = email; 
  Path = path; 
  JobTitle = jobTitle; 
  } 
  [DataMemberAttribute()] 
  public int Id{ get; set; } 
  [DataMemberAttribute()] 
  public string UserName{ get; set; } 
  [DataMemberAttribute()] 
  public string FirstName { get; set; } 
  [DataMemberAttribute()] 
  public string LastName { get; set; } 
  [DataMemberAttribute()] 
  public string Email { get; set; } 
  [DataMemberAttribute()] 
  public string PictureURL { get; set; } 
  [DataMemberAttribute()] 
  public string Path { get; set; } 
  [DataMemberAttribute()] 
  public string JobTitle { get; set; } 
  public override List<UserProfileObject> GetItems(){ 
  List<UserProfileObject> allProfiles = new List<UserProfileObject>(); 
  FullTextSqlQuery q = new FullTextSqlQuery(ServerContext.Current); 
  q.QueryText = "SELECT AccountName,FirstName,LastName, WorkEmail,PictureUrl,Path,JobTitle FROM Scope() WHERE \"Scope\"='People'";  
  q.ResultTypes = ResultType.RelevantResults; 
  q.RowLimit = 20000; 
  ResultTableCollection results = q.Execute(); 
  ResultTable queryResultsTable = results[ResultType.RelevantResults]; 
  DataTable queryDataTable = new DataTable(); 
  queryDataTable.Load(queryResultsTable, LoadOption.OverwriteChanges); 
  AddProfiles(allProfiles, queryDataTable.Rows); 
  return allProfiles; 
  } 
  public void AddProfiles(List<UserProfileObject> profiles, DataRowCollection rows)  
  { 
  for (int i = 0; i < rows.Count; i++){ 
  DataRow row = rows[i]; 
  profiles.Add(new UserProfileObject  
  { 
  Id=i, 
  UserName = (row["AccountName"] != null) ? row["AccountName"].ToString() : string.Empty, 
  FirstName = (row["FirstName"] != null) ? row["FirstName"].ToString() : string.Empty, 
  LastName = (row["LastName"] != null) ? row["LastName"].ToString() : string.Empty, 
  Email = (row["WorkEmail"] != null) ? row["WorkEmail"].ToString() : string.Empty, 
  PictureURL = (row["PictureURL"] != null) ? row["PictureURL"].ToString() : string.Empty, 
  JobTitle = (row["JobTitle"] != null) ? row["JobTitle"].ToString() : string.Empty, 
  Path = (row["Path"] != null) ? row["Path"].ToString() : string.Empty 
  }); 
  } 
  } 

Quelques explications:
- On spécifie que Id est notre clé primaire car cela est requis par oData
- On implémente GetItems(), méthode requise par notre interface qui retourne tous les profils en utilisant la search API. On rappatrie uniquement les propriétés souhaitées.
- Ensuite, on crée la méthode AddProfiles() qui construit notre liste de UserProfileObject

Maintenant, on construit la classe DataHelper qui nous permettra de récupérer les profils utilisateurs:

 
public class DataHelper 

  public List<T> GetItems<T>() where T : ISharePointData<T>, new() 
  { 
  bool disableCachingProperty = false; 
  T objectToCache = new T(); 
  object cache = HttpContext.Current.Cache[objectToCache.GetType().ToString()];  
  if (objectToCache.CacheDurationInSeconds != 0) 
  {  
  if (cache == null) 
  {  
  List<T> itemsRetrieved = objectToCache.GetItems();  
  HttpContext.Current.Cache.Insert( 
  objectToCache.GetPropertyBagKeyForCache(), 
  itemsRetrieved, 
  null, 
  DateTime.Now.AddSeconds(objectToCache.CacheDurationInSeconds), 
  System.Web.Caching.Cache.NoSlidingExpiration); 
 
  return itemsRetrieved; 
  } 
  else 
  { 
  return (List<T>)cache; 
  } 
  } 
  else 
  { 
  // Do not get items from the cache 
  return objectToCache.GetItems(); 
  } 
  } 

Le comportement souhaité est donc de récupérer l'objet du cache si il y est déjà, sinon on appelle la méthode GetItems() et on met en cache le résultat.

Developper le service

Ajouter le fichier .svc

Comme pour tous les services WCF, il faut créer le fichier .svc qui sera notre endpoint.

  • Créez un répertoire mappé sur ISAPI
  • Créez un fichier texte, renommez-le en .svc et ajoutez-y ceci
     
    <%@ ServiceHost Language="C#" Debug="true" Factory="Microsoft.SharePoint.Client.Services.MultipleBaseAddressDataServiceHostFactory, Microsoft.SharePoint.Client.ServerRuntime, Version=14.0.0.0, Culture=neutral, PublicKeyToken=71e9bce111e9429c" Service="demoOdata.Services.CacheoDataService,$SharePoint.Project.AssemblyFullName$"%> 

On spécifie qu'on utilise la factory MultipleBaseAddressDataServiceHostFactory. Celle-ci indique à SharePoint que notre service est un data service.

Implémentez le service

La classe principale du service ressemble à ceci:
 
[BasicHttpBindingServiceMetadataExchangeEndpoint] 
[AspNetCompatibilityRequirements(RequirementsMode = AspNetCompatibilityRequirementsMode.Required)] [System.ServiceModel.ServiceBehavior( IncludeExceptionDetailInFaults = true)]  
  public class CacheoDataService : DataService<CacheDataContext> { 
  public static void InitializeService(DataServiceConfiguration config){  
  config.SetEntitySetAccessRule("*", EntitySetRights.AllRead);  
  config.DataServiceBehavior.MaxProtocolVersion = DataServiceProtocolVersion.V2; 
  } 
  } 

Le premier attribut sert à exposer un endpoint MEX (metadata), le deuxième est requis par SharePoint. Le service doit être ASP.NET friendly. Enfin, le troisième attribut est utile en phase de développement car si une erreur est rencontrée, elle sera retournée dans la réponse du service, ce qui est plus pratique pour debugger.

Ensuite, on spécifie que toutes les opérations sont accessibles en lecture, on utilise pas ici d'opération CRUD, en fait, on implémente seulement le R. Comme vous le constatez, notre classe dérive de DataContexte qui prend en générique, le type CacheDataContext dont voici le code:

 
public class CacheDataContext{ 
  DataHelper dataHelper = new DataHelper(); 
  public IQueryable<UserProfileObject> UserProfiles{ 
  get{ 
  return dataHelper.GetItems<UserProfileObject>().AsQueryable(); 
  } 
  } 


Ce qui est important ici, c'est le fait de retourner une collection IQueryable. La manière dont la méthode est implémentée importe peu.

Si je voulais retourner des news, je pourrais faire ceci:

 
public IQueryable<News> News { 
  get{ 
  return dataHelper.GetItems<News>().AsQueryable(); 
  } 

Etant donné que je ne travaille pas avec une base de données, je n'utilise pas EntityFramework qui aurait été un choix de prédilection même si la version d'EF 4 n'est pas utilisable directement dans SharePoint qui n'est compatible qu'avec .NET 3.5.

Après toutes ces étapes, votre solution doit ressembler à ceci:

Notez que vous aurez plus d'infos sur la page odataTestPage.htm dans les sections suivantes.

Déployez la solution

Déployez la solution avec Visual Studio et tapez l'URL vers votre endpoint, Vous devez voir toutes les opérations disponibles, ajoutez le caractère / à la fin de l'URL

En ajoutant l'opération dans l'URL, vous obtenez ceci:

Et vous pouvez commencer à utiliser les paramètres oData dans l'URL tels que $filter, $select etc…Ceci est donc bien plus puissant qu'un "simple" service RESTFUL où il aurait fallu implémenter tous ces paramètres nous-mêmes.

Consommer le service depuis une application console

  • Créez une application Console
  • Ajoutez une référence de type service comme suit:

Dans la méthode Main, ajoutez le code suivant:
 
CacheDataContext.CacheDataContext ctx = new CacheDataContext.CacheDataContext( 
  new Uri("http://sp2010srv/_vti_bin/cachedodata.svc")); 
ctx.Credentials = System.Net.CredentialCache.DefaultCredentials; 
var query = from profile in ctx.UserProfiles 
  where profile.JobTitle != string.Empty 
  select profile; 
foreach (var queryresut in query) 
 Console.WriteLine(queryresut.UserName); 

Cela donne le résultat suivant:

Consommer le service avec jQuery

Pour tester cela, une simple page HTML déployé dans le répertoire LAYOUTS de SharePoint doit suffire. Insérez-y le code suivant:
 
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN"> 
<html> 
 <head> 
  <title></title> 
  <script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script> 
  <script type="text/javascript"> 
  $(document).ready(function () { 
  $.ajax({url: "_vti_bin/CachedoData.svc/UserProfiles()",  
  dataType: "json", 
  success: function (data) { 
  if (data.d) { 
  var html = ""; 
  for (var i = 0; i < data.d.length; i++) { 
  html += data.d[i].UserName + "<br/>"; 
  } 
 
  $("#response").empty().append(html); 
  } 
  }, 
  error: function (XMLHttpRequest, textStatus) { 
  alert(XMLHttpRequest.status); 
  } 
  }); 
  }); 
  </script> 
 </head> 
 <body> 
  <div id="response"></div> 
 </body> 
</html> 
 

Cela affichera tous les noms d'utilisateur:

Note que ce code jQuery doit éventuellement être ajuté selon le browser, je ne l'ai testé qu'avec IE9 et Firefox 5. Pour une plus grande compatibilité, vous pouvez utiliser ATOM au lieu de JSON.

Happy Coding!

Vous devez être identifié pour poster un commentaire.

11/11/2011

Permalink 06:37:17, Catégories: Outils, 513 mots   French (FR) , stephane eyskens

SharePoint 2010 - Dépasser la limite des 10000 résultats de recherche

Je ne sais pas si vous avez déjà remarqué mais depuis SharePoint 2010, les résultats de recherche sont limités à maximum 10000 par requête. Bien que cela soit généralement plus que suffisant, il peut arriver que cela s'avère insuffisant.

J'ai récemment eu besoin de mettre en cache tous les profils utilisateurs (quelques propriétés seulement) sur une implémentation SharePoint qui contenait plus ou moins 15000 profils...J'ai donc tout simplement essayé d'exécuter la requête suivante:

 
FullTextSqlQuery PeopleQuery = new FullTextSqlQuery(ServerContext.Current);  
PeopleQuery.QueryText = "SELECT AccountName FROM Scope() WHERE \"Scope\"='People'";  
PeopleQuery.ResultTypes = ResultType.RelevantResults;  
PeopleQuery.RowLimit = 20000;  
ResultTableCollection PeopleQueryResults = PeopleQuery.Execute();  
ResultTable PeopleQueryResultsTable = PeopleQueryResults[ResultType.RelevantResults];  
Console.WriteLine(PeopleQueryResultsTable.TotalRows); 
 

Ce qui a provoqué l'erreur suivante:

Si je retire le RowLimit=20000, cela fonctionne mais cela ne me retourne que 50 résultats. En gros, dès qu'on dépasse 10000, on a l'erreur ci-dessus qui n'est de sucroît pas très explicite vous en conviendrez :).

En fait, depuis SharePoint 2010, les résultats de requête de recherche sont limités à 10000 par défaut afin d'éviter de surcharger SQL serveur avec des requêtes lourdes et pour protéger SharePoint contre des attaques potentielles.

Si, après conseil éventuel de Microsoft, vous pensez ne pas être dans une situation critique en termes d'exposition (attaques) et en termes de ressources, vous pouvez augmenter cette limite de la façon suivante :

- Ouvrez une console PowerShell SharePoint
- Exécutez les commandes suivantes :

 
$ssa = Get-SPEnterpriseSearchServiceApplication  
$ssa.UpdateSetting("Config:qp_MaxResultsReturned", 20000)  
$ssa.Update() 

20000 étant dans ce cas-ci, la nouvelle valeur limite. Pour que le changement soit effectif, vous devez faire un IISRESET ou un Recycle de l'application pool associé au service de recherche.

J'en ai profité par la même occasion pour faire un peu de reverse engineering et j'ai constaté que d'autres paramètres étaient configurables, j'ai même fait un petit programme console qui permet de les lister et d'obtenir leurs valeurs actuelles:

 
static void Main(string[] args) 
  { 
  string[] properties = new string[] 
  {  
  "Config:qp_SecTrimCacheSize", 
  "Config:qp_MaxResultsReturned", 
  "Config:qp_SecTrimMultiplier", 
  "Config:qp_NearDupMultiplier",  
  "Config:qp_JoinMultiplier", 
  "Config:qp_JoinSdidMultiplier", 
  "Config:qp_UseSqlFirstJoinStrategy",  
  "Config:qp_AvoidSqlOuterJoins",  
  "Config:ForceAliasNormalisationEnabled", 
  "Config:UseDefaultDecimalPlaces" 
  }; 
 
  SPFarm farm = SPFarm.Local; 
  SearchServiceApplication searchApp = 
  (SearchServiceApplication)farm.Services.GetValue<SearchServiceApplication>("Search Service Application");  
  foreach (string property in properties) 
  { 
  Console.WriteLine("{0} : {1}", property, searchApp.GetSetting(property)); 
  } 
   
  } 

Ce qui affiche ceci:

Config:qp_SecTrimCacheSize : 10000
Config:qp_MaxResultsReturned : 20000 => ici ajusté...
Config:qp_SecTrimMultiplier : 2.6
Config:qp_NearDupMultiplier : 1.8
Config:qp_JoinMultiplier : 10
Config:qp_JoinSdidMultiplier : 1.01
Config:qp_UseSqlFirstJoinStrategy : False
Config:qp_AvoidSqlOuterJoins : False
Config:ForceAliasNormalisationEnabled : False
Config:UseDefaultDecimalPlaces : False

Je vous recommande bien sûr de bien vous renseigner avant de modifier l'une de ces propriétés...

Happy Coding!

Vous devez être identifié pour poster un commentaire.

09/11/2011

Permalink 08:24:10, Catégories: SharePoint, Outils, Tutos, 1611 mots   French (FR) , stephane eyskens

Mon top 10 lorsque je teste des webparts SharePoint

Salut,

Voici mon top 10 des tests que j'effectue lorsque je teste des webparts, qu'ils soient développés par moi-même ou par d'autres consultants.

1. Se logger en tant que simple visiteur

Bien souvent, les développeurs sont connectés en tant qu'administrateur local et en tant qu'administrateur de ferme sur leur machine de développement, c'est à dire, avec toutes les permissions requises quoi qu'ils fassent. Le danger, c'est que cette situation ne reflète pas le contexte réel dans lequel le webpart sera déployé.

En effet, il est important se s'authentifier uniquement avec des permissions de visiteur afin de s'assurer qu'aucune erreur liée à la sécurité ne se produise. Les types d'opération qui peuvent mener à des problèmes sont par exemple:

- Le webpart essaye d'écrire des données dans une liste d'application, dans un property bag...alors qu'évidemment, le simple visiteur n'a aucun droit d'écriture
- le webpart consomme une partie de son information dans une liste applicative utilisant des permissions uniques auxquelles les simples visiteurs n'ont pas accès.
- ....

Bien que cela semble évident, il est nécessaire d'y penser à chaque fois, ceci est d'ailleurs valables pour tous les composants SharePoint, pas seulement les webparts mais ceux-ci étant les plus "utilisés" par les visiteurs, c'est davantage nécessaire.

2. Ajouter le webpart plusieurs fois sur la même page

Ceci peut sembler anodin mais bien qu'il soit évident qu'un webpart peut par nature être ajouté plusieurs fois sur une page, tout le monde n'y pense pas et les effets indésirables sont souvent surprenants lorsqu'on n'a pas pris les précautions nécessaires.

Les 3/4 du temps, les erreurs que l'on va rencontrer seront liées au code HTML/Javascript custom généré par notre wepbart. En effet, si on ne prend garde à donner des ID/noms uniques à nos éléments DOM et à nos variables javascript, le fait d'ajouter le webpart plusieurs fois sur la page va créer plusieurs fois des éléments HTML et javascript avec les mêmes noms, ce qui provoquera une certaine confusion au niveau du navigateur.

Un exemple concret est le suivant:

Disons que vous souhaitez développer un webpart qui expose une propriété permettant de le lier à une liste SharePoint. Pour faire simple, lorsque la liste est choisie, le webpart affiche le nombre d'éléments qu'elle contient. Vous créez un Visual WebPart et dans le contrôle utilisateur (.ascx), vous implémentez le code suivant:

 
<script type="text/javascript"> 
  ExecuteOrDelayUntilScriptLoaded(GetItems, "sp.js"); 
  function GetItems() { 
  this.ClientContext = SP.ClientContext.get_current(); 
  if (this.ClientContext != undefined && this.ClientContext != null) { 
  this.ReturnedList = this.ClientContext.get_web().get_lists().getByTitle('<%= ListName %>'); 
  this.ClientContext.load(this.ReturnedList); 
  this.ClientContext.executeQueryAsync( 
  Function.createDelegate(this, this.Success), 
  Function.createDelegate(this, this.Failure)); 
  } 
  } 
  function Success(sender, args) { 
  document.getElementById("NonUniqueIDDivElement").innerText = this.ReturnedList.get_itemCount(); 
  } 
  function Failure(sender, args) { 
  document.getElementById("NonUniqueIDDivElement").innerText = "nok"; 
  } 
</script> 
<div id="NonUniqueIDDivElement"></div> 

Vous spécifiez le nom de la liste au niveau de la propriété:

Quand vous ajoutez le webpart une seule fois dans la page, vous obtenez le résultat attendu:

Si par exemple, vous l'ajoutez une seconde fois dans la page et vous le liez à la bibliothèque "docs" qui contient seulement 1 élément, vous obtenez ceci:

Le deuxième webpart affiche son résultat "1" dans la zone du premier...La raison est évidente, l'élément

que nous avons généré a le même ID pour les deux webparts, du coup, le navigateur affecte la valeur à la première instance qu'il trouve ayant cet ID...Si vous n'aviez ajouté le webpart qu'une seule fois dans la page, vous n'auriez jamais constaté le problème.

Ceci est valable pour le javascript ou il faut s'assurer que le contexte js soit bien indépendant d'une instance de webpart à une autre. Ceci dit, il faut également voir si le scénario métier impose cette vigileance. Si pour des raisons évidentes, vous savez que le webpart que vous développez ne sera jamais ajouté plus d'une fois, il n'est peut-être pas indispensable de prendre toutes ces précautions mais il faut au moins connaître les risques.

3. Vérifier que le fichier webpart .webpart ou .dwp soit bien supprimé de la galerie

Les développeurs SharePoint expérimentés connaissent bien ce problème...Lorsque vous créez une feature, déployez le fichier .webpart dans la galerie via un "Module", à l'activation de la feature, le fichier est bien déployé mais à la désactivation, celui-ci n'est pas supprimé de la galerie.

L'impact est donc que même après désactivation, les utilisateurs voient toujours ce webpart comme disponible et peuvent toujours l'ajouter dans les pages. Pour éviter cette incohérence, vous pouvez utiliser un feature receiver qui supprime le fichier .webpart de la galerie lors de la désactivation de la feature.

4. Vérifier l'association entre la feature et le webpart

Comme vous le savez, on utilise généralement une feature scopée à "Site" pour déployer un webpart. Comme vous le savez aussi, pour tout webpart, il est nécessaire qu'une entrée de type "SafeControl" soit ajoutée dans le web.config de l'application SharePoint sur laquelle on déploie le webpart.

Ceci signifie donc, qu'en terme de sécurité SharePoint, le webpart sera utilisable pour toutes les collections de sites associée à cette application SharePoint. Ceci n'est pas toujours souhaitable. Comme évoqué au point précédent, la feature nous sert à déployer le fichier .webpart dans la galerie, donc si la feature n'est pas activée, par défaut, les utilisateurs ne verront pas le webpart lorsqu'ils essayeront de l'ajouter dans la page. Cependant, un simple propriétaire de site au niveau du rootweb peut lui-même aller ajouter manuellement le webpart dans la galerie alors qu'il ne peut pas activer la feature au niveau de la site collection.

Du coup, votre webpart peut-être utilisé dans un contexte métier qui n'a pas forcément été prévu...et alors même que vous n'avez pas activé la fonctionnalité.

C'est pourquoi, pour les webparts "sensibles" d'un point de vue métier, il est préférable de s'assurer que le webpart ne soit utilisable que si la fonctionnalité a explicitement été activée par un administrateur, ceci peut se faire très facilement:

 
if(SPContext.Current.Site.Features[new Guid(“..”)] == null)  
  Error.Text = “This webpart cannot be used, ask your administrator…”;  
 

5. Valider les propriétés

Si le webpart expose des propriétés, il est important de valider le type/la nature de celles-ci, par exemple qu'un lien soit bien un lien...

6. Vérifier la CAS/Trust Level

Si le webpart est déployé dans le bin de la webapp SharePoint, il est toujours bon de modifier (sur une machine de dev) le trust level et de le mettre à WSS_Minimal et vérifier si le webpart fonctionne toujours car cela devrait être le cas. Si il a besoin de plus que WSS_Minimal, il doit implémenter sa propre politique CAS.

7. Vérifier sa réutilisation dans une SandBoxed

Depuis SharePoint 2010 et les SandBoxed solutions, pas mal de webparts pourraient se contenter d'être déployés au sein d'une SandBoxed mais par défaut, les développeurs choisissent souvent d'ignorer cette possibilité et déploient le webpart dans une solution de ferme.

Il est intéressant d'analyser la faisabilité avec une SandBoxed dans l'optique d'une migration future des composants vers le cloud et notamment Office 365 qui ne permet à l'heure actuelle, que de déployer des SandBoxed.

8. Surveiller le temps d'exécution du webpart avec le Developer Dashboard

Comme les webparts sont encapsulés au sein de la page, ils peuvent impacter de manière visible le temps ce chargement de celle-ci et par la même avoir une influence négative sur l'expérience utilisateur. Afin de rapidement identifier des problèmes de consommation de ressources, il est toujours intéressant d'activer le Developer Dashboard.

9. Vérifier le comportement du webpart en mode édition

Il est parfois surprenant de constater le comportement de certains webparts en mode édition...Un bon exemple concerne les validateurs ASP.NET. Si vous utilisez un validateur de type "RequiredFieldValidator", lorsque vous basculerez en édition de page et que vous éditerez d'autres webparts, votre webpart va générer des erreurs car vous n'aurez pas rempli les champs obligatoires...Ceci est dû au fait que lorsque vous éditez d'autres webpart, cela génère des postbacks et c'est à cause de cela que votre validateur s'enclenche et râle comme illustré ci-dessous:

Pour éviter cela, vous pouvez par exemple ajouter un contrôle supplémentaire:

 
protected override void CreateChildControls() 
  { 
  if (this.WebPartManager.DisplayMode == WebPartManager.BrowseDisplayMode) 
  { 
  Control control = Page.LoadControl(_ascxPath); 
  Controls.Add(control); 
  } 
  } 
 

Dans ce cas-ci, le webpart n'affichera rien du tout en mode édition, j'ai pris un raccourci :). Vous pouvez tenter d'être plus granulaire et n'agir que sur les valideurs.

10. Tester les opérations habituelles

Généralement cela ne pose pas de problème mais il est toujours préférable de tester la suppression et la fermeture du webpart suivie d'une restauration...

Happy Coding!

Vous devez être identifié pour poster un commentaire.

07/11/2011

Permalink 22:21:09, Catégories: Outils, 2151 mots   French (FR) , stephane eyskens

Mon TOP 10 lorsque je fais un code review SharePoint

Salut,

Voici les 10 aspects que je regarde le plus lorsque j'audite du code SharePoint.

1. Fuites mémoire
Les fuites mémoire sont récurrentes dans le développement SharePoint mais il est important d'avoir une approche pragmatique lorsqu'on essaye de les débusquer.

Tout d'abord, petit rappel sur ce qu'est une fuite mémoire: une fuite mémoire est un espace en mémoire qui n'est pas libéré après utilisation. L'accumulation de fuites mémoire peut donc mener à une saturation de cette dernière et donc à une chute de performance voire, dans le contexte SharePoint à un recyclage automatique des pools d'application IIS.

Avant d'essayer de les débusquer, il est important d'identifier le processus qui les crée. En effet, parfois, je vois des gens s'acharner à trouver des fuites mémoire dans des scripts PowerShell...Bien qu'il soit toujours préférable de libérer la mémoire directement après utilisation, il n'est pas absolument nécessaire de le faire en PowerShell car la plupart du temps, un script a une durée limitée et toute la mémoire associée au script est automatiquement libérer lorsque le processus PowerShell.exe termine son exécution.

Donc, ce n'est certainement pas les scripts que j'irais vérifier si je devais "chasser" les fuites mémoire sur une grosse implémentation SharePoint.

Le TOP 3 des compsants qui doivent être surveillés de près est :

- Les tâches planifiées : comme elles sont exécutées toutes les xx minutes/heures/jours elles sont par définition de nature à générer des fuites mémoire régulièrement. De plus, elles sont exécutées par OWSTIMER, un processus permanent qui tourne sur les serveurs de la ferme, et donc, ne libérant jamais la mémoire sauf quand le service est redémarré ou si précisémment, vous la libérer dans votre code.

- Les composants tels que les WebParts/WebControls et en fait, tous les composants chargés par w3wp.exe pour la même raison que ci-dessus. Bien que les processus w3wp.exe sont recyclés automatiquement par IIS, il est important qu'ils ne soient pas recyclés de manière intempestive et inattendue à cause de vos fuites mémoire.

- SandBoxed Solutions:à nouveau, les composants SandBoxed sont exécutés par des processus permanents sur les serveurs applicatifs SharePoint.

Pour résumer, je focalise sur toutes les fuites dont les processus parents sont permanents ou s'exécutent durant une longue période car ils sont forcément susceptibles de générer une grande quantité de fuites pouvant réellement impacter les serveurs.
Pour chasser les fuites, j'utilise l'outil SPDisposeCheck http://archive.msdn.microsoft.com/SPDisposeCheck bien connu maintenant de la communauté SharePoint.

Vérifier le nombre d'instantiation des objets SPSite et SPWeb

Comme vous le savez certainement, les objets SPWeb et SPSite sont très consommateurs de ressource. Il est donc important de minimiser les instanciations et de s'assurer de n'instancier que lorsque c'est absolument nécessaire. Par exemple, je "chasse" tout particulièrement ce type de pattern:

 
class Utils 

  public string GetWebTitle(string url) 
  { 
  using(SPSite Site = new SPSite(url)) 
  { 
  using(SPWeb Web=Site.OpenWeb()) 
  { 
  return Web.Title; 
  } 
  } 
  } 
 
  public string GetWebList(string url, Guid listid) 
  { 
  using(…)//comme ci-dessus 
  } 

C'est typiquement ce qu'il faut éviter dans la mesure où ce type de classe va forcément générer beaucoup trop d'instanciations. Plutôt que de passer une URL en paramètre, il est préférable de soit passer directement un objet de type SPWeb, ou de le passer au constructeur de la classe et d'ensuite le réutiliser dans les méthodes ou encore, et c'est ma préférence à chaque fois que cela est possible : utiliser SPContext.Current.Site et SPContext.Current.Web dans les contrôles de type WebParts, master page, webcontrols etc...et les objets dérivés de "properties" dans les event receivers, feature receivers etc...

3. L'utilisation de RunWithElevatedPrivileges

C'est parfois impératif d'un point de vue applicatif de s'impersoner avec l'identité du pool d'application lié à l'application SharePoint courante mais je constate malheureusemnet souvent un recours intempestif à cette impersonation.

Souvent, afin d'éviter tout type de problème lié à la sécurité, certains développeurs ont un recours systématique à cette élévation, je vois même cela parfois dans des applications Console alors que le code ne s'exécute même pas dans un contexte web...Il est important de limiter ces élévations car elles peuvent potentiellement causer un "trou" de sécurité et génère automatiquement un overhead de performance.

Par ailleurs, si la configuration SharePoint est bien faite, le compte lié à l'application pool de votre application n'aura pas tous les droits sur toutes les ressources (User Profile, autres applications, configuration database etc...), il est donc indispensable de se poser les vraies questions en matière de sécurité.

4. Vérifier que le scope des features est correct

Ne demandez pas à une fonctionnalité d'une collection de sites d'effectuer des modifications au niveau de l'application, dans ce cas, scopez votre feature à WebApplication car vous pourrez raisonnablement penser que celui qui activera cette fonctionnalité aura les droits requis pour modifier l'application car il devra le faire soit depuis la Centrale d'Administration, soit par PowerShell, donc dans tous les cas, il aura plus de permissions qu'un administrateur de collection de sites...

D'ailleurs, toute feature scopée à WebApplication ou Farm doit être contrôlée par les administrateurs de la ferme pour s'assurer que votre solution ne modifie pas la configuration de manière inattendue.

5. Vérifier que les fonctionnalités sont activables/désactivables/upgradable via PowerShell

De nos jours, PowerShell est de plus en plus utilisé dans les environnements SharePoint. Souvent, les équipes administrant SharePoint, déploient vos solutions et activent les fonctionnalités nécessaires via PowerShell or PowerShell s'exécute en dehors de tout contexte web. Ceci signifie que concrètement, si vous avez un feature receiver, qui utilise SPServiceContext.Current pour se connecter aux User Profiles par exemple, cela fonctionnera lorsque la fonctionnalité est activée manuellement depuis les interfaces web SharePoint mais cela crashera lorsqu'elle sera activée depuis PowerShell car cet objet sera NULL...Globalement, tous les objets contextuels du type SPContext.Current, SPServerContext.Current seront NULL depuis une exécution PowerShell.

Il faut donc s'assurer que votre code est suffisament robuste pour pouvoir être déclenché depuis PowerShell.

6. Modification du Web.config

SharePoint n'est pas ASP.NET...arrêtez donc de continuer à utiliser appsettings pour stocker vos paramètres applicatifs :). Les fichiers web.config ne doivent être modifiés que par les solutions (WSP) SharePoint, il faut éviter d'aller manuellement les modifier pour une raison x ou y car le jour où vous ajouterez un server WFE à votre ferme, vous risquez d'oublier d'ajuster le web.config et vous rencontrerez des problèmes fonctionnels qui ne seront pas facile à diagnostiquer.

SharePoint offre des alternatives bien plus intéressantes pour stocker vos paramètres applicatifs comme par exemple les property bag et les SPPersistedObject. http://msdn.microsoft.com/en-us/library/microsoft.sharepoint.administration.sppersistedobject.aspx

De plus, si vous devez réellement modifier un web.config comme par exemple pour référencer un HTTPModule, il est préférable d'en discuter avec les administrateurs pour voir quelle est leur politique en la matière car ils vont diront soit d'utiliser le framework SharePoint pour le faire via SPWebConfigModification, soit de documenter le changement afin qu'ils puissent eux-mêmes procéder aux modifications...

7. Vérifier qu'aucun fichier par défaut de SharePoint ne soit écrasé

Une solution WSP peut en théorie tout détruire car vous pouvez potentiellement écraser tous les fichiers SharePoint délivrés par défaut...Si, pour une raison ou l'autre, vous êtes forcé d'écraser un fichier standard (le fichier webtemp est un cas classique..), il est important de s'assurer que vous restez dans une configuration supportée par Microsoft car le jour où vous avez un problème, si Microsoft voit que vous avez écrasé des fichiers système, ils pourraient refuser de traiter vos tickets et vous demander de vous remettre en conformité.

De plus, ce type de changement n'est pas perraine dans la mesure où un service pack, un cumulative update pourrait réécraser votre version par une version standard mise à jour...

8. Vérifier que les composants sont "Farm aware"

La plupart du temps, les développeurs développent sur une machine standalone ou sur une machine SharePoint combinée à un serveur SQL à part...Ceci est rarement le cas dans un environnement de production ou même d'acceptance ou bien souvent, vous avez au moins 2 ou 3 serveurs SharePoint combinés à un serveur SQL. Cette différence de topologie peut générer des erreurs qui n'avaient pas été anticipées au niveau du développement.

Typiquement, si vous créez une tâche planifiée avec un locktype de type "none", SharePoint va exécuter cette tâche sur tous les serveurs de la ferme alors que si vous spécifiez le locktype "job", il ne l'exécutera que sur un seul serveur.

En développement, vous ne verrez aucune différence entre l'un et l'autre puisque vous n'avez qu'un serveur. Ceci pourrait par contre avoir d'énormes impacts lors d'un déploiement en production car la topologie étant différente, le job pourrait s'exécuter plusieurs fois alors que vous n'escomptiez qu'une seule exécution...

9. Vérifier qu'il n'y ait pas de DLL superflue ainsi que le ciblage des déploiements

Que vous utilisiez WSPBuilder ou Visual Studio, par défaut, ils créent tous les deux une DLL en output de projet, même si votre projet SharePoint ne contient que des artifacts et pas une seule ligne de code compilée...J'ai donc régulièrement vu des solutions WSP contenir des DLL vides...parce que les développeurs n'avaient pas pris la précaution d'éviter de l'embarquer dans la solution. Avec Visual Studio, il suffit de spécifier l'option "Include in Package => No" et la DLL superflue ne sera pas intégrée à la solution.

Un autre aspect lié aux DLLs est le déploiement vers BIN ou GAC. Ceci est souvent source de débat et on rencontre deux types de réactions :

- Les gens qui déploient tout en GAC même si le code est loin d'avoir besoin d'un contexte FULL TRUST
- Les gens qui déploient tout en BIN avec des custom CAS.

J'essaye d'avoir une position intermédiaire. Les CAS policies sont un beau concept mais c'est honnêtement assez complexe et un effort supplémentaire à ne pas sous-estimer. Heureusement, depuis SharePoint 2010, on a les SandBoxed Solutions qui sont par défaut hyper sécurisées...je préfère donc aller vers cette option si je n'ai pas besoin d'un code FULLY TRUSTED. Il est important de toujours tenir compte du contexte. J'ai déjà vu des intégristes de la sécurité pousser des DLL contenant des event handlers dans le BIN et puis s'étonner que le système crashe lorsque l'event est déclenché depuis un processus de type PowerShell, Workflow...car tout naturellement, ces contextes n'ont pas accès au contexte WEB et donc, n'ont pas la connaissance des DLL déployées dans le BIN...

Donc si par exemple, vous avez un évènement de type ItemAdding, vous l'encapsulez dans une DLL que vous déployez dans le BIN car vous êtes vraiment soucieux de la sécurité...Disons que sur ItemAdding, vous devez vérifier que la taille du fichier ne dépasse pas les 3KG sinon vous devez refuser l'upload. Vous effectuez vos tests depuis les interfaces SharePoint et cela fonctionne bien. Disons que derrière, vous avez un timer job qui fait également des upload dans cette bibliothèque, vous constatez avec effroi que des fichiers de plus de 3KG ont pu être ajoutés...juste parce que vous n'avez pas déployé votre DLL en GAC parce que vous êtes vraiment un gars soucieux de la sécurité :)...Bref, la morale de l'histoire c'est que tout est question de contexte et de scénario métier. La politique du "tout dans le GAC" ou "tout dans le BIN" ne fonctionne tout simplement pas.

10. Vérifier la procédure de déploiement

Ce point-ci est facile...Juste vérifier que la procédure de déploiement ne contient QUE des solutions WSP (ou CAB) et qu'on ne vous parle pas d'exécuter un fichier .bat ou autre...car à nouveau, seules les solutions SharePoint sont adaptées aux différentes topologies.

Happy Coding

Vous devez être identifié pour poster un commentaire.

Permalink 21:07:53, Catégories: Outils, 46 mots   French (FR) , stephane eyskens

Utiliser une URL dynamique (contextuelle par rapport à l'environnement cible) pour déployer un modèle BCS

Salut,

Si vous vous demandez comment éviter de spécifier une URL statique dans le fichier feature.xml pour déployer un modèle BCS, vous trouverez une réponse intéressante sur mon blog UK.

Plus d'infos ici:

http://www.silver-it.com/node/92

Happy Coding!

Vous devez être identifié pour poster un commentaire.

Permalink 21:03:55, Catégories: SharePoint, Outils, Tutos, 71 mots   French (FR) , stephane eyskens

SharePoint 2010, synchronization des profils utilisateurs via BCS

Salut,

Si, après avoir mis en place une synchronization des profils utilisateurs via une couche BCS développée ou crée avec SharePoint Designer, vous rencontrez le problème suivant:

stopped-extension-dll

Ce problème est peut-être dû au format des données provenant de la source à l'autre bout de votre couche BCS.

Pour plus d'infos, vous pouvez consulter mon blog UK:

http://www.silver-it.com/node/91

Happy Coding!

Vous devez être identifié pour poster un commentaire.

07/08/2011

Permalink 16:24:51, Catégories: SharePoint, Outils, Tutos, 92 mots   French (FR) , stephane eyskens

DropBox pour SharePoint 2010

Salut,

J'ai développé une DropBox pour SharePoint Server. L'idée est de permettre aux utilisateurs de l'intranet d'associer quelques documents à leur profil sans pour autant autoriser les My Sites.

Comme vous le savez, ceux-ci doivent être entourés d'une gouvernance pour éviter une prolifération mal contrôlée de My Sites hétérogènes et disparates dans l'entreprise.

La DropBox est une alternative très légère ne nécessitant pas spécialement de gouvernance.

Plus d'infos ici (vidéo inclue) : http://www.silver-it.com/node/90

Happy Coding!

Vous devez être identifié pour poster un commentaire.

Permalink 16:21:31, Catégories: Outils, 35 mots   French (FR) , stephane eyskens

Outil pour le SharePoint Developer DashBoard

Salut,

J'ai développé un petit outil permettant d'identifier rapidement les composants qui consomment beaucoup de ressources.

Cet outil enrichit le Developer Dashboard de SharePoint, plus d'infos ici : http://www.silver-it.com/node/89

Happy Coding!

Vous devez être identifié pour poster un commentaire.

Permalink 16:19:41, Catégories: Outils, 39 mots   French (FR) , stephane eyskens

A la découverte d'Office 365

Salut,

J'ai récemment plongé dans Office 365, voici le résultat de mes investigations:

Personnaliser la page maître du site public : http://www.silver-it.com/node/87
Office 365, de l'excitation à la frustration : http://www.silver-it.com/node/88

Happy Coding!

Vous devez être identifié pour poster un commentaire.

Permalink 16:17:25, Catégories: Outils, 32 mots   French (FR) , stephane eyskens

Utiliser le Client Object Model depuis un Document Information Panel personnalisé

Salut,

J'ai enregistré une vidéo démontrant comment utiliser le COM depuis les clients Office via un custom DIP.

Plus d'infos sur Channel9 : http://channel9.msdn.com/posts/Using-the-SharePoint-Client-Object-model-...

Happy Coding!

Vous devez être identifié pour poster un commentaire.

« Page Précédente 1 2 3 ... 5 6 7 Page suivante »

Liste des blogs

Blog de Stephane Eyskens sur SharePoint

Catégories


Rechercher

<  Janvier 2012  >
Lun Mar Mer Jeu Ven Sam Dim
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31          

Syndiquez ce blog XML

Articles :

Commentaires :

 
 
 
 
Partenaires

Hébergement Web