Fermer

septembre 5, 2022

Jetons de recherche SXA personnalisés basés sur une chaîne de requête pour divers scénarios de résultats de recherche

Jetons de recherche SXA personnalisés basés sur une chaîne de requête pour divers scénarios de résultats de recherche


Défi:

La configuration d’une page de résultats de recherche générique avec une zone de recherche et des composants de résultats de recherche peut être simple. Parfois, nous obtenons l’exigence spécifique pour la page de recherche qui se concentre sur certains champs particuliers et des ensembles spécifiques de résultats de recherche.

Considérons une page ayant un composant de résultats de recherche affichant des résultats filtrés en fonction d’une chaîne de requête comme fullname=Rakesh Gupta. Ici, le nom complet est un champ calculé qui a des valeurs concaténées des champs « Prénom », « Deuxième prénom » et « Nom » avec un délimiteur d’espace.

Voici les cas de résultats de recherche dont nous allons explorer la mise en œuvre.

  1. Résultats où la valeur du champ donné correspond aux mots d’entrée exacts et est insensible à la casse. Par exemple, lorsque « Rakesh Gupta » ou « rakesh gupta » est recherché, le résultat contient toutes les entrées correspondant à « Rakesh Gupta ».
  2. Les résultats où la valeur du champ donné correspond à l’un des mots d’entrée et sont insensibles à la casse. Par exemple, lorsque « Rakesh Gupta » ou « rakesh gupta » est recherché, le résultat contient soit « rakesh » soit « gupta ».
  3. Résultats où la valeur du champ donné commence par l’un des mots d’entrée.
  4. Résultats où la valeur de champ donnée contient l’un des mots d’entrée en tant que sous-chaîne.
  5. Résultats où la valeur de champ donnée correspond aux mots d’entrée exacts et est sensible à la casse.

Prérequis:

  1. Sitecore 10.2 avec un site locataire SXA.
  2. Bon à lire rapidement le post – Jeton SXA personnalisé pour la requête de portée de recherche afin de prendre en charge toutes les opérations de filtrage

La solution:

Pour tester l’implémentation, créez un nouveau modèle de page nommé « Développeur » héritant du modèle de page existant. Ajoutez les champs « Prénom », « Deuxième prénom » et « Nom de famille » de type de champ de texte sur une seule ligne. Quelque part sous l’élément Accueil, créez une nouvelle page à partir du modèle de page existant à des fins de résultat de recherche. Ajoutez-y le composant de résultats de recherche. Créez plusieurs éléments à partir du modèle Developer. Fournissez les valeurs des champs nouvellement ajoutés dans ces éléments.

Reconstruisez vos index après avoir déployé la solution ou réindexez l’arborescence au niveau de l’élément parent si vous testez par rapport à la base de données principale pour gagner du temps d’indexation 🙂

Vous pouvez consulter le Référentiel GitHub pour le code source et les fichiers de configuration.

Classe de champ calculé FullName

using Sitecore.ContentSearch;
using Sitecore.ContentSearch.ComputedFields;
using Sitecore.Data.Items;

namespace CustomSXA.Foundation.Search.ComputedFields
{
    public class FullName : AbstractComputedIndexField
    {
        public override object ComputeFieldValue(IIndexable indexable)
        {
            Item obj = (Item)(indexable as SitecoreIndexableItem);
            if (obj == null)
                return (object)null;

            string fullname = (obj["First Name"] + " " + obj["Middle Name"] + " " + obj["Last Name"]).Replace("  ", " ");
            if (string.IsNullOrWhiteSpace(fullname.Trim()))
                return null;
            return fullname;
        }
    }
}

Patchez la classe ci-dessus comme ci-dessous.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <contentSearch>
      <indexConfigurations>
        <defaultSolrIndexConfiguration type="Sitecore.ContentSearch.SolrProvider.SolrIndexConfiguration, Sitecore.ContentSearch.SolrProvider">
          <documentOptions type="Sitecore.ContentSearch.SolrProvider.SolrDocumentBuilderOptions, Sitecore.ContentSearch.SolrProvider">
            <fields hint="raw:AddComputedIndexField">
              <!--fullname is used in case 1 and case 2 for demo purpose-->
              <field fieldName="fullname" returnType="text">CustomSXA.Foundation.Search.ComputedFields.FullName, CustomSXA.Foundation.Search</field>
              <!--exactfullname is used in case 3 - for exact search phrase match and is case sensitive. Note the return type here is string-->
              <field fieldName="exactfullname" returnType="string">CustomSXA.Foundation.Search.ComputedFields.FullName, CustomSXA.Foundation.Search</field>
            </fields>
          </documentOptions>
        </defaultSolrIndexConfiguration>
      </indexConfigurations>
    </contentSearch>
  </sitecore>
</configuration>

Créez les éléments de portée avec les noms et les valeurs ci-dessous pour la requête de portée sous

/sitecore/content/{tenant}/{site}/Settings/Scopes

Mettez à jour l’ID du modèle avec votre modèle de développeur ou un autre modèle dont vous souhaitez prendre en compte les éléments pour les résultats.

Pour chaque cas ci-dessus, prenons un jeton de recherche SXA personnalisé et un élément d’étendue.

Sitecore - Comprendre les approches de développement : une perspective de Sitecore

Voici la classe de base héritée par les classes concrètes pour l’implémentation du jeton SXA.

using Sitecore.ContentSearch.Utilities;
using Sitecore.XA.Foundation.Search.Attributes;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text.RegularExpressions;
using System.Web;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public abstract class ItemsWithQueryStringValueInField : ResolveSearchQueryTokensProcessor
    {
        protected abstract string TokenPart { get; }

        protected abstract string Operation { get; set; }

        [SxaTokenKey]
        protected override string TokenKey => FormattableString.Invariant(FormattableStringFactory.Create("{0}|ParamName", (object)this.TokenPart));

        public override void Process(ResolveSearchQueryTokensEventArgs args)
        {
            if (args.ContextItem == null)
                return;
            for (int index = 0; index < args.Models.Count; ++index)
            {
                SearchStringModel model = args.Models[index];
                if (model.Type.Equals("sxa") && this.ContainsToken(model))
                {
                    string paramName = model.Value.Replace(this.TokenPart, string.Empty).TrimStart('|');
                    if (string.IsNullOrEmpty(paramName))
                        return;
                    this.Operation = model.Operation; //setting the operation given in the scope
                    UpdateFilter(paramName, model, args, index);
                }
            }
        }

        protected abstract void UpdateFilter(string paramName, SearchStringModel model, ResolveSearchQueryTokensEventArgs args, int index);

        protected virtual SearchStringModel BuildModel(
          string replace,
          string fieldValue)
        {
            return new SearchStringModel("custom", FormattableString.Invariant(FormattableStringFactory.Create("{0}|{1}", (object)replace.ToLowerInvariant(), (object)fieldValue)))
            {
                Operation = this.Operation
            };
        }

        protected override bool ContainsToken(SearchStringModel m) => Regex.Match(m.Value, FormattableString.Invariant(FormattableStringFactory.Create("{0}\\|[a-zA-Z ]*", (object)this.TokenPart))).Success;

        protected string GetURLRefererQueryStringParamValue(string paramName)
        {
            var queryCollection = HttpUtility.ParseQueryString(HttpContext.Current.Request.UrlReferrer.Query);
            if (queryCollection.AllKeys.Contains(paramName, StringComparer.OrdinalIgnoreCase))
            {
                return queryCollection.Get(paramName) ?? queryCollection.Get(paramName.ToLower());
            }
            return null;
        }
    }
}

Cas 1 : ItemsWithQueryStringValueInFieldAllWords

Créez l’élément d’étendue nommé ItemsWithQueryStringValueInFieldAllWords et fournissez la valeur de requête d’étendue ci-dessous.

+template:{4746BE25-C48D-4E48-9B4B-E531C57C6132};+sxa:CurrentLanguage;+sxa:ItemsWithQueryStringValueInFieldAllWords|fullname

Classe ItemsWithQueryStringValueInFieldAllWords

using Sitecore.ContentSearch.Utilities;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System.Web;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class ItemsWithQueryStringValueInFieldAllWords : ItemsWithQueryStringValueInField
    {
        protected override string TokenPart => nameof(ItemsWithQueryStringValueInFieldAllWords);

        protected override string Operation { set; get; }

        protected override void UpdateFilter(string paramName, SearchStringModel model, ResolveSearchQueryTokensEventArgs args, int index)
        {
            string queryStringValue = HttpContext.Current.Request.QueryString[paramName];
            if (string.IsNullOrEmpty(queryStringValue))
                queryStringValue = GetURLRefererQueryStringParamValue(paramName);
            if (string.IsNullOrEmpty(queryStringValue))
                return;
            args.Models.Insert(index, this.BuildModel(paramName, queryStringValue)); //pass the field value for filter
            args.Models.Remove(model);
        }
    }
}

