L’arrivée de la sainte WebPart

Avec SharePoint 2013, Microsoft a introduit la « WebPart de recherche de contenu » autrement appelée de l’autre côté de l’Atlantique « Content Search WebPart » (disponible uniquement On-Premise). Auparavant, la seule manière de remonter du contenu était d’utiliser la « WebPart de requête de contenu » (Content Query WebPart) et personnaliser le rendu des résultats de recherche était possible via une transformation XSLT.

Désormais, la personnalisation de l’affichage des résultats est plus accessible grâce aux « display templates » fonctionnant avec du HTML et du Javascript. Cette nouvelle WebPart n’apporte pas seulement de la flexibilité dans l’affichage des résultats mais aussi la possibilité de remonter du contenu provenant de plusieurs collections de sites en apportant également plus de possibilités pour le tri multi-niveaux ou un meilleur affinement.

Toutefois, le nouveau contenu de vos sites ne sera pas affiché tant qu’une indexation de la recherche n’aura pas été lancée, ceci également à des fins de performance car la WebPart utilise l’index de la recherche pour sa requête. Il sera bon de définir une stratégie d’indexation efficace afin que vos utilisateurs ne ressentent pas trop le delta d’affichage du contenu. Pour avoir plus d’informations sur la recherche au sein de SharePoint 2013, je vous invite à consulter nos précédents articles :

La recherche sous SharePoint 2013:

  1. Recherche SP2013 vs SP2010
  2. Mise en place du service de recherche sous SharePoint 2013
  3. Mise en place de l’architecture de crawl
  4. Managed Properties et Crawled Properties dans SharePoint 2013
  5. Mise en forme de l’interface sous Sharepoint 2013
  6. SharePoint Online : associer les propriétés de profil manquantes

Un besoin régulièrement remonté par les clients pour lesquels je suis intervenu est celui d’afficher un lien « plus d’infos » et « voir tout » pour des news. Le résultat sera de cette forme :
resultat_cswp

Avant de commencer, il vous faudra veiller à avoir créé une collection de sites de type « publication » et avoir lancé l’indexation de la recherche.

Le comportement standard

Pour comprendre l’intérêt de la personnalisation, nous allons insérer une « WebPart de recherche de contenu ». Pour cela, direction les paramètres de site :

site_settings_btn

Puis, dans la rubrique « Action de site », cliquer sur « Gérer les fonctionnalités » :

mng_site_features

Activer la fonctionnalité « WebParts de recherche et modèles» :

search_feature

 

Retourner sur votre page et l’éditer pour y insérer la WebPart située dans « Remontée de contenu » :cswp_ribbon

 

Editer la WebPart et « Modifier la requête » :

cswp_props

Basculer en mode avancé, valider puis visualiser le résultat :

cswp_query

 

La personnalisation

Tout d’abord, créer un projet SharePoint 2013 et ajouter une référence à « C:\Program Files\Common Files\microsoft shared\Web Server Extensions\15\ISAPI\Microsoft.Office.Server.Search.dll ».

Ajouter une WebPart, nous la nommerons « CustomContentSearchWebPart » :

cswp_add_wp

Faire hériter cette WebPart de « ContentBySearchWebPart ».

using System;
using System.ComponentModel;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.WebControls.WebParts;
using Microsoft.SharePoint;
using Microsoft.SharePoint.WebControls;
using Microsoft.Office.Server.Search.WebControls;
using SharePointDemo.Common.Localization;
using Microsoft.SharePoint.WebPartPages;
using System.Collections.Generic;

namespace SharePointDemo.CustomContentSearchWebPart
{
   [ToolboxItemAttribute(false)]
   public class CustomContentSearchWebPart : ContentBySearchWebPart
   {
   }
}

Y ajouter nos propriétés de WebPart : SeeMoreUrl, SeeMoreText, ReadMoreUrl et ReadMoreText.

Pour cet exemple, j’ai préféré utiliser des propriétés localisées.

[WebBrowsable(true),
LocalizedWebDisplayNameAttribute("SharePointDemo,SeeMoreUrl_Name"),
LocalizedWebDescriptionAttribute("SharePointDemo,SeeMoreUrl_Description"),
Personalizable(PersonalizationScope.Shared),
LocalizedCategoryAttribute("SharePointDemo,WebPart_Category")]
public string SeeMoreUrl { get; set; }

[WebBrowsable(true),
LocalizedWebDisplayNameAttribute("SharePointDemo,SeeMoreText_Name"),
LocalizedWebDescriptionAttribute("SharePointDemo,SeeMoreText_Description"),
Personalizable(PersonalizationScope.Shared),
LocalizedCategoryAttribute("SharePointDemo,WebPart_Category")]
public string SeeMoreText { get; set; }

