YamlHttpClient 2.0.0-rc5
See the version list below for details.
dotnet add package YamlHttpClient --version 2.0.0-rc5
NuGet\Install-Package YamlHttpClient -Version 2.0.0-rc5
<PackageReference Include="YamlHttpClient" Version="2.0.0-rc5" />
<PackageVersion Include="YamlHttpClient" Version="2.0.0-rc5" />
<PackageReference Include="YamlHttpClient" />
paket add YamlHttpClient --version 2.0.0-rc5
#r "nuget: YamlHttpClient, 2.0.0-rc5"
#:package YamlHttpClient@2.0.0-rc5
#addin nuget:?package=YamlHttpClient&version=2.0.0-rc5&prerelease
#tool nuget:?package=YamlHttpClient&version=2.0.0-rc5&prerelease
YamlHttpClient
YAML config-based .NET HttpClient with retry, caching, chaos engineering, and multi-step orchestration.
Download from NuGet: https://www.nuget.org/packages/YamlHttpClient/
Table of contents
- Basic usage
- YAML config reference
- Loading config
- Dependency injection
- AutoCallAsync
- Retry
- Cache
- Chaos Monkey
- Orchestrator
- Handlebars helpers
- Features checklist
Basic usage
var anyInputObject = new
{
table = new[] { "v1", "v2" },
date = new DateTime(2000, 1, 1),
obj = new[] { new { test = 1 }, new { test = 2 } },
val1 = new Dictionary<string, object>() { { "testkey", "testval" } },
place = "urlPartDemo",
System = new { CodeNT = "internalCode" }
};
// Load settings from a YAML file, targeting a named key
YamlHttpClientFactory httpClient = new YamlHttpClientFactory(
new YamlHttpClientConfigBuilder().LoadFromFile("myYamlConfig.yml", "myHttpCall"));
// Build the HTTP message — placeholders are resolved from anyInputObject
var request = httpClient.BuildRequestMessage(anyInputObject);
// Optionally inspect the built content
var readContent = await request.Content.ReadAsStringAsync();
// Send
var response = await httpClient.SendAsync(request);
// Read response
var returnData = await response.Content.ReadAsStringAsync();
// Assert response matches check_response rules from config (throws if not)
await httpClient.CheckResponseAsync(response);
Or use the shorthand that combines build + send in one call:
var response = await httpClient.AutoCallAsync(anyInputObject);
await httpClient.CheckResponseAsync(response);
YAML config reference
http_client:
myHttpCall:
method: POST
url: https://api.example.com/{{place}}/endpoint
# NTLM auto-negotiation (app pool identity)
use_default_credentials: true
# HTTP Basic authentication
# auth_basic: 'username:password'
headers:
CodeNT: '{{System.CodeNT}}'
Accept: 'application/json'
content:
# JSON body with Handlebars templating
json_content: |
{
"someVal": "{{val1}}",
"flattenObj": {{{Json . ">flatten;_;_{0}" ">forcestring"}}}
"obj": {{{Json .}}}
}
# Other content types (pick one):
# string_content: 'raw text {{val}}'
# form_content:
# field1: value1
# field2: '{{val}}'
# Throws if the response body does not match expectations
check_response:
throw_exception_if_body_contains_any:
- error
throw_exception_if_body_not_contains_all:
- success
retry:
max_retries: 3
delay_milliseconds: 500
retry_on_status_codes:
- 500
- 502
- 503
cache:
enabled: true
ttl_seconds: 120
chaos:
enabled: false
injection_rate_percentage: 33
delay_milliseconds: 200
simulate_status_code: 503
simulate_network_exception: false
Loading config
Three loaders are available on YamlHttpClientConfigBuilder:
// From a file path
var settings = new YamlHttpClientConfigBuilder().LoadFromFile("config.yml", "myHttpCall");
// From a YAML string (e.g. loaded from a database or environment variable)
var settings = new YamlHttpClientConfigBuilder().LoadFromString(yamlString, "myHttpCall");
// From a byte array (e.g. embedded resource)
var settings = new YamlHttpClientConfigBuilder().LoadFromBytes(yamlBytes, "myHttpCall");
var httpClient = new YamlHttpClientFactory(settings);
Dependency injection
// Startup / Program.cs
services.AddYamlHttpClientAccessor();
// In your service
public class MyService
{
private readonly IYamlHttpClientAccessor _client;
public MyService(IYamlHttpClientAccessor client)
{
_client = client;
_client.HttpClientSettings = new YamlHttpClientConfigBuilder()
.LoadFromFile("config.yml", "myHttpCall");
_client.HandlebarsProvider = YamlHttpClientFactory.CreateDefaultHandleBars();
}
public async Task<string> CallAsync(object data)
{
var response = await _client.AutoCallAsync(data);
await _client.CheckResponseAsync(response);
return await response.Content.ReadAsStringAsync();
}
}
AutoCallAsync
AutoCallAsync is a convenience method that wraps BuildRequestMessage + SendAsync(Func<HttpRequestMessage>) in a single call. It integrates with the retry and cache pipelines automatically.
// Without cancellation token
var response = await httpClient.AutoCallAsync(myData);
// With cancellation token
var response = await httpClient.AutoCallAsync(myData, cancellationToken);
Use
AutoCallAsyncinstead ofBuildRequestMessage+SendAsyncwhenever you want retry and cache to work together correctly — the factory-based overload ofSendAsyncis required for those features.
Retry
Retry is configured per HTTP client in YAML. The engine retries on specified HTTP status codes and on transient network exceptions (HttpRequestException, timeout).
http_client:
myHttpCall:
method: GET
url: https://api.example.com/data
retry:
max_retries: 3 # Number of retries after the initial attempt
delay_milliseconds: 500 # Wait between attempts
retry_on_status_codes: # Only retry on these codes; omit to retry only on exceptions
- 500
- 502
- 503
- 504
var settings = new YamlHttpClientConfigBuilder().LoadFromString(yaml, "myHttpCall");
var httpClient = new YamlHttpClientFactory(settings);
// AutoCallAsync handles retries transparently
var response = await httpClient.AutoCallAsync(myData);
Cache
Responses are cached in memory keyed by Method + URL + body hash. Only successful responses (2xx) are cached. The cache is shared across all instances for the same URL.
http_client:
myHttpCall:
method: GET
url: https://api.example.com/reference-data
cache:
enabled: true
ttl_seconds: 600 # 10 minutes; default is 600
var settings = new YamlHttpClientConfigBuilder().LoadFromString(yaml, "myHttpCall");
var httpClient = new YamlHttpClientFactory(settings);
// First call hits the network; subsequent identical calls return from cache
var response = await httpClient.AutoCallAsync(myData);
Cache and retry work together: the cache is checked before the retry loop, and a successful retry response is stored in cache.
Chaos Monkey
Chaos Monkey injects failures into your HTTP calls at a configurable rate. This is useful for testing resilience locally or in a staging environment without needing an unstable external service.
http_client:
myHttpCall:
method: POST
url: https://api.example.com/endpoint
chaos:
enabled: true
injection_rate_percentage: 30 # 30% of calls will be affected
delay_milliseconds: 300 # Always add 300ms delay (regardless of injection rate)
simulate_status_code: 503 # Return a fake 503 on affected calls
# simulate_network_exception: true # Throw HttpRequestException instead
The three chaos modes (combinable):
| Option | Effect |
|---|---|
delay_milliseconds |
Adds a fixed delay to every call |
simulate_status_code |
Returns a fake HTTP response with that status code |
simulate_network_exception: true |
Throws an HttpRequestException (simulates DNS failure, connection reset, etc.) |
simulate_status_code and simulate_network_exception are only triggered when a call falls within the injection_rate_percentage. delay_milliseconds is always applied when set.
// Chaos is fully transparent to calling code — no changes needed
var response = await httpClient.AutoCallAsync(myData);
Combining chaos with retry lets you verify your retry policy actually recovers from failures:
http_client:
myHttpCall:
method: GET
url: https://api.example.com/data
chaos:
enabled: true
injection_rate_percentage: 50
simulate_status_code: 503
retry:
max_retries: 3
delay_milliseconds: 100
retry_on_status_codes:
- 503
Orchestrator
YamlHttpOrchestrator (requires .NET 6+) executes named pipelines defined in http_client_set. Each pipeline runs an ordered sequence of HTTP calls; every step's response is aggregated and made available to subsequent steps and to the final data_adapter template via Handlebars.
YAML config
A single YAML file contains both http_client_set (the pipelines) and http_client (the individual client definitions). Each pipeline references its clients by name and defines its own data_adapter output template.
http_client_set:
users:
sequence:
- http_client: get_token
- http_client: get_users
as: get_users # optional alias; defaults to http_client name
data_adapter:
template: |
{
"count": {{get_users.body.total}},
"items": {{{Json get_users.body.records}}}
}
user_orders:
sequence:
- http_client: get_token
- http_client: get_user
- http_client: get_orders
data_adapter:
template: |
{
"customer": "{{get_user.body.name}}",
"email": "{{get_user.body.email}}",
"orders": {{{Json get_orders.body.records}}}
}
http_client:
get_token:
method: POST
url: https://auth.example.com/token
content:
json_content: |
{ "client_id": "{{input.clientId}}", "client_secret": "{{input.secret}}" }
get_users:
method: GET
url: https://api.example.com/users
headers:
Authorization: 'Bearer {{get_token.body.access_token}}'
Accept: 'application/json'
retry:
max_retries: 2
delay_milliseconds: 500
retry_on_status_codes: [500, 502, 503]
chaos:
enabled: false
injection_rate_percentage: 50
simulate_status_code: 500
get_user:
method: GET
url: 'https://api.example.com/users/{{input.userId}}'
headers:
Authorization: 'Bearer {{get_token.body.access_token}}'
Accept: 'application/json'
get_orders:
method: GET
# References input data and a previous step's response
url: 'https://api.example.com/orders?userId={{get_user.body.id}}'
headers:
Authorization: 'Bearer {{get_token.body.access_token}}'
Accept: 'application/json'
If
data_adapter.templateis omitted or blank,ExecuteSetAsyncreturns the full aggregated data object as raw JSON.
Data model inside templates
After each step completes, its result is added to the aggregated data object under its alias (defaulting to the http_client name). The full shape available in any template is:
{
input: { ...your inputData... },
get_token: { body: {...}, headers: {...}, url: "https://..." },
get_user: { body: {...}, headers: {...}, url: "https://..." },
get_orders: { body: {...}, headers: {...}, url: "https://..." }
}
Both url fields in http_client definitions and data_adapter templates have full access to this object via Handlebars.
C# usage — ExecuteSetAsync
The preferred entry point. Loads the pipeline and all client definitions directly from the parsed config:
using YamlHttpClient;
using YamlHttpClient.Settings;
using YamlDotNet.Serialization;
using YamlDotNet.Serialization.NamingConventions;
// Parse the full YAML config (both http_client_set and http_client)
var yaml = File.ReadAllText("pipeline.yml");
var config = new DeserializerBuilder()
.WithNamingConvention(NullNamingConvention.Instance)
.IgnoreUnmatchedProperties()
.Build()
.Deserialize<YamlHttpClientConfigBuilder>(yaml);
var handlebars = YamlHttpClientFactory.CreateDefaultHandleBars();
var orchestrator = new YamlHttpOrchestrator(handlebars);
// Execute a named pipeline by key
var result = await orchestrator.ExecuteSetAsync(
setName: "regions",
config: config,
inputData: new { clientId = "my-app", secret = "s3cr3t" },
defaultHttpTimeout: TimeSpan.FromSeconds(30),
ct: CancellationToken.None
);
Console.WriteLine(result);
// Inspect which URLs were actually called
foreach (var url in orchestrator.LastCalledUrls)
Console.WriteLine($"Called: {url}");
C# usage — ExecuteSequenceAsync (advanced)
Use this overload when the sequence and data adapter template are defined in C# rather than in YAML:
var steps = new List<dynamic>
{
new { HttpClient = "get_token", As = "get_token" },
new { HttpClient = "get_regions", As = "get_regions" }
};
var result = await orchestrator.ExecuteSequenceAsync(
inputData: new { clientId = "my-app", secret = "s3cr3t" },
sequenceAppels: steps,
dictClientsConfig: config.HttpClient,
dataAdapterTemplate: "{{get_regions.body.result.records}}",
defaultHttpTimeout: TimeSpan.FromSeconds(30),
ct: CancellationToken.None
);
Default options
YamlHttpOrchestratorOptions provides fallback cache and retry settings applied to any step that does not define its own:
| Option | Default |
|---|---|
DefaultCacheSettings.Enabled |
true |
DefaultCacheSettings.TtlSeconds |
1200 (20 min) |
DefaultRetrySettings.MaxRetries |
3 |
DefaultRetrySettings.RetryOnStatusCodes |
500, 501, 502, 503, 504 |
Pass null for options to use these defaults, or override selectively.
Handlebars helpers
All templates (URL, headers, body, data adapter) are processed with Handlebars.Net.
{{{Json VAR}}}
Serializes a variable to JSON.
{{{Json obj}}} → {"test":1}
{{{Json obj ">forcestring"}}} → "{\"test\":1}"
{{{Json val1 val2}}} → "concatenated strings"
{{{Json VAR ">flatten;SEP;IDX"}}}
Flattens a nested object to a single-level dictionary.
{{{Json . ">flatten;.;[{0}]"}}} → {"obj[0].test":1,"obj[1].test":2}
{{{Json . ">flatten;_;_{0}"}}} → {"obj_0_test":1,"obj_1_test":2}
{{{Json . ">flatten;_;_{0}" ">forcestring"}}} → {"obj_0_test":"1","obj_1_test":"2"}
{{#ifCond A OP B}}
Conditional block helper. Supported operators: =, ==, !=, <>, <, >, contains, in.
{{#ifCond status '=' 'active'}}Active{{else}}Inactive{{/ifCond}}
{{#ifCond roles 'contains' 'admin'}}Has admin{{/ifCond}}
{{{Base64 VAR}}}
Encodes an object or image to Base64.
{{{Base64 myObject}}}
Template caching
Templates are compiled once and cached automatically via CompileWithCache. Call HandleBarsExtensions.ClearTemplateCache() if you need to reload templates at runtime (e.g. hot reload of YAML config).
Features checklist
- ✅ All HTTP methods (GET, POST, PUT, DELETE, PATCH...)
- ✅ Any request headers with Handlebars templating
- ✅ JSON, string, form data and binary (Base64) content types
- ✅ Basic HTTP authentication
- ✅ NTLM authentication (app pool auto-negotiation)
- ✅ Response validation with configurable rules (
check_response) - ✅ Automatic retry with configurable status codes and delay
- ✅ In-memory response cache with TTL
- ✅ Chaos Monkey (delay, fake status code, network exception simulation)
- ✅ Multi-step HTTP orchestration with data aggregation (
YamlHttpOrchestrator, .NET 6+) - ✅ Dependency injection support (
IYamlHttpClientAccessor) - ⬜ NTLM with explicit user/password
- ⬜ Client certificate authentication
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net5.0 is compatible. net5.0-windows was computed. net6.0 is compatible. net6.0-android was computed. net6.0-ios was computed. net6.0-maccatalyst was computed. net6.0-macos was computed. net6.0-tvos was computed. net6.0-windows was computed. net7.0 is compatible. net7.0-android was computed. net7.0-ios was computed. net7.0-maccatalyst was computed. net7.0-macos was computed. net7.0-tvos was computed. net7.0-windows was computed. net8.0 is compatible. net8.0-android was computed. net8.0-browser was computed. net8.0-ios was computed. net8.0-maccatalyst was computed. net8.0-macos was computed. net8.0-tvos was computed. net8.0-windows was computed. net9.0 was computed. net9.0-android was computed. net9.0-browser was computed. net9.0-ios was computed. net9.0-maccatalyst was computed. net9.0-macos was computed. net9.0-tvos was computed. net9.0-windows was computed. net10.0 is compatible. net10.0-android was computed. net10.0-browser was computed. net10.0-ios was computed. net10.0-maccatalyst was computed. net10.0-macos was computed. net10.0-tvos was computed. net10.0-windows was computed. |
| .NET Core | netcoreapp3.1 is compatible. |
-
.NETCoreApp 3.1
- Handlebars.Net (>= 2.1.1)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Caching.Memory (>= 3.1.17)
- Microsoft.Extensions.Http (>= 3.1.17)
- Microsoft.Extensions.Options (>= 3.1.17)
- Microsoft.Extensions.Primitives (>= 3.1.17)
- Newtonsoft.Json (>= 13.0.1)
- System.Drawing.Common (>= 5.0.3)
- YamlDotNet (>= 11.2.1)
-
net10.0
- Handlebars.Net (>= 2.1.1)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Caching.Memory (>= 3.1.17)
- Microsoft.Extensions.Http (>= 3.1.17)
- Microsoft.Extensions.Options (>= 3.1.17)
- Microsoft.Extensions.Primitives (>= 3.1.17)
- Newtonsoft.Json (>= 13.0.1)
- System.Drawing.Common (>= 5.0.3)
- YamlDotNet (>= 11.2.1)
-
net5.0
- Handlebars.Net (>= 2.1.1)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Caching.Memory (>= 3.1.17)
- Microsoft.Extensions.Http (>= 3.1.17)
- Microsoft.Extensions.Options (>= 3.1.17)
- Microsoft.Extensions.Primitives (>= 3.1.17)
- Newtonsoft.Json (>= 13.0.1)
- System.Drawing.Common (>= 5.0.3)
- YamlDotNet (>= 11.2.1)
-
net6.0
- Handlebars.Net (>= 2.1.1)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Caching.Memory (>= 3.1.17)
- Microsoft.Extensions.Http (>= 3.1.17)
- Microsoft.Extensions.Options (>= 3.1.17)
- Microsoft.Extensions.Primitives (>= 3.1.17)
- Newtonsoft.Json (>= 13.0.1)
- System.Drawing.Common (>= 5.0.3)
- YamlDotNet (>= 11.2.1)
-
net7.0
- Handlebars.Net (>= 2.1.1)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Caching.Memory (>= 3.1.17)
- Microsoft.Extensions.Http (>= 3.1.17)
- Microsoft.Extensions.Options (>= 3.1.17)
- Microsoft.Extensions.Primitives (>= 3.1.17)
- Newtonsoft.Json (>= 13.0.1)
- System.Drawing.Common (>= 5.0.3)
- YamlDotNet (>= 11.2.1)
-
net8.0
- Handlebars.Net (>= 2.1.1)
- Microsoft.AspNetCore.Http.Abstractions (>= 2.2.0)
- Microsoft.Extensions.Caching.Memory (>= 3.1.17)
- Microsoft.Extensions.Http (>= 3.1.17)
- Microsoft.Extensions.Options (>= 3.1.17)
- Microsoft.Extensions.Primitives (>= 3.1.17)
- Newtonsoft.Json (>= 13.0.1)
- System.Drawing.Common (>= 5.0.3)
- YamlDotNet (>= 11.2.1)
NuGet packages
This package is not used by any NuGet packages.
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 2.0.0 | 0 | 3/12/2026 |
| 2.0.0-rc5 | 0 | 3/12/2026 |
| 2.0.0-rc4 | 29 | 3/12/2026 |
| 2.0.0-rc3 | 32 | 3/12/2026 |
| 2.0.0-rc2 | 75 | 3/9/2026 |
| 2.0.0-rc1 | 85 | 3/9/2026 |
| 1.0.8 | 578 | 6/16/2025 |
| 1.0.7 | 227 | 12/23/2024 |
| 1.0.6 | 642 | 9/17/2024 |
| 1.0.5 | 266 | 9/15/2023 |
| 1.0.4 | 225 | 9/15/2023 |
| 1.0.3 | 199 | 9/15/2023 |
| 1.0.2 | 345 | 3/15/2023 |
| 1.0.1 | 836 | 3/29/2022 |
| 1.0.0 | 642 | 1/30/2022 |
| 1.0.0-rc7 | 360 | 11/9/2021 |
| 1.0.0-rc6 | 375 | 11/9/2021 |