Corrigez la classe donnée à la fin, ou vérifiez Fichier CustomSXA.Foundation.Search.config.

Accédez à la page de résultats de recherche et modifiez les propriétés de contrôle des résultats de recherche et définissez ItemsWithQueryStringValueInFieldAllWords dans le champ Portée, enregistrez et testez la page comme indiqué ci-dessous.

  Élémentsavecvaleurdechaînederequêtedanslechamptouslesmots

Élémentsavecvaleurdechaînederequêtedanslechamptouslesmots

De même, testez les autres cas comme indiqué ci-dessous.

Cas 2 : ItemsWithQueryStringValueInFieldAnyWord

+template:{4746BE25-C48D-4E48-9B4B-E531C57C6132};+sxa:CurrentLanguage;sxa:ItemsWithQueryStringValueInFieldAnyWord|fullname

Classe ItemsWithQueryStringValueInFieldAnyWord

using Sitecore.ContentSearch.Utilities;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System.Web;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class ItemsWithQueryStringValueInFieldAnyWord : ItemsWithQueryStringValueInField
    {
        protected override string TokenPart => nameof(ItemsWithQueryStringValueInFieldAnyWord);

        protected override string Operation { set; get; }

        protected override void UpdateFilter(string paramName, SearchStringModel model, ResolveSearchQueryTokensEventArgs args, int index)
        {
            string queryStringValue = HttpContext.Current.Request.QueryString[paramName];
            if (string.IsNullOrEmpty(queryStringValue))
                queryStringValue = GetURLRefererQueryStringParamValue(paramName);
            if (string.IsNullOrEmpty(queryStringValue))
                return;
            //split the search phrase into words and pass each word for filter with OR condition i.e. should operation
            //so we can filter result containing any of them.
            //note the should operation is set in the base class and was supplied from the scope query filter i.e. no toggle filter set in the scope so default is 'should'
            string[] allWords = queryStringValue.Split(' ');        
            for (int i = 0; i < allWords.Length; i++)
            {
                args.Models.Insert(index, this.BuildModel(paramName, allWords[i])); //pass the field and field value for filter
                args.Models.Remove(model);
            }
            args.Models.Insert(index, this.BuildModel(paramName, queryStringValue)); //pass the query string value i.e. the input words phrase
            args.Models.Remove(model);
        }
    }
}
Élémentsavecunevaleurdechaînederequêtedansunchamptoutmot

Élémentsavecunevaleurdechaînederequêtedansunchamptoutmot

Cas 3 : ItemsWithQueryStringValueInFieldStartsWithAnyWord

+template:{4746BE25-C48D-4E48-9B4B-E531C57C6132};+sxa:CurrentLanguage;sxa:ItemsWithQueryStringValueInFieldStartsWithAnyWord|fullname

Classe ItemsWithQueryStringValueInFieldStartsWithAnyWord

using Sitecore.ContentSearch.Utilities;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System.Web;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class ItemsWithQueryStringValueInFieldStartsWithAnyWord : ItemsWithQueryStringValueInField
    {
        protected override string TokenPart => nameof(ItemsWithQueryStringValueInFieldStartsWithAnyWord);

        protected override string Operation { set; get; }

        protected override void UpdateFilter(string paramName, SearchStringModel model, ResolveSearchQueryTokensEventArgs args, int index)
        {
            string queryStringValue = HttpContext.Current.Request.QueryString[paramName];
            if (string.IsNullOrEmpty(queryStringValue))
                queryStringValue = GetURLRefererQueryStringParamValue(paramName);
            if (string.IsNullOrEmpty(queryStringValue))
                return;
            //split the search phrase into words and pass each word for filter with OR condition i.e. should operation
            //so we can filter result containing any of them.
            //note the should operation is set in the base class and was supplied from the scope query filter i.e. no toggle filter set in the scope so default is 'should'
            string[] allWords = queryStringValue.Split(' ');        
            for (int i = 0; i < allWords.Length; i++)
            {
                if (!string.IsNullOrEmpty(allWords[i]))
                {
                    //pass the field name and value for filter.
                    //Since * is applied in end, it will consider result items where field value starts with the given input words.
                    args.Models.Insert(index, this.BuildModel(paramName, allWords[i] + "*")); 
                    args.Models.Remove(model);
                }
            }
            args.Models.Insert(index, this.BuildModel(paramName, queryStringValue)); //pass the query string value i.e. the input words phrase
            args.Models.Remove(model);
        }
    }
}
Les éléments avec une valeur de chaîne de requête dans le champ commencent par n'importe quel mot