[WebBrowsable(true),
LocalizedWebDisplayNameAttribute("SharePointDemo,ReadMoreUrl_Name"),
LocalizedWebDescriptionAttribute("SharePointDemo,ReadMoreUrl_Description"),
Personalizable(PersonalizationScope.Shared),
LocalizedCategoryAttribute("SharePointDemo,WebPart_Category")]
public string ReadMoreUrl { get; set; }

[WebBrowsable(true),
LocalizedWebDisplayNameAttribute("SharePointDemo,ReadMoreText_Name"),
LocalizedWebDescriptionAttribute("SharePointDemo,ReadMoreText_Description"),
Personalizable(PersonalizationScope.Shared),
LocalizedCategoryAttribute("SharePointDemo,WebPart_Category")]
public string ReadMoreText { get; set; }

Voici comment implémenter des propriétés de WebPart localisées :

/// <summary>
/// The display name must be written this way : ResourceFileName,ResourceName
/// </summary>
public sealed class LocalizedWebDisplayNameAttribute : WebDisplayNameAttribute
{
    bool m_isLocalized;

    public LocalizedWebDisplayNameAttribute(string displayName) : base(displayName) { }

    public override string DisplayName
    {
        get
        {
            if (!string.IsNullOrEmpty(base.DisplayName))
            {
                if (base.DisplayName.Contains(','))
                {
                    if (!m_isLocalized)
                    {
                        string[] values = base.DisplayName.Split(',');
                        this.DisplayNameValue = LocalizationHelpers.GetLocalizedString(values[0], values[1], CultureInfo.CurrentUICulture.LCID);
                        m_isLocalized = true;
                    }
                    return base.DisplayName;
                }
            }
            return "";
        }
    }
}

/// <summary>
/// The description must be written this way : ResourceFileName,ResourceName
/// </summary>
public sealed class LocalizedWebDescriptionAttribute : WebDescriptionAttribute
{
    bool m_isLocalized;
    public LocalizedWebDescriptionAttribute(string description) : base(description) { }
    public override string Description
    {
        get
        {
            if (!string.IsNullOrEmpty(base.Description))
            {
                if (base.Description.Contains(','))
                {
                    if (!m_isLocalized)
                    {
                        string[] values = base.Description.Split(',');
                        this.DescriptionValue = LocalizationHelpers.GetLocalizedString(values[0], values[1], CultureInfo.CurrentUICulture.LCID);
                        m_isLocalized = true;
                    }
                    return base.Description;
                }
            }
            return "";
        }
    }
}

/// <summary>
/// The category must be written this way : ResourceFileName,ResourceName
/// </summary>
public sealed class LocalizedCategoryAttribute : CategoryAttribute
{
    public LocalizedCategoryAttribute(string category): base(category) { }
    protected override string GetLocalizedString(string value)
    {
        if (!string.IsNullOrEmpty(value))
        {
            if (value.Contains(','))
            {
                string[] values = value.Split(',');
                return LocalizationHelpers.GetLocalizedString(values[0], values[1], CultureInfo.CurrentUICulture.LCID);
            }
        }
        return "";
    }
}

class LocalizationHelpers
{
    /// <summary>
    /// Use this method to get a localized resource from its name, a resource file and a chosen culture.
    /// </summary>
    /// <param name="resourceFileName">The name of the resource file</param>
    /// <param name="resourceName">The name of the resource</param>
    /// <param name="LCID">The LCID corresponds to a certain language.
    /// If the named resource does not have a value for the specified language,
    /// the method will return the value for the invariant language
    /// </param>
    /// <returns>Returns the corresponding resource value</returns>
    public static string GetLocalizedString(string resourceFileName, string resourceName, int LCID)
    {
        if (string.IsNullOrEmpty(resourceName) || string.IsNullOrEmpty(resourceFileName))
            return string.Empty;
        return SPUtility.GetLocalizedString("$Resources:" + resourceName, resourceFileName, (uint)LCID);
    }

    /// <summary>
    /// Use this method to get a localized resource from its name, a resource file and the current culture.
    /// </summary>
    /// <param name="resourceFileName">The name of the resource file</param>
    /// <param name="resourceName">The name of the resource</param>
    /// <returns></returns>
    public static string GetLocalizedString(string resourceFileName, string resourceName)
    {
        return GetLocalizedString(resourceFileName, resourceName, CultureInfo.CurrentUICulture.LCID);
    }
}

Déployer la WebPart sur votre site, l’ajouter sur une page et éditer la WebPart pour voir le résultat :

cswp_add_1

Et… vos propriétés ne s’affichent pas.

cswp_props_1

Comme vous pouvez le constater il reste encore un peu de travail… A très bientôt pour un nouvel article 😉