Loading...
Area: Episerver Find
Applies to versions: 12 and higher
Other versions:

Unified search

Unified Search lets you index and query objects of existing classes, such as PageData objects, without having to map them to an intermediate type that is put into the Episerver Find index. And, Unified Search indexes objects such that developers can query them in several ways using a fluent API that requires minimal search engine skills. Unified Search is very powerful, since it lets you to use Find for free text search and deterministic querying, such as navigation and listings.

Unified Search provides the benefits of indexing objects using the “dumbed down,” least common denominator approach while maintaining Find’s original power of indexing more or less full .NET objects. This approach makes it easier to

  • search through different, unrelated types
  • build generic functionality for querying and displaying search results (that is, helper methods for displaying results)

Sample Unified Search query

var result = SearchClient.Instance
    .UnifiedSearchFor("some search term")
    .GetResult();

This sample searches for both PageData objects and files in VPPs (UnifiedFile). Using a few lines of code, you can include other types in the search. The result object contains hit objects with a title, excerpt (both of which can be highlighted), and other common search result properties.

Unified Search versus the Find query API

Unified Search is generally useful if you:

  • Build standard search pages that do not require filtering on type-specific properties.
  • Build generic functionality and do not know the content types for which it will be used.

The regular .NET Client API is better for:

  • Most querying scenarios. That is, content retrieval/navigations/listings that do not involve free text search.
  • Doing fine-grained and type-specific filtering.

Components

Unified Search has four parts:

  • A common interface, ISearchContent, for declaring properties to use when building search (not querying) functionality.
  • An object that maintains a configurable list of types to be searched when searching for ISearchContent. Also, optionally, rules for filtering and projecting those types when searching IUnifiedSearchRegistry, which is exposed by the IClient.Conventions.UnfiedSearchRegistry property.
  • Classes for results returned when searching for ISearchContentUnifiedSearchResults, which contain a number of UnifiedSearchHit.
  • Special methods for building and executing search queries: UnifiedSearch(), UnifiedSearchFor(), and an overloaded GetResult() method.

ISearchContent

The ISearchContent interface resides in the EPiServer.Find.UnifiedSearch namespace and is the least common denominator. The interface declares several properties for which you may search, all of whose names begin with Search. Classes registered in Unified Search do not have to implement ISearchContent. By having a property or extension method with the same name and type as a property in the interface, the property is treated as if it were received from the interface.

  • SearchTitle
  • SearchSummary
  • SearchText
  • SearchHitUrl
  • SearchTypeName
  • SearchHitTypeName
  • SearchSection
  • SearchSubsection
  • SearchAuthors
  • SearchGeoLocation
  • SearchPublishDate
  • SearchUpdateDate
  • SearchAttachment
  • SearchCategories
  • SearchFilename
  • SearchFileExtension
  • SearchMetaData
public class MySearchableClass
{
    public string Heading { get; set; }
    public string Body { get; set; }
    public string Url { get; set; }

    public virtual string SearchTitle { get { return Heading; } }
    public virtual string SearchSummary { get { return Body; } }
    public virtual string SearchText { get { return String.Format(CultureInfo.InvariantCulture, "{0} {1} {2}", Heading, Body, Url); } }
    public virtual string SearchHitUrl { get { return Url; } }
    public virtual string SearchSection { get { return GetType().Name; } }
}

IUnifiedSearchRegistry

IUnifiedSearchRegistry, also residing in EPiServer.Find.UnifiedSearch, exposes methods for building and configuring types to be included when searching for ISearchContent. In addition to methods for adding (the Add method) and listing types (the List method), IUnifiedSearchRegistry declares methods that let you add rules to filter specific types when searching and projecting found documents to the common hit type (UnifiedSearchHit).

SearchClient.Instance.Conventions.UnifiedSearchRegistry<br/>
.Add<MySearchableClass>();

UnifiedSearchResults and UnifiedSearchHit

While ISearchContent provides a decent common denominator for fields in which to search, it is not useful for getting back instances of it as the result of a search query. For instance, while you may want to search an indexed object's full text, you typically only want back a text snippet to display in search results. Also, it is technically problematic to get instances of ISearchContent back from the search engine, since the matched object does not actually implement that interface, or at least it does not have to.

Therefore, when searching for ISearchContent and invoking the GetResult method, you do not get back instances of ISearchContent. Instead, you get an instance of the UnifiedSearchResults class, which contains several UnifiedSearchHit objects. A UnifiedSearchHit object contains properties that you would typically want to display for each search result, such as Title, URL and excerpt (text snippet). The UnifiedSearchHit object has additional properties, such as PublishDate, ImageUri, Section, FileExtension and TypeName.

Methods for building and executing queries

To search for ISearchContent, use the regular Search method, client.Search(). When doing so, you determine which fields to search when building free text search. Since ISearchContent is a special type that the .NET API knows about, two methods enable you to add sensible defaults: UnifiedSearch() and UnifiedSearchFor().

