Customizing Azure Search fields
Kentico 11 introduces native Azure Search integration with a variety of customization options. In this article, we’ll learn how to change the data type of our Azure search fields.
Azure Search integration is a powerful way to extend the search capability of your Kentico site, as well as incorporate your indexed content into other applications. Out-of-the-box, your Kentico fields will be converted into basic Microsoft data types like Edm.String, but your applications might expect a more specific data type.
In this example, we will be indexing the DancingGoat.Cafe pages on the Dancing Goat sample site. To follow along with this guide, you can install the sample site using the web template provided by your Kentico installation: https://docs.kentico.com/k11/configuring-kentico/managing-sites/installing-new-sites/creating-new-sites-from-templates.
1. Creating the Azure Search service
To create Azure Search indexes, you first need to have an Azure Search service running with your Azure subscription. You can find instruction on creating the service in Microsoft’s documentation: https://docs.microsoft.com/en-us/azure/search/search-create-service-portal.
2. Adding a custom field in Kentico
Once you have the Dancing Goat sample site installed, you may begin our customization.
By default, the DancingGoat.Cafe page type contains fields such as CafeStreet and CafeCity, but what if you want to provide exact coordinates for use on a map or in a location search? Let’s add a new field to do this to the page type:
- While editing the DancingGoat.Cafe page type in the Page types module, click the Fields tab.
- Click the New field button and fill in the following properties:
- Field name: CafeGeolocation
- Data type: Text
- Field caption: Lat/Lng
- Field description: Enter the cafe's coordinates, separated by a comma.
- Click Save.
- On the Search fields tab, make sure Search is enabled is checked and click save.
- Check the Searchable box under the Azure section of the CafeGeolocation field, then click Save.
Now, go to the Pages module and select the cafes from the content tree and enter coordinates in the Form tab for several of the pages. Here are some coordinates you can use for this example:
- Boston: 42.299744,-71.072383
- Allendale: 42.978361,-85.916874
- Los Angeles: 34.050518,-118.511553
- New York: 40.749345,-73.996675
- Ottawa: 45.415824,-75.689566
- Toronto: 43.664006,-79.390779
- Amsterdam: 52.359872,4.876509
- Birmingham: 52.480798,-1.900119
- Liverpool: 40.389553,-90.004861
- London: 26.304427,50.169413
- Madrid: 40.417313,-3.691632
- Brisbane: -27.469309,153.026164
- Melbourne: -37.814750,144.983066
- Sydney: -33.879792,151.207440
3. Setting up the index in Kentico
https://docs.kentico.com/k11/configuring-kentico/setting-up-search-on-your-website/using-azure-search/creating-azure-search-indexes
- Go to Smart search > Azure indexes and click the New index button.
- Fill in the following properties:
- Display name: Dancing Goat Cafes
- Index type: Pages
- Service name: The name entered while creating the Azure Search service in the Azure Portal. This is the first part of the service’s URL, e.g. https://<servicename>.search.windows.net.
- Admin key: Either the Primary key or Secondary key found within your Azure Search service’s Keys tab.
- Click Save.
- In the Indexed content tab, click Add allowed content and fill in the following properties:
- Path: /Cafes/%
- Page types: DancingGoat.Cafe
- Click Save.
4. Our progress so far
Don’t rebuild your index yet! If you did, you would see that the index in the Azure Portal has been updated to include your new CafeGeolocation field, but it is stored as an Edm.String object.
This may be fine for your purposes, and if so, you’re done here! But, what if other applications accessing this index require the coordinates to be stored as Edm.GeographyPoint objects. How can we change the field in Azure Search?
To do this, we will be using two events that can be found on the following page: https://docs.kentico.com/k11/configuring-kentico/setting-up-search-on-your-website/using-azure-search/customizing-azure-search.
- DocumentFieldCreator.CreatingField - Occurs when the system creates individual fields for a document within an Azure Search index. Triggered separately for each field of every indexed object.
- DocumentCreator.AddingDocumentValue - Occurs when the system sets the values of individual fields for documents within an Azure Search index. Triggered separately for each field of every indexed object.
5. Time to write some code!
As the documentation explains, the event handlers we need to use can be registered using a custom Module class: https://docs.kentico.com/k11/custom-development/creating-custom-modules/initializing-modules-to-run-custom-code. Although our recommendation is to add these types of customizations in a separate library project, for the sake of simplicity you can create your custom files and store them in the /App_Code folder (or /Old_App_Code for web applications). Create a new class for your custom Module, e.g. “CustomEventHandlers” and enter the following code:
using CMS;
using CMS.DataEngine;
using CMS.Helpers;
using CMS.Search.Azure;
using Microsoft.Spatial;
using System;
[assembly: RegisterModule(typeof(CustomEventHandlers))]
public class CustomEventHandlers: Module
{
public CustomEventHandlers(): base("CustomEventHandlers")
{
}
protected override void OnInit()
{
base.OnInit();
}
}
Now, we need to change the automatically-generated “cafegeolocation” field in the Azure Search service from Edm.String to Edm.GeographyPoint. This is where the CreatingField event comes into play. Inside the OnInit() method, add an event handler:
DocumentFieldCreator.Instance.CreatingField.After += CreatingField_After;
Add the CreatingField_After method to the class:
private void CreatingField_After(object sender, CreateFieldEventArgs e)
{
if (e.SearchField.FieldName == "CafeGeolocation")
{
e.Field = new Microsoft.Azure.Search.Models.Field("cafegeolocation", Microsoft.Azure.Search.Models.DataType.GeographyPoint);
}
}
This code will run when the “cafegeolocation” field is created during index generation, and will change the field type to Edm.GeographyPoint. However, the value that is stored in that field will still be the plain comma-separated string that you entered in the Kentico interface, which will cause Azure to throw errors when the index rebuilds if we leave it that way.
To account for this, we need to dynamically convert the lat/lng string into an Edm.GeographyPoint object every time the index is built. We register the AddingDocumentValue event handler in OnInit():
DocumentCreator.Instance.AddingDocumentValue.Execute += AddingDocumentValue_Execute;
Then, add the method that will convert the string value into a GeographyPoint:
private void AddingDocumentValue_Execute(object sender, AddDocumentValueEventArgs e)
{
if (e.AzureName == "cafegeolocation")
{
if (!DataHelper.IsEmpty(e.Value))
{
string[] latlng = e.Value.ToString().Split(',');
if (latlng.Length == 2 && !DataHelper.IsEmpty(latlng[0]) && !DataHelper.IsEmpty(latlng[1]))
{
e.Value = GeographyPoint.Create(Double.Parse(latlng[0].Trim()), Double.Parse(latlng[1].Trim()));
}
}
}
}
At this point, you may get the following error message:
The type 'Object' is defined in an assembly that is not referenced. You must add a reference to assembly 'System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a'.
Visual Studio should be able to resolve this issue if you press CTRL+. and click the suggested fix, or you can add the following to your web.config’s <compilation><assemblies> section:
<add assembly="System.Runtime, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" />
6. Finishing up
One more change needs to be made to the CustomEventHandlers.cs file for this to work. The C# type Microsoft.Spatial.GeographyPoint needs to be mapped to the Edm.GeographyPoint type, as described here: https://docs.kentico.com/k11/configuring-kentico/setting-up-search-on-your-website/using-azure-search/customizing-azure-search/registering-custom-data-types-for-azure-search. To do this, you can add the following to the OnInit() method:
DataMapper.Instance.RegisterMapping(typeof(GeographyPoint), Microsoft.Azure.Search.Models.DataType.GeographyPoint);
You can now click the Rebuild button on your Smart Search index. It may take a few minutes, but eventually you will see the CafeGeolocation field is stored as an Edm.GeographyPoint object in the Azure Search service.
Now that we are able to store Edm.GeographyPoint types in Azure Search it means we can use some of Azure’s built in functionality like calculating the distance between two points and filtering or ordering the results all on the Search Service. You can test this against your index by going into your Azure Search Service, into the Search explorer and use the following filter:
$filter=geo.distance(cafegeolocation, geography'POINT(-71.071138 42.300101)') le 300
It should return all the cafés within 300 km from the given point. In this case it will return Boston and New York, since it is a Boston geolocation.
We can also order the results by how far they are to that same point to get back a relevant list of cafes for our user by adding the following to the filter query:
&orderby=geo.distance(cafegeolocation, geography'POINT(-71.071138 42.300101)')
Using this index we could easily create a custom store locator web part to allow users to discover the closest cafés to their location.