Les éléments avec une valeur de chaîne de requête dans le champ commencent par n’importe quel mot

Cas 4 : ItemsWithQueryStringValueInFieldSubstringAnyWord

+template:{4746BE25-C48D-4E48-9B4B-E531C57C6132};+sxa:CurrentLanguage;sxa:ItemsWithQueryStringValueInFieldSubstringAnyWord|fullname

Classe ItemsWithQueryStringValueInFieldSubstringAnyWord

using Sitecore.ContentSearch.Utilities;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System.Web;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class ItemsWithQueryStringValueInFieldSubstringAnyWord : ItemsWithQueryStringValueInField
    {
        protected override string TokenPart => nameof(ItemsWithQueryStringValueInFieldSubstringAnyWord);

        protected override string Operation { set; get; }

        protected override void UpdateFilter(string paramName, SearchStringModel model, ResolveSearchQueryTokensEventArgs args, int index)
        {
            string queryStringValue = HttpContext.Current.Request.QueryString[paramName];
            if (string.IsNullOrEmpty(queryStringValue))
                queryStringValue = GetURLRefererQueryStringParamValue(paramName);
            if (string.IsNullOrEmpty(queryStringValue))
                return;
            //split the search phrase into words and pass each word for filter with OR condition i.e. should operation
            //so we can filter result containing any of them.
            //note the should operation is set in the base class and was supplied from the scope query filter i.e. no toggle filter set in the scope so default is 'should'
            string[] allWords = queryStringValue.Split(' ');
            for (int i = 0; i < allWords.Length; i++)
            {
                if (!string.IsNullOrEmpty(allWords[i]))
                {
                    //pass the field name and value for filter.
                    //Since * is applied in start and end, it will be considered as substring.
                    args.Models.Insert(index, this.BuildModel(paramName, "*" + allWords[i] + "*"));
                    args.Models.Remove(model);
                }
            }
            args.Models.Insert(index, this.BuildModel(paramName, queryStringValue)); //pass the query string value i.e. the input words phrase
            args.Models.Remove(model);
        }
    }
}
Élémentsavecvaleurdechaînederequêtedanslechampsous-chaînetoutmot

Élémentsavecvaleurdechaînederequêtedanslechampsous-chaînetoutmot

Cas 5 : ItemsWithQueryStringValueInFieldExactMatch

+template:{4746BE25-C48D-4E48-9B4B-E531C57C6132};+sxa:CurrentLanguage;+sxa:ItemsWithQueryStringValueInFieldExactMatch|exactfullname

Classe ItemsWithQueryStringValueInFieldExactMatch

using Sitecore.ContentSearch.Utilities;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System.Web;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class ItemsWithQueryStringValueInFieldExactMatch : ItemsWithQueryStringValueInField
    {
        protected override string TokenPart => nameof(ItemsWithQueryStringValueInFieldExactMatch);

        protected override string Operation { set; get; }

        protected override void UpdateFilter(string paramName, SearchStringModel model, ResolveSearchQueryTokensEventArgs args, int index)
        {
            string queryStringValue = HttpContext.Current.Request.QueryString[paramName];
            if (string.IsNullOrEmpty(queryStringValue))
                queryStringValue = GetURLRefererQueryStringParamValue(paramName);
            if (string.IsNullOrEmpty(queryStringValue))
                return;
            args.Models.Insert(index, this.BuildModel(paramName, queryStringValue)); //pass the query string value i.e. the input words phrase
            args.Models.Remove(model);
        }
    }
}
Élémentsavecvaleurdechaînederequêtedanslechampcorrespondance exacte

