Searching on module content on pages in Sitecore

When developing search implementation for a Sitecore site, we usually do an index search on the internal _content field which is an accumulation of all the string fields on the current item. Often the pages have a lot of important data on module datasources as opposed to be on the context item itself. We also have reference fields on the context item which might warrant updating the search results based on the business importance of such data.

To ensure that your search functionality also takes into account this additional data while retrieving results, we added a couple of computed fields to account for this data.

  • ModuleContent – This computed field accesses the presentation of an item and finds the renderings with datasources on them. It then adds all the string content on these datasource items to the computed field.
  • ReferenceContent – This computed field accesses all the fields on the context item which are reference fields and iterates through the referenced items to add into the index, the string fields of all these referenced items.
  • Now, while we can create new computed fields, we could also append to _content, which will let the search code unchanged. Writing to the _content field from the computed index field code simply appends the data into the existing field, thus extending it.

    public class ModuleContent : IComputedIndexField
        {
            public string FieldName { get; set; }
            public string ReturnType { get; set; }
    
            public object ComputeFieldValue(IIndexable indexable)
            {
                Item obj = indexable as SitecoreIndexableItem;
                if (obj?.Template == null)
                    return null;
    
                if (obj.Paths.IsContentItem)
                {
                    try
                    {
                        LayoutDefinition layout = LayoutDefinition.Parse(LayoutField.GetFieldValue(obj.Fields["__Renderings"]));
    
                        if (layout.Devices != null && layout.Devices.Count > 0)
                        {
                            DeviceDefinition dev = (DeviceDefinition)layout.Devices[0];
                            ArrayList renderings = dev?.Renderings;
                            if (renderings != null)
                            {
                                IEnumerable<RenderingDefinition> renderingDefinitions = renderings.Cast<RenderingDefinition>();
    
                                foreach (RenderingDefinition renderingDefinition in renderingDefinitions)
                                {
                                    if (renderingDefinition.ItemID != null)
                                    {
                                        if (!string.IsNullOrWhiteSpace(renderingDefinition.Datasource))
                                        {
                                            Item datasource = SitecoreHelper.GetItemByID(renderingDefinition.Datasource);
    
                                            if (datasource != null)
                                            {
                                                return datasource.Fields.Where(f => !f.Name.StartsWith("__")
                                                    && f.Name != IBase_Rendering_AssetsConstants.Rendering_ScriptsFieldName
                                                    && f.Name != IBase_Rendering_AssetsConstants.Rendering_StylesheetsFieldName
                                                    && (f.Type == "Single-Line Text" || f.Type == "Multi-Line Text" || f.Type == "Rich Text"))
                                                    .Select(f => datasource.Fields[f.ID]?.Value).Where(v => !string.IsNullOrWhiteSpace(v));
                                            }
                                        }
                                    }
                                }
                            }
                        }
    
                        return new List<string>();
                    }
                    catch (Exception ex)
                    {
                        Log.Error("Site.Project._classes.Computed_Fields.ModuleContent", ex, this);
                    }
                }
    
                return string.Empty;
            }
        }
    

    In the configuration, we include the computed field:

    <configuration xmlns:patch="http://www.sitecore.net/xmlconfig/">
      <sitecore>
        <contentSearch>
          <indexConfigurations>
            <defaultLuceneIndexConfiguration type="Sitecore.ContentSearch.LuceneProvider.LuceneIndexConfiguration, Sitecore.ContentSearch.LuceneProvider">
              <fields hint="raw:AddComputedIndexField">
                <field fieldName="_content" returnType="stringCollection" storageType="YES">
                  Site.Domain._classes.Computed_Fields.ModuleContent,Site.Domain
                </field>
              </fields>
            </defaultLuceneIndexConfiguration>
          </indexConfigurations>
        </contentSearch>
      </sitecore>
    </configuration>
    

    In a similar manner, we could include the reference item string fields, except we would filter by link / list field tyes like Multilist / Treelist / Droptree / Droplink etc in the code, to get the guids, then items and then string fields on them, to include in the index for the current item.

    When time comes for the search, we would simply do a free text index search on the _content field to get results.

    Hope this helps!

    Advertisements

Faceting on product specifications with SOLR computed fields in Sitecore

In most product repositories, we come across product specification sets for each product, which usually comprise of a key value pair.
2015-09-20_015800
We had one such scenario in our project, and we came across the requirement to facet on various specification keys.

We have currently added a list of comma separated (key, value) pairs in the specifications computed field. The computed field needed to be declared as a string collection:

<field fieldName="productspecificationstext" returnType="stringCollection" storageType="YES" indexType="string">
    mysite.ComputedFields.ProductSpecificationsText,mysite
</field>

And in the code for the computed field (implementing IComputedIndexField), you will want to split this list and return it as a List.
So this

public object ComputeFieldValue(IIndexable indexable)

would return List<string>

Here’s a sample from the index:

2015-09-20_020439

So then we facet on this field, and filter out the facets that start with (specification key + ‘,’) for the specification key we want the facets for and remove the preceding key and ‘,’.
So if I were to facet on size specification for the below data (provided we had a separator between the specification key and value), we would get:

2015-09-20_021329

Large (1)
Small (3)
Medium (1)

This way, we use the same computed field to facet on any kind of specification.

The method used would be the Sitecore.ContentSearch method:

2015-09-20_021622