HomeDev GuideAPI Reference
Dev GuideAPI ReferenceUser GuideGitHubNuGetDev CommunitySubmit a ticketLog In
GitHubNuGetDev CommunitySubmit a ticket

Unified Search

Describes the Unified Search functionality for Optimizely Search & Navigation.

Unified Search is an integration with Optimizely Content Management System (CMS) that lets you index and query objects of existing classes without having to map them to an intermediate type in the index.

Using Unified Search, you can index and query objects of existing classes, such as PageData objects. Unified Search indexes objects so developers can query them in several ways using a fluent API requiring minimal search engine skills. Unified Search is powerfully used with 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 Search & Navigation'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, for example, helper methods for displaying results.

Example of Unified Search query

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

This sample searches for 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 can be highlighted), and other common search result properties.

Unified Search and the 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, or listings that do not involve free text search.
  • Doing fine-grained and type-specific filtering.

Key concepts

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, you must add a SearchTitle field to them. But you do not have to implement the full ISearchContent interface. Also, because the .NET API and the search engine do not distinguish between properties and methods, you 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, you add the SearchTitle field without modifying the classes.

Components

  • 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 ISearchContent: UnifiedSearchResults, which contains severalUnifiedSearchHit.
  • 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 you may search, whose names begin with Search. Classes registered in Unified Search do not have to implement ISearchContent. Having a property or extension method with the same name and type as a property in the interface treats the property 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
.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 a text snippet to display in search results. Also, it is technically problematic to get instances of ISearchContent back from the search engine because the matched object does not implement that interface, or at least it does not have to.

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 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 a free text search. Because ISearchContent is a special type that the .NET API knows about, two methods let you 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 because you (hopefully) specified to only search in, or filter on, several 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()

Use Unified Search

This example shows how to search for Optimizely Content Management System (CMS) content, like pages and uploaded files.

📘

Note

By default, Unified Search does not return a container page or content folder. To change this behavior, create your own implementation of IUnifiedSearchSetUp interface.

  1. Create a search query using 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);
  }

To customize what is indexed and returned for content, 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 you can override by adding your 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 Optimizely Search & Navigation, 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 NOT be of type PageData or if they are, be created by someone named "Joel."