Base classes

Instead of implementing the ISearchOptions interface from scratch each time, it's recommended to use a base class so you'd have a bit of logic to start with. For instance this package provides the SearchOptionsBase that you could extend, and then override the logic that you'd need to adjust.

SearchOptionsBase

See the SearchOptionsBase class on GitHub.

Custom base class

When working on client solution, we'd usually create a custom base class for that particular solution. For instance:

using System;
using System.Text.RegularExpressions;
using Skybrud.Umbraco.Search;
using Skybrud.Umbraco.Search.Options;
using Skybrud.Umbraco.Search.Options.Fields;

namespace Umbracotests.Repositories.Search {
    
    public class MySearchOptionsBase : ISearchOptions {

        /// <summary>
        /// Gets or sets the context (ID of ancestor) under which to search.
        /// </summary>
        public int ContextId { get; set; }

        /// <summary>
        /// Gets or sets the text to search for.
        /// </summary>
        public string Text { get; set; }

        /// <summary>
        /// Gets or sets whether the check on the <c>hideFromSearch</c> field should be disabled.
        /// </summary>
        public bool DisableHideFromSearch { get; set; }

        public virtual string GetRawQuery(ISearchHelper searchHelper) {
            return GetQueryList(searchHelper).GetRawQuery();
        }

        protected virtual QueryList GetQueryList(ISearchHelper searchHelper) {

            QueryList query = new QueryList();

            SearchType(searchHelper, query);
            SearchText(searchHelper, query);
            SearchPath(searchHelper, query);
            SearchHideFromSearch(searchHelper, query);

            return query;

        }

        protected virtual FieldList GetTextFields(ISearchHelper helper) {
            return new FieldList {
                new Field("nodeName", 50),
                new Field("title", 40),
                new Field("teaser", 20)
            };
        }

        protected virtual void SearchType(ISearchHelper searchHelper, QueryList query) { }

        protected virtual void SearchText(ISearchHelper searchHelper, QueryList query) {

            if (string.IsNullOrWhiteSpace(Text)) return;

            string text = Regex.Replace(Text, @"[^\wæøåÆØÅ\-@\. ]", string.Empty).ToLowerInvariant().Trim();

            string[] terms = text.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
            if (terms.Length == 0) return;

            // Fallback if no fields are added
            FieldList fields = GetTextFields(searchHelper);
            if (fields == null || fields.Count == 0) fields = FieldList.GetFromStringArray(new[] { "nodeName", "title", "teaser" });

            query.Add(fields.GetQuery(terms));

        }

        protected virtual void SearchPath(ISearchHelper searchHelper, QueryList query) {
            if (ContextId > 0) query.AppendAncestor(ContextId);
        }

        protected virtual void SearchHideFromSearch(ISearchHelper searchHelper, QueryList query) {
            if (DisableHideFromSearch) return;
            query.Add("hideFromSearch:0");
        }

    }

}

The only thing coming from the ISearchOptions interface is the GetRawQuery method. The other methods are extension points, each with a default implementation that you may or may not choose to overwrite in each class extending your custom base class.

GetQueryList

The GetQueryList splits the construction of the query into different methods, which makes it easier to override the different parts. You may even choose to override this method in a sub class - eg. if you have a need beyond the methods of the base class:

protected override QueryList GetQueryList(ISearchHelper helper) {

    QueryList query = base.GetQueryList(helper);

    query.Add("tags:umbraco");

    return query;

}

GetTextFields

This method describes the default fields that should be used for the free text search. Which fields to search may be determined by other properties, which is why this has been delegated to it's own method.

For instance for a grid based page, you could override it to include the grid content as well:

protected override FieldList GetTextFields(ISearchHelper helper) {
    return new FieldList {
        new Field("nodeName", 50),
        new Field("title", 40),
        new Field("teaser", 20),
        new Field("grid", 5)
    };
}

or call the base method to inherit the default fields:

protected override FieldList GetTextFields(ISearchHelper helper) {
    
    var fields = base.GetTextFields(helper);

    fields.Add("grid", 5);

    return fields;

}

The second parameter is the boost for the particular field. A third parameter also let's you specify a fuzzy level.

SearchType

The base class in this example doesn't restrict the search to any specific types by default. But a news search, you could override the SearchType method do something like:

protected override void SearchType(ISearchHelper searchHelper, QueryList query) {
    
    query.AppendNodeTypeAlias("newsPage");

}

SearchText

When first implemented in the base class, you may not need to override the SearchText, as the fields it's searching is controlled by the GetTextFields method. But should you have the need, you can override this one as well.

SearchPath

If specified via the ContextId property, this method restricts the search to items under a given ancestor.

SearchHideFromSearch

We typically add a hideFromSearch property to pages in Umbraco, so the editors can exclude individual pages from the search if they need to. This method makes sure that this criteria is met, but also let's you disable this behavior via the DisableHideFromSearch.