Unified Search also provides a GetResult method. Because it has the same name as the regular method for executing search queries, you do not have to do anything special to use the GetResult method. Because it has a more specific generic-type constraint than other GetResult methods, the compiler uses it.

Note: The GetResult method modifies search queries by searching for objects that implement ISearchContent and for all types added to the UnifiedSearchRegistry. The GetResult method also adds a projection from ISearchContent to UnifiedSearchHit with sensible defaults, along with any type-specific projections added to the UnifiedSearchRegistry. Finally, before executing a search query (such as the GetResult method), Unified Search adds type-specific filters that were added to the UnifiedSearchRegistry.

When you invoke GetResult, the search query searches types that may not have implemented ISearchContent. But since you (hopefully) specified to only search in, or filter on, a number of fields declared by ISearchContent, the search only considers fields with those names, even if the objects do not implement ISearchContent. The GetResult method has an overload that requires an argument of type HitSpecification. This argument lets you control the length of the excerpt, whether to highlight titles and excerpts, and so forth.

SearchClient.Instance.UnifiedSearchFor(searchQuery).GetResult()

How it works

Unified Search uses two key concepts:

  • the type hierarchy is indexed for objects
  • the search engine does not care what type declares a given field (property) as long as it has the expected name and type

For example, you add two classes to the registry (A and B) and use the code below.

SearchClient.Instance
    .UnifiedSearch()
    .Filter(x => x.SearchTitle.Prefix("A"))
    .GetResult();

This code searches the index for objects that implement ISearchContent or are of types A or B. The code filters objects that have a field named SearchTitle with a value that starts with “A”.

Objects that implement ISearchContent have such a field, but A and B may not. If they do not, the filter does not match, and instances of A and B are not returned.

However, if instances of A and B have such a field, the search engine does not care why they have it. That is, the engine does not care about the fact that A and B do not have ISearchContent.SearchTitle. The type filtering is separate, so as long as A and B have a string field named SearchTitle, the search engine can filter on it.

Of course, to include A or B objects in the query result, we must add a SearchTitle field to them. But we do not have to the implement the full ISearchContent interface. Also, since the .NET API and the search engine do not distinguish between properties and methods, we can create an extension method for A or B and configure the client’s convention to include it when indexing instances of those types. In this way, we add the SearchTitle field without modifying the classes.

How to use it

Using Unified Search to search for CMS content, both pages and uploaded files, is easy.

  1. Create a search query, using either the UnifiedSearch or UnifiedSearchFor method invoked on the SearchClient.
  2. Execute the query using GetResult.
  3. Do what you want with the result. Typically, you iterate over each hit in a view.
using EPiServer.Find;
using EPiServer.Find.Framework;
using EPiServer.Find.Cms;
using EPiServer.Find.UnifiedSearch;

var result = SearchClient.Instance
    .UnifiedSearchFor(Query)
    .GetResult();

foreach (UnifiedSearchHit hit in result)
{
    Response.Write(hit.Url);
    Response.Write(hit.Title);
    Response.Write(hit.Excerpt);
}

If you want to customize what is indexed and/or returned for content, either add a property with the same type and name as one of the properties in ISearchContent or create and include an extension method matching such a property. For example, the CMS integration includes a default SearchSection method to PageData objects. If you don’t like that, or want to modify it, add a property named SearchSection to the page type class.

public abstract class SitePageData : PageData
{
  [Ignore]
  public virtual string SearchSection
  {
    get
    {
      var section = this.SearchSection();
      if (!string.IsNullOrWhiteSpace(section))
      {
        return section;
      }

      if (ParentLink.CompareToIgnoreWorkID(
            ContentReference.StartPage))
      {
        return PageName;
      }

      return null;
    }

    //Other properties
}

The same goes for SearchText. The CMS integration includes a default method that you can override by adding your own property.

public class NewsPage : StandardPage
{
  [Ignore]
  public string SearchText
  {
    get
    {
      return MainBody.ToHtmlString(
        PrincipalInfo.AnonymousPrincipal);
    }
  }
  
  //Other properties
}

Type conditional filtering

You can search for objects that do not implement ISearchContent as if they did. If the objects have properties or included methods with matching names as properties declared in ISearchContent, they are also returned if you filter by them. Remember that the full object is still stored in the object. This, and the fact you can filter by type using Find, means you can apply additional criteria to objects of some types using type conditional filtering.

For example, you want to search for everything included as ISearchContent (typically PageData and UnifiedFile) and apply a filter. For PageData objects, you want to add additional criteria. You can then do things like this:

SearchClient.Instance
  .UnifiedSearch()
  .Filter(x => 
    x.SearchTitle.Prefix("A")
    & (!x.MatchTypeHierarchy(typeof(PageData))
       | ((PageData)x).CreatedBy.Match("Joel"))
    ).GetResult();

In the above code, you first add a filter that requires objects to have a SearchTitle field with a value starting with “A”. You then require the objects to either NOT be of type PageData or if they are, be created by someone named Joel.

Last updated: Mar 27, 2019

Feedback?