Kentico π Azure Search
Kentico has included custom search indexing for a long time and has traditionally provided this functionality through Lucene.NET.
The locally (in-process) hosted search indexing is simple to enable and scales well with the CMS but there are some caveats. π―
Note: These caveats might not apply to you. If they don't, Kentico's built-in local Smart Search is a great solution.
- β Scaling search indexing and querying due to high traffic requires scaling the entire CMS.
- β More complex search functionality like faceted navigation is not supported.
- β Advanced functionality like Cognitive Search and auto-complete are not realistic possibilities.
Fortunately, Kentico also has a deep integration into Azure Search out of the box. π
It enables simple use cases without any custom code, in much the same way as local Smart Search.
More complex use cases can be solved through hooks into the search index creation process.
Since Azure Search exists as an enterprise level search solution, developers can leverage its full set of querying options directly, using the Microsoft.Azure.Search NuGet package or the OData querying API directly. π
Faceting
Faceting is an awesome feature that can help your site's users find what they are looking for or explore your content in a much more intuitive way.
To quote the Azure Search docs:
Facets can help you find what you're looking for, while ensuring that you donβt get zero results. [...] In online retail applications, faceted navigation is often built over brands, departments (kidβs shoes), size, price, popularity, and ratings.
If we look at HomeDepot.com we can see faceting in action:
The counts of the results on the page that fall into each of the taxonomies available are seen in this list. π€
In Kentico, an intuitive way of grouping content into a set of taxonomies like this is by using Categories.
Kentico includes both the CategoryInfo.CategoryDisplayName
and CategoryInfo.CategoryID
values, for all categories for a document, in the search index as documentcategories
and documentcategoryids
fields respectively.
However, by default, these fields are not facetable. π
What we want is for the search index that Kentico generates in our Azure Search instance to make one or the other facetable. In the example below I'm going to work with the documentcategories
field.
Here is what we should end up with when we look at our index's fields in the Azure Portal:
Custom Azure Search Module
The customization on the Kentico side is actually pretty simple - we just need to create a custom Module
and tap into the Azure Search index creation events that Kentico exposes. π§
public class CustomAzureSearchModule : Module
{
public const string AZURE_SEARCH_INDEX = "put-your-index-code-name-here";
public CustomAzureSearchModule() : base(nameof(CustomAzureSearchModule)) { }
protected override void OnInit()
{
base.OnInit();
DocumentFieldCreator.Instance.CreatingField.After += CreatingField_After;
}
private void CreatingField_After(object sender, CreateFieldEventArgs e)
{
// If you have multiple indexes and only want category faceting on one
if (!e.SearchIndex.IndexCodeName.Equals(AZURE_SEARCH_INDEX, StringComparison.OrdinalIgnoreCase))
{
return;
}
var field = e.Field;
if (field.Name.Equals("DocumentCategories", StringComparison.OrdinalIgnoreCase))
{
field.IsFacetable = true;
}
}
}
So, what are we doing here?
First, we create a custom Module
that Kentico grabs, through reflection, and executes during the application startup.
We create an event handler for DocumentFieldCreator.Instance.CreatingField.After
so that we can customize the search index configuration for a field after Kentico has setup the defaults for that field.
Finally, we match on a specific field that is being added to the search index - DocumentCategories
- and we set that field's IsFacetable
property to true
.
After adding this customization to your site, make sure you re-build the Azure Search Index in the CMS "Smart Search" module.
Easy, peasy. π
Example: Document Category Faceting
The DocumentCategories
field is now facetable, which means when we query the Azure Search API for search results, it will return the facet counts per category for the specific results returned.
Assume we have (3) documents which are categorized as "Cool", "Fun", and "Potato" (maybe we should have a word with the content editors π€£).
- "Cool", "Fun", "Potato"
- "Fun"
- "Fun", "Potato"
A default search (no filtering applied) should return all documents and the following facet counts:
- "Cool" (1)
- "Fun" (3)
- "Potato" (2)
If you want to test out your search index configuration, go to the Azure Portal blade for your Azure Search instance, select the correct index and type the following into the "query string" field of the "Search Explorer" tab:
facet=documentcategories&$filter=documentcategories/any(dc: dc eq 'Potato')
This should return (2) results, documents 1 and 3. π
Querying From Kentico 12 MVC
To get this faceting integrated into your Kentico 12 MVC site you'll need to do a bit more coding. There's a pretty thorough example in the Kentico documentation with tons of sample code. π
However, this example doesn't include faceting with a collection.
All fields in Azure Search indexes have specific data types. The
documentcategories
field has a type ofCollection(Edm.String)
which is different than the fields queried in the Kentico docs example.
To query against a facetable field that is a collection of values you'll need to have a slightly more advanced query string passed to your Microsoft.Azure.Search.Models.SearchParameters
instance.
Hint: It's the one above used for testing in the Azure "Search Explorer". π
Here's an example of how to generate this query string:
public class FacetFilterViewModel
{
public string Name { get; set; }
public string Value { get; set; }
public bool IsSelected { get; set; }
public FacetFilterViewModel()
{
Name = "";
Value = "";
IsSelected = false;
}
}
// In a separate class
public const string FACET_CATEGORIES = "documentcategories";
public SearchParameters BuildSearchParameters(IEnumerable<FacetFilterViewModel> facetFilters)
{
var categoryFilters = facetFilters
.Where(x => x.IsSelected)
.Select(item => $"{FACET_CATEGORIES}/any(dc: dc eq '{item.Value.Replace("'", "''")}')");
string categoryFilterTerm = string.Join(" or ", categoryFilters);
return new SearchParameters
{
Filter = categoryFilterTerm,
Facets = new List<string>
{
FACET_CATEGORIES
}
};
}
These SearchParameters
can be passed to your call to the ISearchIndexClient
(found in the Kentico docs code example) as follows:
// These come from the form submission, which is defined in a Razor View
// IEnumerable<FacetFilterViewModel> facetFilters
// string searchTerm
var searchParameters = BuildSearchParameters(facetFilters);
var result = await client.Documents.SearchAsync(searchTerm, searchParameters);
// See the Kentico docs example code on how to use "result"
This should give you faceted Category results that can be rendered out in a Razor View much like the screenshot from Home Depot above (the design is up to you!). π€
Summary
Looking back, we can see we now know the following:
- Kentico supports both local search indexes and Azure Search indexes.
- Azure Search provides more advanced scaling and features.
- Integrating with Azure Search in Kentico is pretty simple.
- Faceting is a great feature for helping site visitors search your content.
- Document Categories are not facetable by default, but this can be customized.
- Customizing Azure Search in Kentico is done through indexing events.
- To render faceted search results for a collection field you need to generate a more complex query string.
This post was meant to serve as a helpful walkthrough to integrating Kentico with Azure Search - specifically the faceting feature when applied to Document Categories.
Thanks for reading! π
I hope you enjoyed this Quick Tip. I plan on posting more tips in this series.
If you are looking for additional Kentico content, checkout the Kentico tag here on DEV:
or my Kentico 12: Design Patterns series.
Top comments (0)