Boite à outils Office

Filtrer et récupérer des éléments d’une liste grâce à Linq et les méthodes d’extensions

Développement, SharePoint No Comments »

– UPDATE —

J’ai changé ma méthode pour récupérer directement une valeur dans ma liste de configuration. Je faisais un query qui me retournais une collection que je filtrais ensuite pour récupérer ma valeur. Alors qu’il suffisait simplement de le faire directement dans mon query Nerd

– UPDATE —

Dans mes applications SharePoint, j’utilise très souvent une liste ou je stocke l’ensemble de mes éléments de configuration comme par exemple :

  • Email sender pour mes workflows
  • Sujet d’un email
  • Corps d’un email
  • Url d’un WebService
  • Et bien d’autres choses encore…

Le gros avantage est que le webmaster du site peut facilement (à ses risques et périls tout de même Nerd) mettre à jour les informations et ceci sans reset de l’application pool ou redémarrage de l’appli web sous IIS dans le cas par exemple de variables stockées dans le web.config.

La structure de ma liste est toute simple :

  • Key (le champ title de base renommé);
  • Value (la valeur à retourner) de type « Multiline of text » en mode Plain Text;
  • Category, un champ de type « Choice » qui me permet de regrouper mes informations. Par exemple, sur une page, il peut m’arriver d’avoir à récupérer plusieurs valeurs de configuration. Ce regroupement, me permet de les récupérer via un seul CAML Query. Ceci m’évitant donc de faire un query pour chaque élément.

image 

J’avais donc implémenté une classe qui me permettait de récupérer ces valeurs via un CAML query sur ma liste. Mais suite à un article posté par sur les , j’ai décidé de refaire cette classe en utilisant ces et les nombreuses autres du framework .Net.

Pour commencer, j’ai étendu l’objet pour accéder à notre Liste de configuration (dans mon cas, j’ai aussi besoin d’un paramètre « Category » pour me retourner l’ensemble des valeurs car je ne souhaite pas faire une requête pour chaque entrée). Cette méthode, me retourne une interface générique de type Generic Interface . Cette interface, va me permettre ensuite de récupérer très facilement une entrée de ma configuration :

