Mix and match search providers
One of my favorite things about Umbraco Search is that it really simplifies the implementation of different search providers 🔍
If Examine for some reason does not meet your search requirements, Umbraco Search offers a standardized process for utilizing a different search technology:
- Implement your own
IIndexerandISearcherfor your chosen search tech. - Register your implementations as handlers for the core search indexes.
As a really neat side effect, Umbraco Search can run multiple search providers simultaneously (though only one per index). So you can really mix and match search providers 🤘
Running multiple search providers does take a little bit of tweaking… but it’s actually quite simple, once you know what’s going on 😅
A quick example
Let’s say I want Elasticsearch to power the public (published) content search, while retaining Examine as the search provider for all backoffice search - draft content, media and members.
I’ll start by installing my Elasticsearch search provider into my Umbraco Search demo site project (which I wrote about in a previous post):
dotnet add package Kjac.SearchProvider.Elasticsearch
Next, the demo site composer needs updating, according to the Elasticsearch search provider install docs:
public void Compose(IUmbracoBuilder builder)
{
builder
// add core services for search abstractions
.AddSearchCore()
// use the Elasticsearch search provider
.AddElasticsearchSearchProvider()
// use the Examine search provider
.AddExamineSearchProvider()
// force rebuild indexes after startup
.RebuildIndexesAfterStartup();
builder.Services.Configure<ClientOptions>(options =>
{
options.Host = new("http://localhost:9200");
options.Authentication = new()
{
ApiKey = "my-api-key"
};
options.EnableDebugMode = true;
});
...
As you can see, I have added search providers for both Examine and Elasticsearch, so at this point they’re running simultaneously 🚀
However, both of the Add...SearchProvider() extension methods assume that their respective implementation should handle all the core search indexes.
In other words: Given the order I’m calling the extension methods, Examine effectively still powers all search. So this needs to be reconfigured:
// re-register the published content index to be powered by Elasticsearch
builder
.Services
.Configure<IndexOptions>(options => options
.RegisterIndex<IElasticsearchIndexer, IElasticsearchSearcher, IPublishedContentChangeStrategy>(
Constants.IndexAliases.PublishedContent,
UmbracoObjectTypes.Document
)
);
And that’s all there is to it! Now Elasticsearch powers the published content search, while Examine handles the rest.
Okay, agreed… that last bit deserves a bit of explanation 👀
I am essentially re-registering the core search index for published content, thus overwriting any existing registrations for that index.
In this re-registration I’m instructing Umbraco Search to use the Elasticsearch implementations of IIndexer and ISearcher to handle the published content index. My Elasticsearch search provider conveniently adds explicit marker interfaces to these implementations, making it easier to reference them.
I’m also instructing Umbraco Search to use a concrete change strategy for handling content changes (the IPublishedContentChangeStrategy), and to only add documents in this index.
Is this even useful?
…or is it just an academic exercise in complexity? You be the judge. Personally, I think it is a super useful feature 🔥
For one, requirements might differ a lot between frontend and backend search.
Also, if you’re using a search technology that’s priced on a consumption basis, you can lower the total cost by only using it for frontend search 💰
Lastly, this whole thing is the foundation for creating custom indexes in Umbraco Search, and that is certainly going to be quite useful.
Happy mixing 💜
The full SiteComposer
For reference - here’s the full implementation of SiteComposer with all the additions mentioned in this post:
using Kjac.SearchProvider.Elasticsearch.Configuration;
using Kjac.SearchProvider.Elasticsearch.DependencyInjection;
using Kjac.SearchProvider.Elasticsearch.Services;
using Umbraco.Cms.Core.Composing;
using Umbraco.Cms.Core.Models;
using Umbraco.Cms.Search.Core;
using Umbraco.Cms.Search.Core.Configuration;
using Umbraco.Cms.Search.Core.DependencyInjection;
using Umbraco.Cms.Search.Core.Services.ContentIndexing;
using Umbraco.Cms.Search.Provider.Examine.DependencyInjection;
namespace Site.DependencyInjection;
public class SiteComposer : IComposer
{
public void Compose(IUmbracoBuilder builder)
{
builder
// add core services for search abstractions
.AddSearchCore()
// use the Elasticsearch search provider
.AddElasticsearchSearchProvider()
// use the Examine search provider
.AddExamineSearchProvider()
// force rebuild indexes after startup
.RebuildIndexesAfterStartup();
builder.Services.Configure<ClientOptions>(options =>
{
options.Host = new("http://localhost:9200");
options.Authentication = new()
{
ApiKey = "my-api-key"
};
options.EnableDebugMode = true;
});
// re-register the published content index to be powered by Elasticsearch
builder
.Services
.Configure<IndexOptions>(options => options
.RegisterIndex<IElasticsearchIndexer, IElasticsearchSearcher, IPublishedContentChangeStrategy>(
Constants.IndexAliases.PublishedContent,
UmbracoObjectTypes.Document
)
);
builder
// configure the Examine search provider for this site
.ConfigureExamineSearchProvider()
// configure System.Text.Json to allow serializing output models
.ConfigureJsonOptions();
}
}