Using Azure Cognitive Search Suggesters in ASP.NET Core and Autocomplete

This post shows how to implement an autocomplete in an ASP.NET Core Razor Page using Azure Cognitive Search Suggesters.

Code: https://github.com/damienbod/AspNetCoreAzureSearch

Posts in this series

Create the index with the Suggester

To use Suggesters in Azure Cognitive Search, the index requires a suggester definition. This can be implemented using the Azure.Search.Documents Nuget package. A new SearchIndex can be created and the Suggesters list can be used to add a new suggester. The name of the suggester is required so that it can be used in the search and the fields of the search index which are to be used in the suggester. The suggester fields must exist in the index.

public async Task CreateIndex()
{
	FieldBuilder bulder = new FieldBuilder();
	var definition = new SearchIndex(
		_index, bulder.Build(typeof(PersonCity)));
		
	definition.Suggesters.Add(
		new SearchSuggester(
			"personSg", new string[] 
			{ "Name", "FamilyName", "Info", "CityCountry" }
	));

	await _searchIndexClient.CreateIndexAsync(definition)
		.ConfigureAwait(false);
}

Searching the index using the Suggester

The SearchClient is used to send the search requests to Azure Cognitive Search. The Suggest method prepares the SuggestOptions options and uses the Azure.Search.Documents SuggestAsync method of the SearchClient instance to query the index.

using Azure;
using Azure.Search.Documents;
using Azure.Search.Documents.Models;
using Microsoft.Extensions.Configuration;
using System;
using System.Threading.Tasks;

namespace AspNetCoreAzureSearch
{
    public class SearchProviderAutoComplete
    {
        private readonly SearchClient _searchClient;
        private readonly string _index;

        public SearchProviderAutoComplete(IConfiguration configuration)
        {
            _index = configuration["PersonCitiesIndexName"];

            Uri serviceEndpoint = new Uri(configuration["PersonCitiesSearchUri"]);
            AzureKeyCredential credential = 
               new AzureKeyCredential(configuration["PersonCitiesSearchApiKey"]);
            _searchClient = new SearchClient(serviceEndpoint, _index, credential);

        }

        public async Task<SuggestResults<PersonCity>> Suggest(
            bool highlights, bool fuzzy, string term)
        {
            SuggestOptions sp = new SuggestOptions()
            {
                UseFuzzyMatching = fuzzy, 
                Size = 5,
            };
            sp.Select.Add("Id");
            sp.Select.Add("Name");
            sp.Select.Add("FamilyName");
            sp.Select.Add("Info");
            sp.Select.Add("CityCountry");
            sp.Select.Add("Web");

            if (highlights)
            {
                sp.HighlightPreTag = "<b>";
                sp.HighlightPostTag = "</b>";
            }

            var suggestResults = await _searchClient.SuggestAsync<PersonCity>(term, "personSg", sp)
              .ConfigureAwait(false);
            return suggestResults.Value;
        }
    }
}

Autocomplete in ASP.NET Razor Page

The OnGetAutoCompleteSuggest method of the Razor Page sends a suggest search request using no highlighting and a fuzzy search with the required term. If you were using an Azure Cognitive Search AuthComplete together with the suggester, then maybe the fuzzy could be set to false.

using Azure.Search.Documents.Models;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Logging;
using System.Collections.Generic;
using System.Threading.Tasks;

namespace AspNetCoreAzureSearch.Pages
{
    public class SearchAutoCompleteModel : PageModel
    {
        private readonly SearchProviderAutoComplete _searchProviderAutoComplete;
        private readonly ILogger<IndexModel> _logger;

        public string SearchText { get; set; }

        public SuggestResults<PersonCity> PersonCities;

        public SearchAutoCompleteModel(SearchProviderAutoComplete searchProviderAutoComplete,
            ILogger<IndexModel> logger)
        {
            _searchProviderAutoComplete = searchProviderAutoComplete;
            _logger = logger;
        }

        public void OnGet()
        {
        }

        public async Task<ActionResult> OnGetAutoCompleteSuggest(string term)
        {
            PersonCities = await _searchProviderAutoComplete.Suggest(false, true, term);
            SearchText = term;

            return new JsonResult(PersonCities.Results);
        }
    }
}

