Search is an important part of most websites to allow visitors to find content across the structure. Sitecore has with Content Search and the underlying indexing mechanism an effective search engine available where most of the tasks are handled out of the box. When publishing an item a SolR document is created with all fields on the page.
Indexing can be extended with additional field configuration and computed fields allow logic to create the content for the field. There are already several computed fields available, eg. for creating the url of the site.
What is ContentSearch
ContentSearch is an abstraction that allows Sitecore to support multiple search engines and still have the same code for the website. Behind the scenes this is solved with a LinqQueryProvider so in code we can create queries with Linq and Sitecore ContentSearch handles the translation to actual SolR queries and the required syntax. I am aware of three providers for ContentSearch, SolR is today the most common and supported out of the box. Previously there have also been a Lucene provider which was the traditional search engine in earlier versions of Sitecore, it is working with physical index files placed within the web server so it gave challenges with scaling eg. when you have multiple Content Delivery servers, content must be indexed for each server and could potentially give different results. SolR is an independent server based on Lucene so much of the query syntax is similar while several of the scaling is solved. There is also an Azure Search based provider for ContentSearch that allows you to use this cloud technology from Microsoft but everything is not working the exactly same was as with SolR.
The challenge with components regarding search
Often we want the editors to be able to create and customize the content of the page, eg. by inserting components with certain functionality such as carousels, timelines, special heroes, tables, etc. and with the superior decoupling of content from presentation in Sitecore you can have the same content rendered on multiple ways and on multiple pages.
However, as the default indexing is working on item level there are no links to those components and hereby any text within those components can not be found when searching for pages.
With computed fields and GlassMapper we have been able to solve this challenge and keep the logic in each individual component to define if there is any content that should be indexed on page level.
How we solved this
In a computed field we can fetch the layout of the page and hereby find components inserted on the page and loop through those. With GlassMapper we register a specific implementation class to handle the data source item (and often use directly in a View). We have defined an interface the component data source models can implement and provide content for the computed field.
Here is one of the models for a component where the content is provided for search. By implementing the interface it is very simple for the component developers to understand the requirements and support searching site wide
The actual computed field is in implemented by inheriting abstract Sitecore.ContentSearch.ComputedFields.AbstractComputedIndexField class. To be able to use GlassMapper it is important to set the SiteContext, even though this is usually not the case during indexing.
namespaceFeature.Search.Solr{publicabstractclassPageContentComputed:AbstractComputedIndexField{privatestaticstring_rootPath;privatestaticboolIsIndexingAllowed(Itemitem){if(_rootPath==null){_rootPath=Sitecore.Context.Site.StartPath;}returnitem.Paths.FullPath.StartsWith(_rootPath,StringComparison.OrdinalIgnoreCase);}publicoverrideobjectComputeFieldValue(IIndexableindexable){Itemitem=indexableasSitecoreIndexableItem;using(newSiteContextSwitcher(SiteContext.GetSite("website"))){if(!IsIndexingAllowed(item)){returnnull;}varsitecore=newSitecoreService(item.Database){CacheEnabled=false};varsb=GetTextForField(item,sitecore);varoutput=sb?.ToString().Trim();if(string.IsNullOrEmpty(output))returnnull;// Replace å with å etc.output=WebUtility.HtmlDecode(output);// Remove html tagsoutput=output.Replace("</p>","</p>\n").Replace("<br","\n<br");output=StringUtil.RemoveTags(output);returnoutput;}}protectedvirtualStringBuilderGetTextForField(Itemitem,SitecoreServicesitecore){varrenderings=item.GetRenderings();if(!renderings.Any()){returnnull;}varsb=newStringBuilder();foreach(varrenderinginrenderings){varmodel=GetModel(sitecore,rendering,item);if(model==null)continue;vartxt=GetTextFromModel(model,sitecore);sb.Append(txt);sb.AppendLine();}returnsb;}privateISearchPageContentComponentGetModel(ISitecoreServicesitecore,RenderingReferencerendering,Itemitem){vardataSource=rendering.Settings.DataSource;if(string.IsNullOrEmpty(dataSource)){returnnull;}if(Guid.TryParse(dataSource,outvarid)){returnGetInferredItem(sitecore,newGetItemByIdOptions(id){Language=item.Language,VersionCount=true,});}if(!dataSource.ToLower().Contains("/sitecore")){dataSource=dataSource.Replace("query:","").Replace(".","");dataSource=item.Paths.FullPath+dataSource;}returnGetInferredItem(sitecore,newGetItemByPathOptions(dataSource){Language=item.Language,VersionCount=true});}protectedISearchPageContentComponentGetInferredItem(ISitecoreServicesitecore,GetItemOptionsoptions){options.InferType=true;options.Lazy=LazyLoading.OnlyReferenced;try{varitm=sitecore.GetItem<SitecoreItemBase>(options);varresult=itmasISearchPageContentComponent;returnresult;}catch(NullReferenceException){// Glass gives null reference exception when no registered type is found for the templatereturnnull;}}protectedoverrideStringBuilderGetTextFromModel(ISearchPageContentComponentmodel,ISitecoreServicesitecore){returnmodel.ToSearchPageContentText(sitecore);}}}
The actual implementation have a few more checks and we have an implementation for both page content and secondary related content that can be prioritized/boosted differently when searching.
The computed field is registered with a Sitecore Patch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<configurationxmlns:patch="http://www.sitecore.net/xmlconfig/"xmlns:role="http://www.sitecore.net/xmlconfig/role/"xmlns:search="http://www.sitecore.net/xmlconfig/search/"><sitecorerole:require="Standalone or ContentManagement or ContentDelivery"search:require="solr"><contentSearch><indexConfigurations><defaultSolrIndexConfiguration><documentOptions><fieldshint="raw:AddComputedIndexField"><fieldfieldName="pagecontent"indexType="tokenized"storageType="no"returnType="text">Feature.Search.Solr.PageContentComputed, Feature</field></fields></documentOptions></defaultSolrIndexConfiguration></indexConfigurations></contentSearch></sitecore></configuration>