Skip to content

Commit 17c3896

Browse files
committed
Register ES client
1 parent 52ef588 commit 17c3896

7 files changed

+247
-0
lines changed

Elasticstretch.DependencyInjection/Elasticstretch.DependencyInjection.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@
2424
</PropertyGroup>
2525

2626
<ItemGroup>
27+
<PackageReference Include="Elastic.Clients.Elasticsearch" Version="*" />
28+
<PackageReference Include="Microsoft.Extensions.Options" Version="*" />
29+
<PackageReference Include="Microsoft.Extensions.Options.ConfigurationExtensions" Version="*" />
2730
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="*" PrivateAssets="All" />
2831
</ItemGroup>
2932

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
namespace Elastic.Clients.Elasticsearch;
2+
3+
using Elastic.Clients.Elasticsearch.Options;
4+
using Microsoft.Extensions.DependencyInjection;
5+
using Microsoft.Extensions.DependencyInjection.Extensions;
6+
using Microsoft.Extensions.Options;
7+
8+
/// <summary>
9+
/// Extensions of <see cref="IServiceCollection"/> for the Elasticsearch client.
10+
/// </summary>
11+
public static class ElasticstretchServiceCollectionExtensions
12+
{
13+
/// <summary>
14+
/// Adds a singleton <see cref="ElasticsearchClient"/> to the services.
15+
/// </summary>
16+
/// <remarks>
17+
/// <see cref="ElasticsearchNodeOptions"/> is also bound to the <c>Elasticsearch</c> configuration section.
18+
/// </remarks>
19+
/// <param name="services">The service collection.</param>
20+
/// <param name="configureNodes">A delegate to configure Elasticsearch nodes.</param>
21+
/// <param name="configureSettings">A delegate to configure additional client settings.</param>
22+
/// <returns>The same services, for chaining.</returns>
23+
public static IServiceCollection AddElasticsearchClient(
24+
this IServiceCollection services,
25+
Action<ElasticsearchNodeOptions>? configureNodes = null,
26+
Action<ElasticsearchClientSettings>? configureSettings = null)
27+
{
28+
services.AddOptions();
29+
30+
TryConfigure<ElasticsearchNodeOptions, ConfigureNodesFromConfig>(services);
31+
TryConfigure<ElasticsearchClientOptions, ConfigureClientFromNodes>(services);
32+
33+
services.TryAddSingleton(
34+
x => new ElasticsearchClient(
35+
x.GetRequiredService<IOptions<ElasticsearchClientOptions>>().Value.ToSettings()));
36+
37+
if (configureNodes != null)
38+
{
39+
services.Configure(configureNodes);
40+
}
41+
42+
if (configureSettings != null)
43+
{
44+
services.Configure<ElasticsearchClientOptions>(x => x.ConfigureSettings += configureSettings);
45+
}
46+
47+
return services;
48+
}
49+
50+
static void TryConfigure<TOptions, TSetup>(IServiceCollection services)
51+
where TOptions : class
52+
where TSetup : class, IConfigureOptions<TOptions>
53+
{
54+
services.TryAddEnumerable(ServiceDescriptor.Singleton<IConfigureOptions<TOptions>, TSetup>());
55+
}
56+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
3+
[assembly: SuppressMessage(
4+
"Performance",
5+
"CA1812:Avoid uninstantiated internal classes",
6+
Scope = "type",
7+
Target = "~T:Elastic.Clients.Elasticsearch.Options.ConfigureNodesFromConfig")]
8+
9+
[assembly: SuppressMessage(
10+
"Performance",
11+
"CA1812:Avoid uninstantiated internal classes",
12+
Scope = "type",
13+
Target = "~T:Elastic.Clients.Elasticsearch.Options.ConfigureClientFromNodes")]
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
namespace Elastic.Clients.Elasticsearch.Options;
2+
3+
using Elastic.Transport;
4+
using Microsoft.Extensions.Options;
5+
6+
sealed class ConfigureClientFromNodes : IConfigureNamedOptions<ElasticsearchClientOptions>
7+
{
8+
private readonly IOptionsFactory<ElasticsearchNodeOptions> nodeOptionsFactory;
9+
10+
public ConfigureClientFromNodes(IOptionsFactory<ElasticsearchNodeOptions> nodeOptionsFactory)
11+
{
12+
this.nodeOptionsFactory = nodeOptionsFactory;
13+
}
14+
15+
public void Configure(string? name, ElasticsearchClientOptions options)
16+
{
17+
var nodeOptions = nodeOptionsFactory.Create(name ?? Options.DefaultName);
18+
19+
AuthorizationHeader? credentials = nodeOptions switch
20+
{
21+
{ ApiKeyId: not null, ApiKey: not null } => new Base64ApiKey(nodeOptions.ApiKeyId, nodeOptions.ApiKey),
22+
{ ApiKey: not null } => new ApiKey(nodeOptions.ApiKey),
23+
_ => null,
24+
};
25+
26+
options.NodePool = nodeOptions switch
27+
{
28+
{ CloudId: not null } => new CloudNodePool(nodeOptions.CloudId, credentials ?? NoAuthorization.Instance),
29+
{ NodeUris.Count : 0 } => options.NodePool,
30+
{ IsSticky : true, UseSniffing : true } => new StickySniffingNodePool(nodeOptions.NodeUris, x => 0),
31+
{ IsSticky : true } => new StickyNodePool(nodeOptions.NodeUris),
32+
{ UseSniffing : true } => new SniffingNodePool(nodeOptions.NodeUris),
33+
_ => new StaticNodePool(nodeOptions.NodeUris),
34+
};
35+
36+
if (credentials != null)
37+
{
38+
options.ConfigureSettings += x => x.Authentication(credentials);
39+
}
40+
}
41+
42+
public void Configure(ElasticsearchClientOptions options)
43+
{
44+
throw new NotSupportedException();
45+
}
46+
47+
sealed class NoAuthorization : AuthorizationHeader
48+
{
49+
public static NoAuthorization Instance { get; } = new NoAuthorization();
50+
51+
public override string AuthScheme => throw new NotSupportedException();
52+
53+
private NoAuthorization()
54+
{
55+
}
56+
57+
public override bool TryGetAuthorizationParameters(out string value)
58+
{
59+
value = string.Empty;
60+
return false;
61+
}
62+
}
63+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace Elastic.Clients.Elasticsearch.Options;
2+
3+
using Microsoft.Extensions.Configuration;
4+
using Microsoft.Extensions.Options;
5+
6+
sealed class ConfigureNodesFromConfig : ConfigureFromConfigurationOptions<ElasticsearchNodeOptions>
7+
{
8+
public ConfigureNodesFromConfig(IConfiguration config)
9+
: base(config.GetSection("Elasticsearch"))
10+
{
11+
}
12+
}
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
namespace Elastic.Clients.Elasticsearch.Options;
2+
3+
using Elastic.Transport;
4+
using System.Diagnostics.CodeAnalysis;
5+
6+
/// <summary>
7+
/// A model to configure Elasticsearch clients using the .NET options pattern.
8+
/// </summary>
9+
public class ElasticsearchClientOptions
10+
{
11+
/// <summary>
12+
/// Gets or sets the client node pool, or <see langword="null"/> to connect locally.
13+
/// </summary>
14+
public NodePool? NodePool { get; set; }
15+
16+
/// <summary>
17+
/// Gets or sets the underlying transport connection, or <see langword="null"/> to use HTTP.
18+
/// </summary>
19+
public TransportClient? Connection { get; set; }
20+
21+
/// <summary>
22+
/// Gets or sets a custom factory for the client source serializer, if any.
23+
/// </summary>
24+
public ElasticsearchClientSettings.SourceSerializerFactory? SourceSerializer { get; set; }
25+
26+
/// <summary>
27+
/// Gets or sets a custom mapper for property serialization, if any.
28+
/// </summary>
29+
public IPropertyMappingProvider? PropertyMappingProvider { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets the delegate to configure additional client settings.
33+
/// </summary>
34+
public Action<ElasticsearchClientSettings>? ConfigureSettings { get; set; }
35+
36+
/// <summary>
37+
/// Converts the options to Elastic client settings.
38+
/// </summary>
39+
/// <returns>The client settings.</returns>
40+
[SuppressMessage(
41+
"Reliability",
42+
"CA2000:Dispose objects before losing scope",
43+
Justification = "Dispose ownership transferred to settings.")]
44+
public IElasticsearchClientSettings ToSettings()
45+
{
46+
var settings = new ElasticsearchClientSettings(
47+
NodePool ?? new SingleNodePool(new("localhost:9200")),
48+
Connection ?? new HttpTransportClient(),
49+
SourceSerializer ?? new((x, y) => x),
50+
PropertyMappingProvider ?? new DefaultPropertyMappingProvider());
51+
52+
ConfigureSettings?.Invoke(settings);
53+
return settings;
54+
}
55+
}
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
namespace Elastic.Clients.Elasticsearch.Options;
2+
3+
using Elastic.Transport;
4+
5+
/// <summary>
6+
/// Options for connecting to Elasticsearch nodes.
7+
/// </summary>
8+
public class ElasticsearchNodeOptions
9+
{
10+
/// <summary>
11+
/// Gets or sets the ID for Elastic's cloud-hosted search service.
12+
/// </summary>
13+
/// <remarks>
14+
/// Incompatible with <see cref="NodeUris"/>.
15+
/// </remarks>
16+
public string? CloudId { get; set; }
17+
18+
/// <summary>
19+
/// Gets or sets the list of self-hosted Elasticsearch node URIs.
20+
/// </summary>
21+
/// <remarks>
22+
/// Incompatible with <see cref="CloudId"/>.
23+
/// </remarks>
24+
public ICollection<Uri> NodeUris { get; } = new List<Uri>();
25+
26+
/// <summary>
27+
/// Gets or sets whether "sticky" node selection is used (e.g. <see cref="StickyNodePool"/>).
28+
/// </summary>
29+
public bool IsSticky { get; set; }
30+
31+
/// <summary>
32+
/// Gets or sets whether "sniffing" is used to reseed nodes (e.g. <see cref="SniffingNodePool"/>).
33+
/// </summary>
34+
public bool UseSniffing { get; set; }
35+
36+
/// <summary>
37+
/// Gets or sets the ID of the API key for node authentication, if any.
38+
/// </summary>
39+
public string? ApiKeyId { get; set; }
40+
41+
/// <summary>
42+
/// Gets or sets the API key for node authentication, if any.
43+
/// </summary>
44+
public string? ApiKey { get; set; }
45+
}

0 commit comments

Comments
 (0)