public static ILookup<String, String> ConfigurationList(this SPWeb spWeb, String Category)
{
    SPList spListConfiguration;
    ILookup<String, String> configurationEntries = null;
    // La liste n'est pas accessible aux end users.
    // On doit donc s'y connecter sur le compte admin pour y accéder
    // Optionnel, si vous avez décidé de mettre la liste en lecture pour tout vos utilisateurs
    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
        using (SPSite mySite = new SPSite(spWeb.Site.ID))
        {
            using (SPWeb myWeb = mySite.OpenWeb(spWeb.ID))
            {

Je récupère ma liste « Configuration » grâce à la méthode d’extension de :

if (myWeb.Lists.TryGet("Configuration", out spListConfiguration))
{

J’exécute ensuite mon query pour récupérer mes éléments :

    SPQuery spQuery = new SPQuery();
    spQuery.Query = String.Format(<Where><Eq><FieldRef Name='Category' /><Value Type='Choice'>{0}</Value></Eq></Where>", Category);

Et je stock les résultats dans un document XML Linq () :

XDocument xml = XDocument.Parse(spListConfiguration.GetItems(spQuery).Xml);

Je définie mon pour me simplifier la lecture du code :

XNamespace z = "#RowsetSchema";

Je renvoie tout ce flux dans mon interface générique en lui spécifiant :

  • Ma clé : key => key.Key;
  • Ma valeur à retourner: element => element.Value;
  • Mon comparateur d’égalité : EqualityComparer<String>.Default (ici comme je travaille sur des Strings je n’ai pas besoin d’en définir de particulier, mais si vous souhaitez le personnaliser, il vous suffit d’écrire une classe qui implémente Generic Interface . Je donne un exemple à la fin de ce post pour implémenter une version qui ne prend pas en compte la casse de la clé) ;
configurationEntries = (from xmlItems in xml.Descendants(z + "row").ToArray()
    select new ConfigurationEntry { Key = (String)xmlItems.Attribute("ows_Title"), Value = (String)xmlItems.Attribute("ows_Value") })
    .ToLookup(key => key.Key, element => element.Value, EqualityComparer<String>.Default);
        });
    }
onfigurationEntries;
}

J’utile cette classe pour stocker ma clé ainsi que la valeur associée dans mon query Linq :

public class ConfigurationEntry
{
    public String Key;
    public String Value;
}

Ensuite, il ne me reste plus qu’a écrire la classe qui va me rechercher mon élément dans ma collection (on peut ici choisir de lui donner un objet SPWeb ou d’utiliser le context)

public sealed class Configuration
{
    private ILookup<String, String> configurationValues;

    public Configuration(SPWeb spWeb, String category)
    {
        if (spWeb == null)
            throw new ArgumentNullException("A SPWeb object cannot be null");
        if (String.IsNullOrEmpty(category))
            throw new ArgumentNullException("A category cannot be empty");

        configurationValues = spWeb.ConfigurationList(category);
    }

    public Configuration(String category)
    {
        if (SPContext.Current == null)
            throw new ArgumentNullException("SPContext cannot be null. Please ensure that you're running a SharePoint application");
        if (String.IsNullOrEmpty(category))
            throw new ArgumentNullException("A category cannot be empty");

        configurationValues = SPContext.Current.Web.ConfigurationList(category);
    }

    public String GetEntry(String key)
    {
        return Convert.ToString(configurationValues[key].FirstOrDefault());
    }
}

Notez l’utilisation du générique , qui nous permet ici de nous retourner un String.Empty s’il n’y a pas de valeur à retourner.

Ensuite, pour l’appel, c’est très simple. Instanciez un objet Configuration et utilisez la méthode GetEntry pour récupérer vos valeurs:

Configuration configValues = new Configuration(spWeb,"SSRS");
Console.WriteLine("SSRS URL:" + configValues.GetEntry("SSRSUrl"));
Console.WriteLine("SSRS Path:" + configValues.GetEntry("SSRSPath"));
Console.WriteLine("SSRS WebPartPage:" + configValues.GetEntry("SSRSWebPartPage"));

image

On peut aussi ajouter une variante à notre méthode d’extension ConfigurationList pour récupérer directement la valeur d’une clé dans notre liste :


public static String ConfigurationList(this SPWeb spWeb, String Category, String Key)
{
    SPList spListConfiguration;
    String returnValue = String.Empty;

    SPSecurity.RunWithElevatedPrivileges(delegate()
    {
        using (SPSite mySite = new SPSite(spWeb.Site.ID))
        {
            using (SPWeb myWeb = mySite.OpenWeb(spWeb.ID))
            {
                if (myWeb.Lists.TryGet("Configuration", out spListConfiguration))
                {
                    // Modification du 25 juin 2008                    // J'ai pas fais attention en publiant mon post que cette méthode était stupide                    // En effet, j'utilisais un query qui me retournais une collection que je filtrais ensuite pour retourner ma valeur                    // Alors que je pouvais faire cela dans un seul CAML Query smile_nerd                    SPQuery spQuery = new SPQuery();
                    spQuery.Query = String.Format("<Where><And><Eq><FieldRef Name='Title' /><Value Type='Text'>{0}</Value></Eq><Eq><FieldRef Name='Category' /><Value Type='Choice'>{1}</Value></Eq></And></Where>", Key, Category);

                    SPListItemCollection items = spListConfiguration.GetItems(spQuery);
                    if (items != null
                        && items.Count > 0)
                        returnValue = Convert.ToString(items[0]["Value"]);

                    // SPQuery spQuery = new SPQuery();                    // spQuery.Query = String.Format("<Where><Eq><FieldRef Name='Category' /><Value Type='Choice'>{0}</Value></Eq></Where>", Category);                    // XDocument xml = XDocument.Parse(spListConfiguration.GetItems(spQuery).Xml);                    // XNamespace z = "#RowsetSchema";                    // configurationEntries = (from xmlItems in xml.Descendants(z + "row")                    // select new ConfigurationEntry { Key = (String)xmlItems.Attribute("ows_Title"), Value = (String)xmlItems.Attribute("ows_Value") })                    // .ToLookup(key => key.Key, element => element.Value, EqualityComparer<String>.Default);                    // returnValue Convert.ToString(configurationEntries[Key].FirstOrDefault()); 

                }
            }
        }
    });

    return returnValue;
}

La récupération de votre valeur se faisant comme ceci:

Console.WriteLine("SSRS Url:" + spWeb.ConfigurationList("SSRS", "SSRSURL"));

image

Comme promis, voici ici un exemple de classe qui vous permet de comparer 2 Strings sans distinction de la casse :

public class StringNoCaseSensitive : IEqualityComparer<String>
{
    public Boolean Equals(String val1, String val2)
    {
        return (val1.ToLower() == val2.ToLower());
    }
    public int GetHashCode(string obj)
    {
        return obj.ToLower().GetHashCode();
    }
}

Dans l’appel du ILookup,remplacer EqualityComparer<String>.Default par new StringNoCaseSensitive() et le tour est joué…

Mettre à jour le web.config de SharePoint

Développement, SharePoint No Comments »

MISE A JOUR DU 06 JUIN 2008
Pour contourner ce problème, il suffit en fait d’ajouter l’attribut id sur votre tag d’action:

<add path="configuration/system.web" id="{45C74BC1-DBA5-489f-A6E9-6932C25F1D97}">
    <xhtmlConformance mode="Strict" />
</add>

Ceci évitant la recréation multiple de vos actions. Grand merci à pour cet éclaircissement. Comme quoi la communauté SharePoint est plus forte que le SDK Eye-rolling

——————————-

BILLET ORIGINAL

Je reprend ici un post de , au sujet de .

Je ne connaissais pas ce système de mise à jour via un flux xml. J’utilisais pour ce genre de déploiement le modèle objet et son .

Intéressé par son post, j’ai donc essayé de le mettre en place sur un de mes projets.

J’ai donc ajouté dans un fichier xml (déployé par ma feature dans le répertoire config du 12) cette ligne:

<add path="configuration/system.web">
    <xhtmlConformance mode="Strict" />
</add>

Pour déployer ce fichier dans votre web.config, il vous faut exécuter la commande :

stsadm -o copyappbincontent

Lors de la toute première commande tout marche parfaitement bien, mon noeud est bien ajouté là ou je le voulais.

<system.web>
    ....
    <xhtmlConformance mode="Strict" />
</system.web>

Le problème de cette commande add est qu’elle ne vérifie pas la présence du noeud à ajouter. Ce qui fais que si vous exécutez une nouvelle fois la commande stsadm -o copyappbincontent, ce noeud est ajouté une deuxième fois. Et dans le cas précis de cet exemple, cela fais planter mon application web car le noeud xhtmlConformance doit être unique.

image

Pour remédier à cela, j’ai simplement ajouté une commande remove juste avant la commande add:

<remove path="configuration/system.web/xhtmlConformance" />

Pour info, la valeur de path est une fonction xpath, vous pouvez donc allez chercher l’élément à supprimer même si les noeuds fils ont le même nom.

<remove path="configuration/system.web/httpModules/add[@name='Session']" />
<add path="configuration/system.web/httpModules">
    <add name="Session" type="System.Web.SessionState.SessionStateModule"/>
</add>

ps: Attention pour les fermes, le stipule bien que cette commande doit être exécutée sur chaque serveur de la ferme.

MOSS ROBOTS meta tag

Développement, SharePoint No Comments »

Je suis en train de travailler sur un site internet sous MOSS et j’ai pas mal de travail à faire sur le rendu d’une page MOSS, qui n’est absolument pas accessible.

Avant de commencer un projet (aussi bien en développement qu’en conseil), j’aime toujours relire les guidances ou autres proof of concepts, afin d’être certains de choisir la bonne solution. Cela me prend souvent beaucoup de temps, mais je pense qu’en prenant soin de lire ce que les autres acteurs on pu faire, on gagne un temps précieux à ne pas commettre les mêmes erreurs. Donc pour en revenir au sujet, je lisais ce qu’il se faisait en termes de modification du rendu de control SharePoint. Ce que j’ai constaté, c’est qu’en majorité, la plupart des articles propose de réécrire son propre custom control afin d’être sur de bien maitriser le rendu. Dans un de ses excellents articles, (je vous recommande la lecture de son blog, qui est une véritable mine d’or) proposait de changer le rendu du tag « RobotsMetaTag » par un custom control : . Ma question a été, pourquoi ne pas utiliser à la place un control adapter ? Cette solution me paraissait plus « propre », car l’on continue à se baser sur le contrôle de base. Stefan a eu la gentillesse de me répondre et d’approuver ma solution Nerd. Il en a profité par la-même pour écrire un poste à ce sujet : .

Grouper les SPItems renvoyés par SPQuery via une requête linq

Développement, SharePoint No Comments »

Auparavant, lorsque je voulais regrouper mes valeurs issues d’un query sur une liste. Je devais faire tout un tas de manipulations, comme :
-fonction xpath : pour regrouper mes items
-sur le regroupement faire des calculs si nécessaire
En effet, le ou l’utilisation d’une vue () avec un regroupement, renvoi a chaque fois l’ensemble des éléments de la liste. Grâce à , on peut maintenant récupérer et regrouper très facilement  nos résultats. Comme par exemple, ici je souhaitais avoir une liste unique de catégories présentent dans ma liste :

// Ma liste
SPList spList = null;
try { spList = SPContext.Current.Site.RootWeb.Lists["MaListe"]; }
catch { }

// si null, exception
if (spList == null) throw new NullReferenceException("La liste  n'existe pas");

// Recuperation du nom interne de la colonne
if (!spList.Fields.ContainsField("Categorie")) throw new NullReferenceException("La colonne categorie n'existe pas");
String categoryColumnName = spList.Fields["Categorie"].InternalName;

// Recuperation des categories distinctes triees
SPQuery spQuery = new SPQuery();
spQuery.Query = String.Format("<OrderBy><FieldRef Name='{0}' /></OrderBy>", categoryColumnName);

// Chargement du Xml
XDocument xml = XDocument.Parse(spList.GetItems(spQuery).Xml);

// Ajout du namesplace pour recuperer mes valeurs
XNamespace z = "#RowsetSchema";

// IMPORTANT: n'oubliez pas de typer votre regroupement
var groupedCategories = from allcategories in xml.Descendants(z + "row")
     group allcategories by (string)allcategories.Attribute("ows_Categorie") into distinctCategories
     select distinctCategories;

// Ensuite il nous suffit de recuperer le resultat
// Ici en l'ajoutant a un control de type Checkbox list
foreach (var cat in groupedCategories)
{
     this.cblCategories.Items.Add(new ListItem((string)cat.Key));
}

Simple non?

WP Theme & Icons by N.Design Studio
Entries RSS Comments RSS Log in
Creative Commons License