A Quick Guide to Elasticsearch for .NET

Implementing search functionality in your .NET Core apps doesn’t have to be hard! Using Elasticsearch makes it easy to develop fast, searchable apps.

In this post, I’ll walk you through building a simple web application using Okta (for user authentication), Elastic Cloud (the official Elasticsearch hosting provider), and the fabulous Elasticsearch NEST SDK.

Why Use Elasticsearch?

Elasticsearch is an analytics and search engine based on the Apache Lucene library. It is developed in Java, following an open-core business model. Deep down within Elasticsearch lays a schema-free, JSON document-oriented database. This means that data is stored as documents, similar to rows in a relational database like MySQL. A document’s data is separated into fields, like columns in a relational database. Documents are essentially just JSON objects. Elasticsearch indexes these documents and searches them quickly. So, even if you have to search through millions of documents, searches are still going to be lightning fast.

Set Up a Sample Application with the Okta CLI

Okta is a secure and customizable solution to add authentication to your app. Okta supports any language on any stack and lets you define how you want your users to sign in. Each time a user tries to authenticate, Okta will verify their identity and send the required information back to your app.

There are now two options to get started with Okta. You can create an account and set up a sample .NET Core application with Okta authentication using the Okta Developer Console and by following the quick start guide. Or you can use the recently released Okta CLI, which provides turnkey application templates configured and ready to use after completing a few simple steps in the command line.

Install the Okta CLI by following the steps on the GitHub page.

Simply run the following command to start the CLI:

okta start

If you already have an Okta Developer account, you can run:

okta login

Select the ASP.NET Core MVC + Okta option when prompted.

A ready application should be created, with the Okta login flow already set up—this includes writing secrets to appsettings.json and adding the new client to your Applications on the Okta Developer Console.

You can test run the application by hitting F5 in Visual Studio or by entering dotnet run in your command line. A browser window will open and you should be able to log in using your Okta credentials.

I have uploaded my working application to GitHub in case you get stuck: Okta .NET Core 3 Elasticsearch Example.

Set Up an Elasticsearch Instance in the Cloud

Go to Elasticsearch Cloud and register for a new account. A free trial will be enough for us here. Elastic Cloud is an Elasticsearch hosting provider that will save you the trouble of learning how to set up, configure, and deploy Elasticsearch locally or in production.

Select Elastic Stack and leave everything else on default.

Hit Create deployment.

Make sure you save your deployment credentials for later.

Once the deployment is complete, click Open Kibana. You may have to log in with the credentials from above.

Click Add Data.

On the Sample Data tab, select the Sample eCommerce orders card.

After this, you have an Elasticsearch database set up and filled with sample data with eCommerce orders. Time to start searching.

Open the Elasticsearch Deployments page. Under the Applications header, click on Copy endpoint next to Elasticsearch. Paste this somewhere for later. Should be something like: https://c0fd6d13bb70485c9b062af5d1a24a82.us-central1.gcp.cloud.es.io:9243. You will need it to be able to connect from your sample application along with the credentials from earlier.

Add Elasticsearch to a .NET Core Application

The plan here is to add a search bar to the application and query the Elasticsearch database of sample orders. The search will result in a list of orders where the name of the customer matches our search condition.

Make sure the application that you created earlier works properly. Open the solution in a preferred code editor and run it.

Log in with your Okta credentials to see Okta is configured as expected. You will provide the search functionality for logged-in users only.

Install the Elasticsearch NEST Client

NEST is an official high-level client created by Elasticsearch that maps all requests and responses as types and provides an interface to execute queries.

Let’s add the NEST Client from NuGet via package manager or by running the following command from the directory where the .csproj file lives:

dotnet add package NEST

Configure the Elasticsearch NEST Client

Open appsettings.json and add your deployment endpoint and credentials.

  "ElasticCloud": {
    "Endpoint": "https://c0fd6d13bb70485c9b062af5d1a24a82.us-central1.gcp.cloud.es.io:9243",
    "BasicAuthUser": "elastic",
    "BasicAuthPassword": "YourPassword"
  }

Create a new file called SearchClient.cs in the folder where the .csproj file lives. Add the following code to add a ISearchClient interface.

using System;
using Microsoft.Extensions.Configuration;
using Nest;
using okta_aspnetcore_mvc_example.Models;

namespace okta_aspnetcore_mvc_example
{
    public interface ISearchClient
    {
        ISearchResponse<Order> SearchOrder(string searchText);
    }
}

Make a SearchClient implementation and initialize the ElasticClient in the constructor just above the ISearchClient declaration, so that your final SearchClient.cs file looks like this:

using System;
using Microsoft.Extensions.Configuration;
using Nest;
using okta_aspnetcore_mvc_example.Models;

