Elastic.Transport 0.11.1
Elastic.Transport
Transport classes and utilities shared among .NET Elastic client libraries. Provides cluster-aware, resilient HTTP transport optimized for the Elastic product suite and Elastic Cloud.
Installation
dotnet add package Elastic.Transport
Quick Start
var settings = new TransportConfiguration(new Uri("http://localhost:9200"));
var transport = new DistributedTransport(settings);
// GET request — returns body as string
var response = transport.Get<StringResponse>("/my-index/_search?q=title:hello");
// POST request — send JSON body
var body = PostData.String(@"{ ""query"": { ""match_all"": {} } }");
var searchResponse = transport.Post<StringResponse>("/my-index/_search", body);
// HEAD request — no body needed
var headResponse = transport.Head("/my-index");
// JSON DOM with safe path traversal
var jsonResponse = transport.Get<JsonResponse>("/my-index/_search?q=title:hello");
int totalHits = jsonResponse.Get<int>("hits.total.value");
string firstId = jsonResponse.Get<string>("hits.hits.[0]._id");
// Async variants
var asyncResponse = await transport.GetAsync<StringResponse>("/my-index/_search?q=title:hello");
Response Types
The generic type parameter on Get<TResponse>, Post<TResponse>, etc. controls how the response body is read:
| Type | Body Representation | Notes |
|---|---|---|
StringResponse |
string |
Good for debugging and small payloads |
BytesResponse |
byte[] |
Raw bytes, useful for binary content |
VoidResponse |
(skipped) | Body is not read. Used for HEAD and fire-and-forget calls |
StreamResponse |
Stream |
Caller must dispose. Best for large payloads |
JsonResponse |
JsonNode |
System.Text.Json DOM with safe Get<T>() path traversal |
DynamicResponse |
DynamicDictionary |
Legacy — prefer JsonResponse |
JsonResponse
JsonResponse deserializes JSON into a System.Text.Json.Nodes.JsonNode and exposes a Get<T>() method for safe, typed path traversal using dot-separated keys:
var response = transport.Get<JsonResponse>("/my-index/_search?q=title:hello");
// Traverse nested JSON with dot notation
int totalHits = response.Get<int>("hits.total.value");
string firstId = response.Get<string>("hits.hits.[0]._id");
// Bracket syntax for array access
string lastId = response.Get<string>("hits.hits.[last()]._id");
string firstSource = response.Get<string>("hits.hits.[first()]._source.title");
// _arbitrary_key_ traverses into the first key at that level
string fieldType = response.Get<string>("my-index.mappings.properties._arbitrary_key_.type");
// Direct DOM access is also available via .Body
JsonNode hitsNode = response.Body["hits"]["hits"];
Configuration
Single node
var settings = new TransportConfiguration(new Uri("http://localhost:9200"));
Elastic Cloud (cloud ID)
var settings = new TransportConfiguration("my-cloud-id", new ApiKey("base64key"));
// or
var settings = new TransportConfiguration("my-cloud-id", new BasicAuthentication("user", "pass"));
Multiple nodes with a node pool
var pool = new StaticNodePool(new[]
{
new Node(new Uri("http://node1:9200")),
new Node(new Uri("http://node2:9200")),
new Node(new Uri("http://node3:9200"))
});
var settings = new TransportConfiguration(pool);
var transport = new DistributedTransport(settings);
All components
var pool = new StaticNodePool(new[] { new Node(new Uri("http://localhost:9200")) });
var requestInvoker = new HttpRequestInvoker();
var product = ElasticsearchProductRegistration.Default;
var settings = new TransportConfiguration(pool, requestInvoker, productRegistration: product);
var transport = new DistributedTransport(settings);
Request Pipeline
The transport models a request pipeline that handles node failover, sniffing, and pinging:

The pipeline introduces two special API calls:
- Sniff — queries the cluster to discover the current node topology
- Ping — the fastest possible request to check if a node is alive
The transport fails over in constant time. If a node is marked dead, it is skipped immediately (as long as the overall request timeout allows).
Components
| Component | Description |
|---|---|
NodePool |
Registry of Node instances. Implementations: SingleNodePool, StaticNodePool, SniffingNodePool, StickyNodePool, CloudNodePool |
IRequestInvoker |
Abstraction for HTTP I/O. Default: HttpRequestInvoker |
Serializer |
Request/response serialization. Default uses System.Text.Json |
ProductRegistration |
Product-specific metadata, sniff/ping behavior. Use ElasticsearchProductRegistration for Elasticsearch |
Observability
Every response inherits from TransportResponse and exposes an ApiCallDetails property:
var response = transport.Get<StringResponse>("/");
// Structured call metadata
ApiCallDetails details = response.ApiCallDetails;
Console.WriteLine(details.HttpStatusCode);
Console.WriteLine(details.Uri);
// Human-readable debug string
Console.WriteLine(details.DebugInformation);
The transport also emits DiagnosticSource events for serialization timing, time-to-first-byte, and other counters.
Custom Typed Responses
Any class inheriting from TransportResponse can be used as a response type. The transport will deserialize the response body into it using System.Text.Json:
public class SearchResult : TransportResponse
{
[JsonPropertyName("hits")]
public HitsContainer Hits { get; set; }
}
public class HitsContainer
{
[JsonPropertyName("total")]
public TotalHits Total { get; set; }
[JsonPropertyName("hits")]
public List<Hit> Hits { get; set; }
}
public class TotalHits
{
[JsonPropertyName("value")]
public long Value { get; set; }
}
public class Hit
{
[JsonPropertyName("_id")]
public string Id { get; set; }
[JsonPropertyName("_source")]
public JsonNode Source { get; set; }
}
// Use it directly as a type parameter
var response = transport.Get<SearchResult>("/my-index/_search?q=title:hello");
long total = response.Hits.Total.Value;
For full control over how a response is built from the stream, implement TypedResponseBuilder<TResponse> and register it via ResponseBuilders on the configuration:
public class CsvResponse : TransportResponse
{
public List<string[]> Rows { get; set; }
}
public class CsvResponseBuilder : TypedResponseBuilder<CsvResponse>
{
protected override CsvResponse Build(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration,
Stream responseStream, string contentType, long contentLength)
{
using var reader = new StreamReader(responseStream);
var rows = new List<string[]>();
while (reader.ReadLine() is { } line)
rows.Add(line.Split(','));
return new CsvResponse { Rows = rows };
}
protected override async Task<CsvResponse> BuildAsync(ApiCallDetails apiCallDetails, BoundConfiguration boundConfiguration,
Stream responseStream, string contentType, long contentLength, CancellationToken cancellationToken = default)
{
using var reader = new StreamReader(responseStream);
var rows = new List<string[]>();
while (await reader.ReadLineAsync(cancellationToken) is { } line)
rows.Add(line.Split(','));
return new CsvResponse { Rows = rows };
}
}
var settings = new TransportConfiguration(new Uri("http://localhost:9200"))
{
ResponseBuilders = [new CsvResponseBuilder()]
};
AOT and Source Generators
The default serializer uses System.Text.Json with a JsonSerializerContext for AOT compatibility. When using custom typed responses in AOT/trimmed applications, provide a JsonSerializerContext that includes your response types:
[JsonSerializable(typeof(SearchResult))]
[JsonSerializable(typeof(HitsContainer))]
[JsonSerializable(typeof(TotalHits))]
[JsonSerializable(typeof(Hit))]
public partial class MySerializerContext : JsonSerializerContext;
Create a concrete serializer that combines your context with the transport's built-in resolvers:
public class MySerializer : SystemTextJsonSerializer
{
public MySerializer() : base(new TransportSerializerOptionsProvider([], null, options =>
{
options.TypeInfoResolver = JsonTypeInfoResolver.Combine(
MySerializerContext.Default,
new DefaultJsonTypeInfoResolver()
);
})) { }
}
var settings = new TransportConfiguration(
new SingleNodePool(new Uri("http://localhost:9200")),
serializer: new MySerializer()
);
Links
Showing the top 20 packages that depend on Elastic.Transport.
| Packages | Downloads |
|---|---|
|
Elastic.Clients.Elasticsearch
This strongly-typed, client library enables working with Elasticsearch. It is the official client maintained and supported by Elastic.
|
6 |
|
Elastic.Clients.Elasticsearch
This strongly-typed, client library enables working with Elasticsearch. It is the official client maintained and supported by Elastic.
|
4 |
|
Elastic.Clients.Elasticsearch
This strongly-typed, client library enables working with Elasticsearch. It is the official client maintained and supported by Elastic.
|
3 |
.NET Framework 4.6.2
- Microsoft.CSharp (>= 4.7.0)
- System.Text.Json (>= 10.0.0)
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
.NET Standard 2.1
- System.Text.Json (>= 10.0.0)
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
- Microsoft.CSharp (>= 4.7.0)
.NET Standard 2.0
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
- Microsoft.CSharp (>= 4.7.0)
- System.Text.Json (>= 10.0.0)
.NET 8.0
- System.Text.Json (>= 10.0.0)
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
.NET 10.0
- No dependencies.
| Version | Downloads | Last updated |
|---|---|---|
| 0.15.1 | 0 | 11.03.2026 |
| 0.15.0 | 2 | 01.03.2026 |
| 0.14.0 | 1 | 01.03.2026 |
| 0.13.0 | 1 | 01.03.2026 |
| 0.12.0 | 1 | 21.02.2026 |
| 0.11.1 | 1 | 21.02.2026 |
| 0.11.0 | 1 | 21.02.2026 |
| 0.10.3 | 1 | 21.02.2026 |
| 0.10.2 | 3 | 11.12.2025 |
| 0.10.1 | 5 | 05.09.2025 |
| 0.10.0 | 5 | 05.09.2025 |
| 0.9.2 | 6 | 04.09.2025 |
| 0.9.1 | 6 | 04.09.2025 |
| 0.9.0 | 6 | 04.09.2025 |
| 0.8.1 | 6 | 04.09.2025 |
| 0.8.0 | 6 | 04.09.2025 |
| 0.7.0 | 6 | 04.09.2025 |
| 0.6.1 | 6 | 04.09.2025 |
| 0.5.9 | 6 | 04.09.2025 |
| 0.5.8 | 5 | 04.09.2025 |
| 0.5.7 | 6 | 04.09.2025 |
| 0.5.6 | 6 | 04.09.2025 |
| 0.5.5 | 6 | 04.09.2025 |
| 0.5.4 | 5 | 04.09.2025 |
| 0.5.3 | 6 | 04.09.2025 |
| 0.5.2 | 6 | 04.09.2025 |
| 0.5.1 | 6 | 04.09.2025 |
| 0.5.0 | 6 | 04.09.2025 |
| 0.4.26 | 5 | 05.09.2025 |
| 0.4.25 | 5 | 05.09.2025 |
| 0.4.24 | 5 | 05.09.2025 |
| 0.4.23 | 5 | 05.09.2025 |
| 0.4.22 | 5 | 05.09.2025 |
| 0.4.21 | 5 | 05.09.2025 |
| 0.4.20 | 5 | 05.09.2025 |
| 0.4.19 | 5 | 05.09.2025 |
| 0.4.18 | 5 | 05.09.2025 |
| 0.4.17 | 5 | 05.09.2025 |
| 0.4.16 | 5 | 05.09.2025 |
| 0.4.15 | 5 | 05.09.2025 |
| 0.4.14 | 5 | 05.09.2025 |
| 0.4.13 | 5 | 05.09.2025 |
| 0.4.12 | 5 | 05.09.2025 |
| 0.4.11 | 5 | 05.09.2025 |
| 0.4.10 | 5 | 05.09.2025 |
| 0.4.9 | 6 | 04.09.2025 |
| 0.4.8 | 6 | 04.09.2025 |
| 0.4.7 | 6 | 04.09.2025 |
| 0.4.6 | 6 | 04.09.2025 |
| 0.4.5 | 6 | 04.09.2025 |
| 0.4.3 | 5 | 04.09.2025 |
| 0.4.2 | 6 | 04.09.2025 |
| 0.4.1 | 6 | 04.09.2025 |
| 0.4.0 | 6 | 04.09.2025 |
| 0.3.2 | 6 | 04.09.2025 |
| 0.3.1 | 6 | 04.09.2025 |
| 0.3.0 | 5 | 04.09.2025 |
| 0.2.3 | 6 | 04.09.2025 |
| 0.2.2 | 6 | 04.09.2025 |
| 0.2.1 | 6 | 04.09.2025 |
| 0.2.0 | 6 | 04.09.2025 |
| 0.1.3 | 5 | 04.09.2025 |
| 0.1.2 | 6 | 04.09.2025 |