The razor page view sends the ajax request using the jQuery autocomplete and processes the result. The data is displayed in a bootstrap 4 card.

@page "{handler?}"
@model SearchAutoCompleteModel
@{
    ViewData["Title"] = "Auto complete suggester";
}

<fieldset class="form">
    <legend>Search for a person in the search engine</legend>
    <table width="500">
        <tr>
            <th></th>
        </tr>
        <tr>
            <td>
                <input class="form-control" id="autocomplete" type="text" style="width:500px" />
            </td>
        </tr>
    </table>
</fieldset>

<br />

<div class="card">
    <h5 class="card-header">
        <span id="docName"></span>
        <span id="docFamilyName"></span>
    </h5>
    <div class="card-body">
        <p class="card-text"><span id="docInfo"></span></p>
        <p class="card-text"><span id="docCityCountry"></span></p>
        <p class="card-text"><span id="docWeb"></span></p>
    </div>
</div>

@section scripts
{
    <script type="text/javascript">
        var items;
        $(document).ready(function () {
            $("input#autocomplete").autocomplete({
                source: function (request, response) {
                    $.ajax({
                        url: "SearchAutoComplete/AutoCompleteSuggest",
                        dataType: "json",
                        data: {
                            term: request.term,
                        },
                        success: function (data) {
                            var itemArray = new Array();
                            for (i = 0; i < data.length; i++) {
                                itemArray[i] = {
                                    label: data[i].document.name + " " + data[i].document.familyName,
                                    value: data[i].document.name + " " + data[i].document.familyName,
                                    data: data[i]
                                }
                            }

                            console.log(itemArray);
                            response(itemArray);
                        },
                        error: function (data, type) {
                            console.log(type);
                        }
                    });
                },
                select: function (event, ui) {
                    $("#docNameId").text(ui.item.data.id);
                    $("#docName").text(ui.item.data.document.name);
                    $("#docFamilyName").text(ui.item.data.document.familyName);
                    $("#docInfo").text(ui.item.data.document.info);
                    $("#docCityCountry").text(ui.item.data.document.cityCountry);
                    $("#docWeb").text(ui.item.data.document.web);
                    console.log(ui.item);
                }
            });
        });
    </script>
}

The required npm packages are loaded using npm and bundled using the bundle Config. The javascript autocomplete is part of the jquery-ui-dist npm package. Or course if you implement a Vue.JS, React or Angular UI, you would use a different autocomplete lib.

{
  "version": "1.0.0",
  "name": "asp.net",
  "private": true,
  "devDependencies": {
    "bootstrap": "4.5.3",
    "jquery": "3.5.1",
    "jquery-ui-dist": "^1.12.1",
    "jquery-validation": "1.19.2",
    "jquery-validation-unobtrusive": "3.2.11",
    "jquery-ajax-unobtrusive": "3.2.6",
    "popper.js": "^1.16.1"
  },
  "dependencies": {
  }
}

When the application is started, the search runs as required.

This could also be implemented using the word autocomplete together with the search but returning the results in a table view or something similar.

Links

https://docs.microsoft.com/en-us/rest/api/searchservice/autocomplete

https://docs.microsoft.com/en-us/azure/search

https://docs.microsoft.com/en-us/azure/search/search-what-is-azure-search

https://docs.microsoft.com/en-us/rest/api/searchservice/

https://github.com/Azure-Samples/azure-search-dotnet-samples/

https://channel9.msdn.com/Shows/AI-Show/Azure-Cognitive-Search-Deep-Dive-with-Debug-Sessions

https://channel9.msdn.com/Shows/AI-Show/Azure-Cognitive-Search-Whats-new-in-security

3 comments

  1. […] Using Azure Cognitive Search Suggesters in ASP.NET Core and Autocomplete […]

  2. […] Using Azure Cognitive Search Suggesters in ASP.NET Core and Autocomplete (Damien Bowden) […]

  3. […] Using Azure Cognitive Search Suggesters in ASP.NET Core and Autocomplete – Damien Bowden […]

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.

%d bloggers like this: