Connectingedit

Connecting to Elasticsearch with Elasticsearch.Net is quite easy and there a few options to suit a number of different use cases.

Choosing the right Connection Strategyedit

If you simply new an ElasticLowLevelClient, it will be a non-failover connection to http://localhost:9200

var client = new ElasticLowLevelClient();

If your Elasticsearch node does not live at http://localhost:9200 but instead lives somewhere else, for example, http://mynode.example.com:8082/apiKey, then you will need to pass in some instance of IConnectionConfigurationValues.

The easiest way to do this is:

var node = new Uri("http://mynode.example.com:8082/apiKey");

var config = new ConnectionConfiguration(node);

var client = new ElasticLowLevelClient(config);

This will still be a non-failover connection, meaning if that node goes down the operation will not be retried on any other nodes in the cluster.

To get a failover connection we have to pass an IConnectionPool instance instead of a Uri.

var node = new Uri("http://mynode.example.com:8082/apiKey");

var connectionPool = new SniffingConnectionPool(new[] { node });

var config = new ConnectionConfiguration(connectionPool);

var client = new ElasticLowLevelClient(config);

Here instead of directly passing node, we pass a SniffingConnectionPool which will use our node to find out the rest of the available cluster nodes. Be sure to read more about Connection Pooling and Cluster Failover.

Configuration Optionsedit

Besides either passing a Uri or IConnectionPool to ConnectionConfiguration, you can also fluently control many more options. For instance:

var node = new Uri("http://mynode.example.com:8082/apiKey");

var connectionPool = new SniffingConnectionPool(new[] { node });

var config = new ConnectionConfiguration(connectionPool)
    .DisableDirectStreaming() 
    .BasicAuthentication("user", "pass")
    .RequestTimeout(TimeSpan.FromSeconds(5));

Additional options are fluent method calls on ConnectionConfiguration

The following is a list of available connection configuration options:

var config = new ConnectionConfiguration()
    .DisableAutomaticProxyDetection() 
    .EnableHttpCompression() 
    .DisableDirectStreaming(); 

var client = new ElasticLowLevelClient(config);

var result = client.Search<SearchResponse<object>>(new { size = 12 });

Disable automatic proxy detection. When called, defaults to true.

Enable compressed request and responses from Elasticsearch (Note that nodes need to be configured to allow this. See the http module settings for more info).

By default responses are deserialized directly from the response stream to the object you tell it to. For debugging purposes, it can be very useful to keep a copy of the raw response on the result object, which is what calling this method will do.

.ResponseBodyInBytes will only have a value if the client configuration has DisableDirectStreaming set

var raw = result.ResponseBodyInBytes;

Please note that using .DisableDirectStreaming only makes sense if you need the mapped response and the raw response at the same time. If you need only a string response simply call

var stringResult = client.Search<string>(new { });

and similarly, if you need only a byte[]

var byteResult = client.Search<byte[]>(new { });

other configuration options

config = config
    .GlobalQueryStringParameters(new NameValueCollection()) 
    .Proxy(new Uri("http://myproxy"), "username", "pass") 
    .RequestTimeout(TimeSpan.FromSeconds(4)) 
    .ThrowExceptions() 
    .PrettyJson() 
    .BasicAuthentication("username", "password");

Allows you to set querystring parameters that have to be added to every request. For instance, if you use a hosted elasticserch provider, and you need need to pass an apiKey parameter onto every request.

Sets proxy information on the connection.

Sets the global maximum time a connection may take. Please note that this is the request timeout, the builtin .NET WebRequest has no way to set connection timeouts (see the MSDN documentation on HttpWebRequest.Timeout Property).

As an alternative to the C/go like error checking on response.IsValid, you can instead tell the client to throw exceptions.

forces all serialization to be indented and appends pretty=true to all the requests so that the responses are indented as well

Note

Basic authentication credentials can alternatively be specified on the node URI directly:

var uri = new Uri("http://username:password@localhost:9200");

var settings = new ConnectionConfiguration(uri);

…but this may become tedious when using connection pooling with multiple nodes.

Exceptionsedit

There are three categories of exceptions that may be thrown:

ElasticsearchClientException
These are known exceptions, either an exception that occurred in the request pipeline (such as max retries or timeout reached, bad authentication, etc…) or Elasticsearch itself returned an error (could not parse the request, bad query, missing field, etc…). If it is an Elasticsearch error, the ServerError property on the response will contain the the actual error that was returned. The inner exception will always contain the root causing exception.
UnexpectedElasticsearchClientException
These are unknown exceptions, for instance a response from Elasticsearch not properly deserialized. These are usually bugs and should be reported. This exception also inherits from ElasticsearchClientException so an additional catch block isn’t necessary, but can be helpful in distinguishing between the two.
Development time exceptions
These are CLR exceptions like ArgumentException, ArgumentOutOfRangeException, etc. that are thrown when an API in the client is misused. These should not be handled as you want to know about them during development.

OnRequestCompletededit

You can pass a callback of type Action<IApiCallDetails> that can eavesdrop every time a response (good or bad) is created. If you have complex logging needs this is a good place to add that in.

var counter = 0;

var client = TestClient.GetInMemoryClient(s => s.OnRequestCompleted(r => counter++));

client.RootNodeInfo();

counter.Should().Be(1);

client.RootNodeInfoAsync();

counter.Should().Be(2);

OnRequestCompleted is called even when an exception is thrown

var counter = 0;

var client = TestClient.GetFixedReturnClient(new { }, 500, s => s
    .ThrowExceptions()
    .OnRequestCompleted(r => counter++)
);

Assert.Throws<ElasticsearchClientException>(() => client.RootNodeInfo());

counter.Should().Be(1);

Assert.ThrowsAsync<ElasticsearchClientException>(() => client.RootNodeInfoAsync());