namespace okta_aspnetcore_mvc_example
{
    public class SearchClient : ISearchClient
    {
        private readonly ElasticClient client;

        public SearchClient(IConfiguration configuration)
        {
            client = new ElasticClient(
                new ConnectionSettings(new Uri(configuration.GetValue<string>("ElasticCloud:Endpoint")))
                    .DefaultIndex("kibana_sample_data_ecommerce")
                    .BasicAuthentication(configuration.GetValue<string>("ElasticCloud:BasicAuthUser"),
                        configuration.GetValue<string>("ElasticCloud:BasicAuthPassword")));
        }

        public ISearchResponse<Order> SearchOrder(string searchText)
        {
            return client.Search<Order>(s => s
                .From(0)
                .Size(10)
                .Query(q => q
                    .Match(m => m
                        .Field(f => f.CustomerFullName)
                        .Query(searchText)
                    )
                ));
        }
    }

    public interface ISearchClient
    {
        ISearchResponse<Order> SearchOrder(string searchText);
    }
}

The SeachClient class takes IConfiguration to read the configuration values (Elastic endpoint URL, user name, password) from appsettings.json to access the Elastic instance.

The SearchOrder() function takes a searchTextparameter to search within the orders by the full name of the customer and list the first ten10 results. See the documentation on how to write more complex queries.

Using the Elasticsearch NEST Client

First, you need to add the SearchClient in Startup.cs as a Singleton object. Add the following line of code to the bottom of the ConfigureServices() method:

services.AddSingleton<ISearchClient, SearchClient>();

Now, it is ready to be initialized by the HomeController’s constructor.

public class HomeController : Controller
{
    private readonly ISearchClient searchClient;

    public HomeController(ISearchClient searchClient)
    {
        this.searchClient = searchClient;
    }
// ...
}

Then add the Model to represent the search text and the search result orders. Add a new file in the Models folder called SearchResultsModel.cs:

using System.Collections.Generic;

namespace okta_aspnetcore_mvc_example.Models
{
    public class SearchResultsModel
    {
        public string SearchText { get; set; }
        
        public List<Order> Results { get; set; }
    }
}

Also add an Order.cs:

using System;
using Nest;

namespace okta_aspnetcore_mvc_example.Models
{
    public class Order
    {
        public int Id { get; set; }
        
        [Text(Name = "customer_full_name")]
        public string CustomerFullName { get; set; }
        
        [Date(Name = "order_date", Format = "MMddyyyy")]
        public DateTime OrderDate { get; set; }
        
        [Number(Name = "taxful_total_price")]
        public decimal TotalPrice { get; set; }
    }
}

Add Search.cshtml with a search bar and some logic to display the result orders in a table.

@model SearchResultsModel
@{
    ViewData["Title"] = "Search Page";
}
<div class="text-center">
    <form class="form-inline" asp-action="Search" asp-controller="Search">
        <div class="form-group">
            <input class="form-control m-2" asp-for="SearchText" placeholder="Search"/>
        </div>
        <button type="submit" class="btn btn-primary m-2">Search</button>
    </form>
    @if (Model?.Results != null)
    {
        <div>
            <table class="table">
                <thead>
                <tr>
                    <th>Customer Name</th>
                    <th>Order Date</th>
                    <th>Total Price</th>
                </tr>
                </thead>
                <tbody>
                @foreach (var result in Model.Results)
                {
                    <tr>
                        <td>@result.CustomerFullName</td>
                        <td>@result.OrderDate</td>
                        <td>@result.TotalPrice</td>
                    </tr>
                }
                </tbody>
            </table>
        </div>
    }
</div>

Add a Search(string searchText) function to your HomeController to handle the search text and retrieve the results.

        [Authorize]
        [HttpPost]
        public IActionResult Search(string searchText)
        {
            var response = searchClient.SearchOrder(searchText);
            var model = new SearchResultsModel {Results = response.Documents.ToList()};
            return View(model);
        }  

Adding the [Authorize] attribute is a good idea here to make sure this endpoint is only accessible to logged-in users. Okta will handle the rest.

Run and Test

Let’s see what we have done. Run the application and search for Mary. You should see something like this:

Takeaways

Elasticsearch is great for numerous use cases, including searching for semi-structured or unstructured data. They provide NEST, a high-level easy-to-use client for .NET that maps all requests and responses as types. It takes advantage of specific .NET features to provide higher-level abstractions, such as auto-mapping. By combining Okta and Elasticsearch, you can build secure and efficient applications with minimal coding and debugging.

Learn More About Using Elasticsearch and Okta in .NET Core

If you have any questions about this post, please ask in the comments below. For more tutorials like this one,, follow @oktadev on Twitter, like us on Facebook, or subscribe to our YouTube channel.