Using the Solr spell checker

Last updated Monday, December 18, 2017 in Sitecore Experience Platform for
Keywords: Development, Search

Note

This topic is valid for Sitecore 9.0 and later.

Solr has a spell checker feature. You use this feature to give users inline query suggestions based on other, similar terms. The source of the suggestions can be terms in a field in Solr, an external text file, or fields in other indexes.

You can configure the source in four ways:

  • IndexBasedSpellChecker: use the Solr index as the source for a parallel index used for spell checking. You must define a field as the basis for the index terms.
  • DirectSolrSpellChecker: use terms from the Solr index without building a parallel index.
  • FileBasedSpellChecker: use an external file as a spelling dictionary.
  • WordBreakSolrSpellChecker: combine adjacent query terms and/or breaking terms into multiple words.

We recommend these best practices:

  • If you use IndexBasedSpellCheck, copy terms from some fields (such as title, body, and so on) to another field that you create for spell checking purposes.
  • If you use DirectSolrSpellCheck, when choosing a field to query for this spell checker, use one that has relatively little analysis performed on it, in particular stemming.

The Solr documentation provides more details.

Solr configuration

You can configure Solr in two ways:

  • Use an existing select request handler if all requests should generate a suggestion
  • Use a spell request handler if a dedicated request handler is required for serving the spellchecking suggestion

You configure Solr by editing the SolrConfig.xml file.

Use an existing request handler

This enables a spell check for the _name field of the master index:

  1. Open the solrconfig.xml file, for example C:\solr-6.3.0\server\solr\configsets\sitecore_core_index\conf\solrconfig.xml.
  2. Find the <arr name="components"> node and enable it. It is disabled by default.
  3. Add <str>spellcheck</str> to the <arr name="components"> node.
  4. Go to the <searchComponent name="spellcheck" class="solr.SpellCheckComponent"> node.
  5. Under the <lst name="spellchecker"> node, change <str name="field">_text_</str> to <str name="field">_name</str> to enable spellchecking for the _name field.

To check that it works:

  1. Open Solr Admin in a browser, select the core index, or go directly with this link: http://localhost:8983/solr/#/sitecore_core_index/query
  2. Select the spellcheck checkbox to enable spell check, and enter a spell check query in the spellcheck.q text field (homt)
  3. Click Execute Query.

Solr Admin looks similar to this if spell check works on the Solr server:

Picture 6

If you use multiple Solr cores, you must repeat this procedure for each of these cores.

Using a spell request handler

This enables spell checker for the _name field of the master index:

  1. Open the solrconfig.xml file, for example C:\solr-6.3.0\server\solr\configsets\sitecore_core_index\conf\solrconfig.xml.
  2. Locate the <searchComponent name="spellcheck" class="solr.SpellCheckComponent"> node.
  3. Under the <lst name="spellchecker"> node, change <str name="field">_text_</str> to <str name="field">_name</str>.
  4. Save the SolrConfig.xml file and restart Solr.

To check that it works:

  1. Go to http://localhost:8983/solr/sitecore_master_index/spell?q=_name:homy&spellcheck=true&spellcheck.count=10&rows=100000000&version=2.2&spellcheck.build=true&wt=json&indent=true in the browser.

If it works, the response looks like this:

Picture 9

Optional configuration

You can configure many other aspects of the Solr spell checker feature. You configure these parameters in the request handler section in solrconfig.xml file by adding nodes the <lst name="defaults"> node

For example, this snippet adds spellcheck.count with a value of 20 as the default count:

<requestHandler name="/select" class="solr.SearchHandler">
    <!-- default values for query parameters can be specified, these
         will be overridden by parameters in the request
      -->
    <lst name="defaults">
      <str name="echoParams">explicit</str>
      <int name="rows">10</int>
      <str name="spellcheck">true</str>
     <str name="spellcheck.count">20</str>

https://cwiki.apache.org/confluence/display/solr/Spell+Checking has more detail.

Sample code

This sample code shows how you can create a list of suggestion when a search term does not result in any results.

For example, if a user searches for the term homy and there are no results, then you can return a list of suggestions that are close to homy, including home.

Sitecore exposes the two APIs that you can use.

  • Use this if your code uses an implementation of ISolrQuery:

    SolrNetProxy.Query

    public static SolrQueryResults<T> Query<T>(this IProviderSearchContext context, ISolrQuery q, QueryOptions queryOptions)

    public static SolrQueryResults<T> Query<T>(this IProviderSearchContext context, string q, QueryOptions queryOptions)

  • Use this if your code does not use ISolrQuery. This is an easier implementation:

    SolrNetProxy.GetSpellCheck

    public static SolrQueryResults<Dictionary<string, object>> GetSpellCheck(this IProviderSearchContext context, ISolrQuery q, SpellCheckHandlerQueryOptions options)

In order to use the APIs, import Sitecore.ContentSearch.SolrNetExtension in your class.

You can use all spell checker parameters exposed by Solr this way:

  • Through QueryOptions.SpellCheck when consuming SolrNetProxy.Query
  • Through SpellCheckHandlerQueryOptions.SpellCheck when consuming SolrNetProxy.GetSpellCheck

The following examples show two different ways of using Solr spell checker from Sitecore.

Using SolrNetProxy.Query

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="HighlightDemo.aspx.cs" Inherits="Sitecore.ContentSearch.SolrProvider.Example.HighlightDemo" %>
<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="HighlightDemo.aspx.cs" Inherits="Sitecore.ContentSearch.SolrProvider.Example.HighlightDemo" %>
<%@ Import Namespace="Sitecore.ContentSearch.SearchTypes" %>
<%@ Import Namespace="System" %>
<%@ Import Namespace="System.Collections.Generic" %>
<%@ Import Namespace="Sitecore.ContentSearch" %>
<%@ Import Namespace="Sitecore.ContentSearch.SolrNetExtension" %>
<%@ Import Namespace="Sitecore.ContentSearch.SolrProvider.SolrNetIntegration" %>
<%@ Import Namespace="SolrNet.Commands.Parameters" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
     <script runat="server">
    protected void OnClick(object sender, EventArgs e)
        {
            FirstSenario();
        }
    private void FirstSenario()
        {
            var index = ContentSearchManager.GetIndex("sitecore_master_index");
            var searchText = this.txtInput.Text;
            if (string.IsNullOrEmpty(searchText))
            {
                //TODO prompt error
                this.ShowResult("Please enter a search text!");
                return;
            }
            using (var context = index.CreateSearchContext())
            {
                var results = context.Query<SearchResultItem>(string.Format("_name:{0}", searchText), new QueryOptions()
                {
                    SpellCheck = new SpellCheckingParameters()
                });
                if (results == null || results.SpellChecking == null || results.SpellChecking.Count < 1)
                {
                    this.ShowResult("No rows returned");
                    this.rptResults.DataSource = new List<string>();
                    this.rptResults.DataBind();
                    return;
                }
                var suggestions = new List<string>();
                foreach (var term in results.SpellChecking)
                {
                    foreach (var suggestion in term.Suggestions)
                    {
                        suggestions.Add(suggestion);
                    }
                }
                this.BindSuggestion(suggestions);
            }
        }
      private void ShowResult(string result)
        {
            this.divResult.InnerHtml = result;
        }
        private void BindSuggestion(List<string> suggestions)
        {
            this.rptResults.DataSource = suggestions;
            this.rptResults.DataBind();
        }
    </script>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <h3>Spell Check Example </h3>
        <h4>Group search result by title</h4>
        <br/>
        <asp:Label runat="server" Text="Name" AssociatedControlID="txtInput"></asp:Label> &nbsp; &nbsp; 
            <asp:TextBox runat="server" ID="txtInput"></asp:TextBox> &nbsp; &nbsp; 
            <asp:Button runat="server" Text="Search" OnClick="OnClick" />
        <br/>
        <div><b>Search Results</b></div>
        <br/>
        <div id="divResult" runat="server"></div>
        <asp:Repeater id="rptResults" runat="server">
             <HeaderTemplate>
              <table cols="1" width="100%" style="padding: 5px">
          </HeaderTemplate>
            <ItemTemplate>
                <tr>
                    <td><%# Container.DataItem %></td>
                </tr>
            </ItemTemplate>
            <FooterTemplate>
                </table>
            </FooterTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
</html>

Using SolrNetProxy.GetSpellCheck

<%@ Page Language="C#" AutoEventWireup="true" CodeBehind="SpellCheckDemo.aspx.cs" Inherits="Sitecore.ContentSearch.SolrProvider.Example.SpellCheckDemo" %>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
    <title></title>
       <style>
        .resultBox {
            padding: 5px 5px 5px 5px;
            margin-bottom: 1px;
            
        }
        #divGroupFieldLists {
            border-style: dashed;
        }
    </style>
</head>
<body>
    <form id="form1" runat="server">
    <div>
    <h3>Spell Check Example </h3>
        <h4>Group search result by title</h4>
        <br/>
        <asp:Label runat="server" Text="Name" AssociatedControlID="txtInput"></asp:Label> &nbsp; &nbsp; 
            <asp:TextBox runat="server" ID="txtInput"></asp:TextBox> &nbsp; &nbsp; 
            <asp:Button runat="server" Text="Search" OnClick="OnClick" />
        <br/>
        <div><b>Search Results</b></div>
        <br/>
        <div id="divResult" runat="server"></div>
        <asp:Repeater id="rptResults" runat="server">
             <HeaderTemplate>
              <table cols="1" width="100%" style="padding: 5px">
          </HeaderTemplate>
            <ItemTemplate>
                <tr>
                    <td><%# Container.DataItem %></td>
                </tr>
            </ItemTemplate>
            <FooterTemplate>
                </table>
            </FooterTemplate>
        </asp:Repeater>
    </div>
    </form>
</body>
</html>
SpellCheckDemo.aspx.cs
namespace Sitecore.ContentSearch.SolrProvider.Example
{
    using System;
    using System.Collections.Generic;
    using Sitecore.ContentSearch.SolrNetExtension;
    using Sitecore.ContentSearch.SolrProvider.SolrNetIntegration;
    using SolrNet.Commands.Parameters;
    using SolrNet;
    public partial class SpellCheckDemo : System.Web.UI.Page
    {
        protected void Page_Load(object sender, EventArgs e)
        {
        }
        protected void OnClick(object sender, EventArgs e)
        {
            FirstSenario();
        }
        private void FirstSenario()
        {
            var index = ContentSearchManager.GetIndex("sitecore_master_index");
            var searchText = this.txtInput.Text;
            if (string.IsNullOrEmpty(searchText))
            {
                //TODO prompt error
                this.ShowResult("Please enter a search text!");
                return;
            }
            using (var context = index.CreateSearchContext())
            {
                var results = context.GetSpellCheck(new SolrQuery(string.Format("_name:{0}", searchText)), new SpellCheckHandlerQueryOptions()
                {
                    SpellCheck = new SpellCheckingParameters()
                    {
                        Count = 10,
                        Build = true
                    }
                });
                if (results == null || results.SpellChecking == null || results.SpellChecking.Count < 1)
                {
                    this.ShowResult("No rows returned");
                    this.rptResults.DataSource = new List<string>();
                    this.rptResults.DataBind();
                    return;
                }
                var suggestions = new List<string>();
                foreach (var term in results.SpellChecking)
                {
                    foreach (var suggestion in term.Suggestions)
                    {
                        suggestions.Add(suggestion);
                    }
                }
                this.BindSuggestion(suggestions);
            }
        }
        #region Helper Method
        private void ShowResult(string result)
        {
            this.divResult.InnerHtml = result;
        }
        private void BindSuggestion(List<string> suggestions)
        {
            this.rptResults.DataSource = suggestions;
            this.rptResults.DataBind();
        }
        #endregion
    }
}

Send feedback about the documentation to docsite@sitecore.net.