Sitefinity Search Indexing Issue for Classification/Taxonomy in v15.0

sitefinity | 2024-04-20

In a recent project using Sitefinity v15.0, we encountered an unexpected issue with Sitefinity search indexing when using the default Lucene indexing. While string indexing worked fine, indexing for classification or taxonomy didn't function as expected.

After a thorough debugging session, we discovered that only the TrackedList GUID was being indexed instead of the actual GUIDs of the taxonomy items. This led us to dive deeper into Sitefinity’s search indexing mechanism and implement a solution that would properly index the taxonomy names.

🚩 The Problem

  • Issue: Only the GUID for tracked lists was being indexed, and not the actual taxonomy names.
  • Solution: By creating a custom TransparentTranslator for the Lucene index, we were able to ensure that the taxonomy names were indexed correctly.

✅ What we have done?

1. Ensured Correct Taxonomy Indexing

First, we needed to ensure that search indexing could capture the correct taxonomy ID/name. To achieve this, we followed a detailed investigation process.

2. Decompiled Telerik.Sitefinity.Search.Impl.dll

We decompiled the Telerik.Sitefinity.Search.Impl.dll to understand how the GUID translation was being handled. We found that the Search Index pipeline was using TransparentTranslator to translate GUIDs.

Screenshot1

3. Implemented a Custom Transparent Translator

We wrote a custom TransparentTranslator to handle the GUID translation properly, specifically translating taxonomy GUIDs into their names. Below is the custom translator implementation:

public class CustomTransparentTranslator : TransparentTranslator
{
    private readonly TaxonomyManager _taxonomyManager;

    public CustomTransparentTranslator()
    {
        _taxonomyManager = TaxonomyManager.GetManager();
    }

    public override object Translate(object[] valuesToTranslate, IDictionary<string, string> translationSettings)
    {
        if (valuesToTranslate.Count() > 0 && IsTrackedList<Guid>(valuesToTranslate[0]))
        {
            StringBuilder concatedStr = new StringBuilder();
            ConcatValues(valuesToTranslate, concatedStr);
            return concatedStr;
        }

        if (valuesToTranslate.Count() == 1)
        {
            return valuesToTranslate[0];
        }

        return valuesToTranslate;
    }

    private static bool IsTrackedList<T>(object data)
    {
        return data is TrackedList<T>;
    }

    private void ConcatValues(object[] data, StringBuilder concatedStr)
    {
        for (int i = 0; i < data.Length; i++)
        {
            string str = "";
            if (IsTrackedList<Guid>(data[i]))
            {
                str = TranslatedTaxonomies(data, i);
            }

            if (!str.IsNullOrEmpty())
            {
                concatedStr.Append(str);
                if (i + 1 < data.Length)
                {
                    concatedStr.Append(' ');
                }
            }
        }
    }

    private string TranslatedTaxonomies(object[] data, int i)
    {
        List<string> taxNames = new List<string>();
        foreach (Guid guid in ((TrackedList<Guid>)data[i]))
        {
            var taxon = _taxonomyManager.GetTaxon(guid);
            taxNames.Add(taxon.Name);
        }

        return string.Join(" ", taxNames);
    }
}

4. Registered the Custom Translator

We registered the CustomTransparentTranslator in the Global.asax.cs file when Sitefinity bootstraps:

PipeTranslatorFactory.RegisterTranslator(new CustomTransparentTranslator());

5. Modified the Search Results Model

Next, we extended the search result model to handle the filtering mechanism for taxonomy names.

We used a regular expression to match category filters from the query string and translated them into key-value pairs for filtering.

Screenshot2

To read the string filter and translate it to key pair value for filtering.

List<KeyValuePair<string, string>> categoriesFilterClauses = new List<KeyValuePair<string, string>>();

Match match = Regex.Match(filter, @"Category=\((.*?)\)");
if (match.Success)
{
    string categoriesStr = match.Groups[1].Value;

    // Split the categories string by comma
    string[] categories = categoriesStr.Split(',');

    // Add each category to the list as a key-value pair
    foreach (string category in categories)
    {
        categoriesFilterClauses.Add(new KeyValuePair<string, string>("Category", category.Trim()));
    }
}

var filterGroups = new List<SearchFilterGroup>();
if (categoriesFilterClauses.Count > 0)
{
    var categoriesFilterGroup = new SearchFilterGroup(QueryOperator.Or, categoriesFilterClauses);
    filterGroups.Add(categoriesFilterGroup);
}

6. Custom Search Query

We then implemented the custom search method with the SearchHelper.Search method, passing in the filter groups to ensure proper filtering.

var customResults = SearchHelper.Search(searchQuery, indexCatalogue, this.SearchFields, out totalCount, filterGroups: filterGroups, groupQueryOperator: QueryOperator.And, skip: itemsToSkip, take: take.Value, orderBy: new List<string> { this.OrderBy.ToString() }).ToList();

The SearchHelper.Search method is reference from the Sitefinity community

7. Handle Base64-Encoded Filter Parameters

Finally, we noted that the filter parameter is base64-encoded, so we converted our filter values into base64 format before using them in the query.

For instance, the category "breaking" is encoded as Y2F0ZWdvcnk9KGJyZWFraW5nKQ==.

/news-search?indexCatalogue=news-index&searchQuery=news&filter=Y2F0ZWdvcnk9KGJyZWFraW5nKQ==

📚 References