Élémentsavecvaleurdechaînederequêtedanslechampcorrespondance exacte

ItemsWithMultipleQueryStringValueInField

De plus, si nous voulons fournir plusieurs champs ou filtres de champs calculés, nous pouvons utiliser les jetons ci-dessus plusieurs fois dans la même requête de portée. Sinon, nous pouvons avoir une seule implémentation de jeton et fournir des noms de champs séparés par des virgules comme ci-dessous.

+template:{4746BE25-C48D-4E48-9B4B-E531C57C6132};+sxa:CurrentLanguage;+sxa:ItemsWithMultipleQueryStringValueInField|first name,middle name,last name

Classe ItemsWithMultipleQueryStringValueInField

using Sitecore.ContentSearch.Utilities;
using Sitecore.XA.Foundation.Search.Pipelines.ResolveSearchQueryTokens;
using System.Web;

namespace CustomSXA.Foundation.Search.SearchQueryToken
{
    public class ItemsWithMultipleQueryStringValueInField : ItemsWithQueryStringValueInField
    {
        protected override string TokenPart => nameof(ItemsWithMultipleQueryStringValueInField);

        protected override string Operation { set; get; }

        protected override void UpdateFilter(string paramName, SearchStringModel model, ResolveSearchQueryTokensEventArgs args, int index)
        {
            string[] queryStringParams = paramName?.Split(new char[] { ',' });
            if (queryStringParams?.Length > 0)
            {
                foreach (var param in queryStringParams)
                {
                    string queryStringValue = HttpContext.Current.Request.QueryString[param] ?? HttpContext.Current.Request.QueryString[param.ToLower()];
                    if (string.IsNullOrEmpty(queryStringValue))
                        queryStringValue = GetURLRefererQueryStringParamValue(param);
                    if (string.IsNullOrEmpty(queryStringValue))
                        continue;
                    args.Models.Insert(index, this.BuildModel(param, queryStringValue)); //pass the field value for filter
                    args.Models.Remove(model);
                }
            }
        }
    }
}
Élémentsavecplusieursvaleursdechaînederequêtedanslechamp

Élémentsavecplusieursvaleursdechaînederequêtedanslechamp

Corrigez toutes les classes ci-dessus comme indiqué ci-dessous.

<?xml version="1.0"?>
<configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
  <sitecore>
    <pipelines>
      <resolveSearchQueryTokens>
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithQueryStringValueInFieldAllWords, CustomSXA.Foundation.Search" resolve="true" />
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithQueryStringValueInFieldAnyWord, CustomSXA.Foundation.Search" resolve="true" />
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithQueryStringValueInFieldStartsWithAnyWord, CustomSXA.Foundation.Search" resolve="true" />
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithQueryStringValueInFieldSubstringAnyWord, CustomSXA.Foundation.Search" resolve="true" />
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithQueryStringValueInFieldExactMatch, CustomSXA.Foundation.Search" resolve="true" />
        <processor type="CustomSXA.Foundation.Search.SearchQueryToken.ItemsWithMultipleQueryStringValueInField, CustomSXA.Foundation.Search" resolve="true" />        
      </resolveSearchQueryTokens>
    </pipelines>
  </sitecore>
</configuration>

Vérifiez les packages NuGet à partir de ici.

REMARQUE:

Nous pouvons également transmettre les paramètres de chaîne de requête avec hachage, c’est-à-dire dans le cadre de la valeur de hachage, comme indiqué ci-dessous. Cela ne recharge pas la page, mais obtient le résultat de l’API //sxa/search/results et met à jour le composant de résultats de recherche.

Paramètres de chaînes de requête avec hachage

Paramètres de chaînes de requête avec hachage

La chaîne de requête directe est accessible via un référent dans le code.

Paramètres de chaînes de requête directe

Paramètres de chaînes de requête directe

On peut avoir le formulaire à partir duquel nous pouvons accepter les entrées et les transmettre aux chaînes de requête de hachage ou aux chaînes de requête d’URL directes pour filtrer les résultats de la recherche.

L’idée générale de cet article est de comprendre les différents cas de sortie et comment ils sont implémentés au niveau du code afin que, lorsque nous avons une exigence très spécifique, nous puissions adapter le code en conséquence.

J’espère que cela t’aides. Bonne portée de recherche Sitecore !






Source link