counter.Should().Be(2);

Complex logging with OnRequestCompletededit

Here’s an example of using OnRequestCompleted() for complex logging. Remember, if you would also like to capture the request and/or response bytes, you also need to set .DisableDirectStreaming() to true

var list = new List<string>();

var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

var settings = new ConnectionSettings(connectionPool, new InMemoryConnection()) 
    .DefaultIndex("default-index")
    .DisableDirectStreaming()
    .OnRequestCompleted(response =>
    {
        // log out the request and the request body, if one exists for the type of request
        if (response.RequestBodyInBytes != null)
        {
            list.Add(
                $"{response.HttpMethod} {response.Uri} \n" +
                $"{Encoding.UTF8.GetString(response.RequestBodyInBytes)}");
        }
        else
        {
            list.Add($"{response.HttpMethod} {response.Uri}");
        }

        // log out the response and the response body, if one exists for the type of response
        if (response.ResponseBodyInBytes != null)
        {
            list.Add($"Status: {response.HttpStatusCode}\n" +
                     $"{Encoding.UTF8.GetString(response.ResponseBodyInBytes)}\n" +
                     $"{new string('-', 30)}\n");
        }
        else
        {
            list.Add($"Status: {response.HttpStatusCode}\n" +
                     $"{new string('-', 30)}\n");
        }
    });

var client = new ElasticClient(settings);

var syncResponse = client.Search<object>(s => s
    .AllTypes()
    .AllIndices()
    .Scroll("2m")
    .Sort(ss => ss
        .Ascending(SortSpecialField.DocumentIndexOrder)
    )
);

list.Count.Should().Be(2);

var asyncResponse = await client.SearchAsync<object>(s => s
    .AllTypes()
    .AllIndices()
    .Scroll("2m")
    .Sort(ss => ss
        .Ascending(SortSpecialField.DocumentIndexOrder)
    )
);

list.Count.Should().Be(4);

list.ShouldAllBeEquivalentTo(new[]
{
    "POST http://localhost:9200/_search?scroll=2m \n{\"sort\":[{\"_doc\":{\"order\":\"asc\"}}]}",
    "Status: 200\n------------------------------\n",
    "POST http://localhost:9200/_search?scroll=2m \n{\"sort\":[{\"_doc\":{\"order\":\"asc\"}}]}",
    "Status: 200\n------------------------------\n"
});

Here we use InMemoryConnection; in reality you would use another type of IConnection that actually makes a request.

Configuring SSLedit

SSL can be configured via the ServerCertificateValidationCallback property on either ServerPointManager or HttpClientHandler depending on which version of the .NET framework is in use.

On the full .NET Framework, this must be done outside of the client using .NET’s built-in ServicePointManager class:

ServicePointManager.ServerCertificateValidationCallback += (sender, cert, chain, errors) => true;

The bare minimum to make .NET accept self-signed SSL certs that are not in the Windows CA store would be to have the callback simply return true.

However, this will accept all requests from the AppDomain to untrusted SSL sites, therefore we recommend doing some minimal introspection on the passed in certificate.

If running on Core CLR, then a custom connection type must be created by deriving from HttpConnection and overriding the CreateHttpClientHandler method in order to set the ServerCertificateCustomValidationCallback property.

Overriding Json.NET settingsedit

Overriding the default Json.NET behaviour in NEST is an expert behavior but if you need to get to the nitty gritty, this can be really useful.

The easiest way is to create an instance of SerializerFactory that allows you to register a modification callback in the constructor

var pool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

var connectionSettings = new ConnectionSettings(
    pool,
    new HttpConnection(),
    new SerializerFactory((jsonSettings, nestSettings) => jsonSettings.PreserveReferencesHandling = PreserveReferencesHandling.All));

var client = new ElasticClient(connectionSettings);

A more involved and explicit way would be to implement your own JsonNetSerializer subclass.

Note

this is subject to change in the next major release. NEST relies heavily on stateful deserializers (that have access to the original request) for specialized features such a covariant search results. This requirement leaks into this abstraction.

public class MyJsonNetSerializer : JsonNetSerializer
{
    public MyJsonNetSerializer(IConnectionSettingsValues settings)
        : base(settings, (s, csv) => s.PreserveReferencesHandling = PreserveReferencesHandling.All) 
    {
        OverwriteDefaultSerializers((s, cvs) => ModifySerializerSettings(s)); 
    }

    public int CallToModify { get; set; } = 0;

    private void ModifySerializerSettings(JsonSerializerSettings settings) => ++CallToModify;

    public int CallToContractConverter { get; set; } = 0;

    protected override IList<Func<Type, JsonConverter>> ContractConverters => new List<Func<Type, JsonConverter>> 
    {
        t => {
            CallToContractConverter++;
            return null;
        }
    };

}

Call this constructor if you only need access to JsonSerializerSettings without local state

Call OverwriteDefaultSerializers if you need access to JsonSerializerSettings with local state

You can inject contract resolved converters by implementing the ContractConverters property. This can be much faster then registering them on JsonSerializerSettings.Converters

You can then register a factory on ConnectionSettings to create an instance of your subclass instead. This is called once per instance of ConnectionSettings.

var connectionPool = new SingleNodeConnectionPool(new Uri("http://localhost:9200"));

var settings = new ConnectionSettings(connectionPool, new InMemoryConnection(), s => new MyJsonNetSerializer(s));

var client = new ElasticClient(settings);

client.RootNodeInfo();

client.RootNodeInfo();

var serializer = ((IConnectionSettingsValues)settings).Serializer as MyJsonNetSerializer;

serializer.CallToModify.Should().BeGreaterThan(0);

serializer.SerializeToString(new Project { });

serializer.CallToContractConverter.Should().BeGreaterThan(0);