Finding the Right Context

      Comments Off on Finding the Right Context

It is a fairly well-known best practice to create your own custom search indexes rather than using and polluting the ones that Sitecore provides out-of-the-box. If you follow this practice in a large solution, you may end up with many small indexes. A question may arise: “how do I get the correct search context (index) for a given feature?”

Most of the documentation and blogs I have seen use one of two methods. First is the hard-coded approach. This will look something like this:

This approach is not recommended for real-world use. It is fragile and tightly couples your code to your index configuration. Second is the pipeline-based approach:

This is a big improvement over the hard-coded approach in that it does not couple your code to your index configuration. However, I have found some cases where it doesn’t work very well.

Behind the scenes, this approach uses the contentSearch.getContextIndex pipeline. The problem with this pipeline is that the only parameter it has is an IIndexable. The default processor filters out indexes that don’t use the correct database or aren’t configured to index the items’ template. From those that are left, it tries to find the index with the closest root item. If there is a tie (more than one index with the same root item), it sorts them alphabetically and picks the first one. This seems like an overly-complicated and not very robust algorithm to me. Sure, you can create your own processors for the pipeline, but I prefer a different approach altogether.

When I create a new index, it is typically specific to a feature. It is based more on the type of data in the index, that is which fields it includes, rather than where the items live in the content tree. There is a natural correlation between the search result model class and the fields specified in the index configuration. While I may occasionally use more than one search result model with a given index, a given search result model is typically only used with a single master/web index pair. We can take advantage of this to create a clean and robust method of retrieving a search context.

The Solution

The basic idea is to use generics and a DI container to create a mapping between your search result model types and search context factory classes. The idea is similar to a stripped down CQRS query handler. Start with an interface for a search context factory:

Note that the generic parameter on this interface is not actually used here. It is only to make it easier for us to register classes with our DI container as we will see in a bit. Most of the time, our indexes come in pairs: one for the master database and one for web. We can create a base class for our factories that looks like this:

A concrete implementation is then reduced to simply specifying the index names like so:

Note that this concrete implementation also specifies a specific search result model type. You will only have a single factory class for a given search result model type. At this point you may be wondering how we get the correct factory. That is where the search context provider comes into play.

This interface looks oddly similar to ISearchContextFactory, but there is an important distinction. The generic parameter here is on the method rather than the class. This allows me to use a single instance of the provider to retrieve any number of search contexts. The implementation will ask the DI container to provide a ISearchContextFactory and delegate the creation of the search context to it.

We can wire this up with a Sitecore dependency injection configurator. The sample below uses Scrutor to handle the assembly scanning.

This configurator registers every implementation of the ISearchContextFactory creating a mapping between the search result model types and factory classes. In addition, the ServiceLocatorSearchContextProvider is registered as the implementation for ISearchContextProvider. This configurator would be registered via a config patch file like so:

When it is all said and done, you can use constructor injection to provide your ISearchContextProvider and then use it like this:

Using this method to get your search context is much more efficient and reliable than the pipeline approach and much more maintainable than the hard-coded approach.