Sitecore Experience Commerce: Customising Commerce Content in the Sitecore Indexes for Storefront Search Results

In this article, we will look at how to control the storefront search results by customising the commerce content that is indexed in the Sitecore master and web indexes.

If you are looking for how to add new fields for faceting and other purposes, see Adding a property field to an index.

How Storefront Search Works

The SXA Search component creates a solr query based on a number of criteria, however focusing on how the search term is involved in the query, we see that the sxacontent field is our culprit for this customisation. This can be seen highlighted below in our storefront and in our search log.

From XC 9.3 onwards, the commerce portion of the Sitecore indexes were migrated over to the Commerce Engine for indexing performance improvements. There are a number of handlers utilised for populating the indexes with catalog data, however for managing the content related to the storefront search component we will look at the SxaContentFieldHandler.

See Catalog Search Field Handlers for a full list of handlers provided with XC, which are utilised when registering index fields in Plugin.Search.Solr.PolicySet-1.0.0.json.

The SxaContentFieldHandler Class

By default, the sellable item properties that are added to the sxacontent field consist of the following entries:

  • Tags
  • ProductId
  • Name
  • DisplayName
  • ItemDefinition
  • Brand

As we can see from the sitecore_web_index in Solr, a query for a specific 'Spectra' product, productid_t:6042260, shows that these properties are indexed against the sellable item.

{
  "sxacontent_txm":["39inch|4k|uhd|television|spectra",
    "6042260",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Product",
    "Spectra Televisions"],
  "productid_t":"6042260",
  ...
}

A common property that I have seen often requested to be included for searching is the sellable item's description, so we will use this property as well as a generic property from a custom component for demonstration purposes.

In the sample code below, we see that we inherit from the default SxaContentFieldHandler and execute the base ComposeValue method, which will populate the default fields listed above. We then simply add the properties to the sxaContent reponse object, which in this case is a list of strings.

You may have noticed that we are only adding the properties to sxacontent if a value is present. This is to minimise the bloat of empty entries and may lead to performance improvement however insignificant it may be. You may prefer to leave empty entries in the index to simplfy troubleshooting in mapping each entry back to its source property, so choose an approach that best suits you.

public class MySxaContentFieldHandler : Sitecore.Commerce.Plugin.Catalog.SxaContentFieldHandler
{
    public override object ComposeValue(object source, ConcurrentDictionary<string, object> context)
    {
        var sxaContent= base.ComposeValue(source, context) as List<string>;
        if (!(source is SellableItem) || sxaContent == null || sxaContent.Count() == 0)
        {
            return sxaContent;
        }

        var sellableItem = source as SellableItem;

        if (!string.IsNullOrWhiteSpace(sellableItem.Description))
        {
            sxaContent.Add(sellableItem.Description);
        }

        var myProperty = sellableItem.GetComponent<MyCustomComponent>().MyCustomProperty;
        if (!string.IsNullOrWhiteSpace(myProperty))
        {
            sxaContent.Add(myProperty);
        }

        return sxaContent;
    }
}

Now that we have created our modified SxaContentFieldHandler class, we register it in Plugin.Search.Solr.PolicySet-1.0.0.json for both the master and web indexes.

{
  "$type": "Sitecore.Commerce.Plugin.Search.ItemIndexablePolicy, Sitecore.Commerce.Plugin.Search",
  "IndexName": "sitecore_web_index",
  "FieldTypeMappers": [ ... ],
  "Fields": [
    {
      "$type": "Sitecore.Commerce.Plugin.Search.Solr.SolrIndexFieldConfiguration, Sitecore.Commerce.Plugin.Search.Solr",
      "Name": "sxacontent",
      "Type": "System.Collections.Generic.List`1[System.String]",
      "TypeHint": "textCollection",
      "Handler": {
        "$type": "Sitecore.Commerce.Engine.Search.MySxaContentFieldHandler, Sitecore.Commerce.Engine"
      }
    },
    ...
  ]
}

After deploying our customisation, boostrapping, and reindexing, we can see our custom entries in the sxacontent field.

{
  "sxacontent_txm":["39inch|4k|uhd|television|spectra",
    "6042260",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Habitat Spectra 39” 4K LED Ultra HD Television",
    "Product",
    "Spectra Televisions",
    "Enjoy incredible picture and dramatic detail with 4X the resolution of full HD. Enjoy incredible hues of color with technology that blends digital color for near-perfect representation. Experience Ultra HD picture quality with enhanced detail in every way.",
    "mycustomproperty"],
  "productid_t":"6042260",
  ...
}

And by performing a search in the storefront our search results now return our product from our custom search entries.

Development Considerations

Field Handlers are Synchronous

At this time, field handlers have been implemented synchronously and expect the information you require to be available on the provided source entity, e.g. sellable item in this case, therefore if you are looking at sourcing data externally it may be worth further consideration.

Following Helix Priniciples

Whether upgrading from a pre XC 9.3 solution or introducing existing component properties into the search index, by implementing the custom SxaContentFieldHandler you may be faced with having to break helix principles, i.e. housing your custom SxaContentFieldHandler in a feature project while referencing another feature project to retrieve the desired properties/content. As feature projects should not reference each other, I reluctantly chose to add MySxaContentFieldHandler to the Commerce Engine project in this instance.

References